#
tokens: 8074/50000 11/11 files
lines: on (toggle) GitHub
raw markdown copy reset
# Directory Structure

```
├── .env_example
├── .gitignore
├── .python-version
├── claude-integration-guide.md
├── confluence-mcp-config.json
├── confluence.py
├── example_usage.py
├── install-claude-integration.ps1
├── pyproject.toml
├── README.md
├── requirements.txt
└── uv.lock
```

# Files

--------------------------------------------------------------------------------
/.python-version:
--------------------------------------------------------------------------------

```
1 | 3.12
2 | 
```

--------------------------------------------------------------------------------
/.env_example:
--------------------------------------------------------------------------------

```
1 | CONFLUENCE_API_BASE=http://localhost:8090/rest/api
2 | CONFLUENCE_USERNAME=admin
3 | CONFLUENCE_PASSWORD=admin
4 | 
```

--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------

```
 1 | # Python-generated files
 2 | __pycache__/
 3 | *.py[oc]
 4 | build/
 5 | dist/
 6 | wheels/
 7 | *.egg-info
 8 | 
 9 | # Virtual environments
10 | .venv
11 | .env
12 | 
```

--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Confluence MCP Server
  2 | 
  3 | A Model Context Protocol (MCP) server for interacting with Confluence Data Center via REST API. This server provides a set of tools that allow AI models to interact with Confluence content.
  4 | 
  5 | ## Features
  6 | 
  7 | This MCP server provides the following operations for Confluence:
  8 | 
  9 | - Execute CQL (Confluence Query Language) searches
 10 | - Get page content by ID
 11 | - Get page content with body
 12 | - Find pages by space key
 13 | - Find page by title and space key
 14 | - Create new pages (with optional parent page)
 15 | - Update existing pages
 16 | - Delete pages
 17 | 
 18 | ## Installation
 19 | 
 20 | 1. Clone this repository
 21 | 2. Install dependencies:
 22 | 
 23 | ```bash
 24 | pip install -r requirements.txt
 25 | ```
 26 | 
 27 | ## Configuration
 28 | 
 29 | Create a `.env` file in the project root with the following variables:
 30 | 
 31 | ```
 32 | CONFLUENCE_API_BASE=http://localhost:8090/rest/api
 33 | CONFLUENCE_USERNAME=your_username
 34 | CONFLUENCE_PASSWORD=your_password
 35 | ```
 36 | 
 37 | Adjust the values to match your Confluence instance.
 38 | 
 39 | ## Running the Server
 40 | 
 41 | ### Development Mode (Recommended)
 42 | 
 43 | The proper way to run an MCP server is using the MCP CLI tool with the development mode. This will start the MCP Inspector UI which allows you to test and debug the server:
 44 | 
 45 | ```bash
 46 | mcp dev confluence.py
 47 | ```
 48 | 
 49 | This will start the MCP Inspector at http://127.0.0.1:6274 by default.
 50 | 
 51 | ### Direct Execution (Not Recommended)
 52 | 
 53 | MCP servers are designed to be run with the MCP CLI tool or integrated with Claude Desktop. Direct execution with Python is not the standard way to run an MCP server, but the script includes a fallback mode for testing:
 54 | 
 55 | ```bash
 56 | python confluence.py
 57 | ```
 58 | 
 59 | However, this mode has limited functionality and is only intended for basic testing.
 60 | 
 61 | ### Installing in Claude Desktop
 62 | 
 63 | To install the server in Claude Desktop:
 64 | 
 65 | ```bash
 66 | mcp install confluence.py
 67 | ```
 68 | 
 69 | ## API Reference
 70 | 
 71 | ### execute_cql_search
 72 | 
 73 | Execute a CQL query on Confluence to search pages.
 74 | 
 75 | **Parameters:**
 76 | - `cql`: CQL query string
 77 | - `limit`: Number of results to return (default: 10)
 78 | 
 79 | ### get_page_content
 80 | 
 81 | Get the content of a Confluence page.
 82 | 
 83 | **Parameters:**
 84 | - `pageId`: Confluence Page ID
 85 | 
 86 | ### get_page_with_body
 87 | 
 88 | Get a page with its body content.
 89 | 
 90 | **Parameters:**
 91 | - `pageId`: Confluence Page ID
 92 | 
 93 | ### find_pages_by_space
 94 | 
 95 | Find pages by space key.
 96 | 
 97 | **Parameters:**
 98 | - `spaceKey`: Confluence Space Key
 99 | - `limit`: Maximum number of results to return (default: 10)
100 | - `expand`: Optional comma-separated list of properties to expand
101 | 
102 | ### find_page_by_title
103 | 
104 | Find a page by title and space key.
105 | 
106 | **Parameters:**
107 | - `title`: Page title
108 | - `spaceKey`: Confluence Space Key
109 | 
110 | ### create_page
111 | 
112 | Create a new page in Confluence.
113 | 
114 | **Parameters:**
115 | - `title`: Page title
116 | - `spaceKey`: Confluence Space Key
117 | - `content`: Page content in storage format (HTML)
118 | - `parentId`: Optional parent page ID
119 | 
120 | ### update_page
121 | 
122 | Update an existing page in Confluence.
123 | 
124 | **Parameters:**
125 | - `pageId`: Confluence Page ID
126 | - `content`: New page content in storage format (HTML)
127 | - `title`: Optional new title for the page
128 | - `spaceKey`: Optional space key (only needed if changing space)
129 | 
130 | ### delete_page
131 | 
132 | Delete a page by ID.
133 | 
134 | **Parameters:**
135 | - `pageId`: Confluence Page ID
136 | 
137 | ## Example Usage
138 | 
139 | Once the server is running and connected to an AI model, you can interact with Confluence using natural language. For example:
140 | 
141 | - "Find all pages in the DOCS space"
142 | - "Get the content of page with ID 123456"
143 | - "Create a new page titled 'Meeting Notes' in the TEAM space with content '<p>Notes from our meeting</p>'"
144 | - "Update page with ID 123456 to have the content '<p>Updated meeting notes</p>'"
145 | - "Update the title of page 123456 to 'Revised Meeting Notes'"
146 | 
147 | ## License
148 | 
149 | MIT
150 | 
```

--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------

```
1 | httpx>=0.24.0
2 | mcp>=0.4.0
3 | uvicorn>=0.23.0
4 | python-dotenv>=1.0.0
5 | 
```

--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------

```toml
 1 | [project]
 2 | name = "mcp-confluence-server"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.12"
 7 | dependencies = [
 8 |     "httpx>=0.28.1",
 9 |     "mcp[cli]>=1.6.0",
10 |     "python-dotenv>=1.1.0",
11 |     "uvicorn>=0.34.2",
12 | ]
13 | 
```

--------------------------------------------------------------------------------
/install-claude-integration.ps1:
--------------------------------------------------------------------------------

```
 1 | # PowerShell script to install the Confluence MCP server in Claude Desktop
 2 | # Run this script from the project directory
 3 | 
 4 | # Check if MCP is installed
 5 | try {
 6 |     $mcpVersion = (uv run mcp --version) 2>&1
 7 |     Write-Host "Found MCP: $mcpVersion"
 8 | } catch {
 9 |     Write-Host "MCP CLI not found. Installing required packages..."
10 |     uv pip install "mcp[cli]"
11 | }
12 | 
13 | # Ensure we're in the right directory
14 | $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
15 | Set-Location $scriptDir
16 | 
17 | # Check if config file exists
18 | $configFile = "confluence-mcp-config.json"
19 | if (-not (Test-Path $configFile)) {
20 |     Write-Host "Configuration file not found: $configFile"
21 |     exit 1
22 | }
23 | 
24 | # Install the MCP server in Claude Desktop
25 | Write-Host "Installing Confluence MCP server in Claude Desktop..."
26 | uv run mcp install --config $configFile
27 | 
28 | Write-Host "`nInstallation complete!"
29 | Write-Host "To verify the installation, open Claude Desktop and check the MCP Servers section in Settings."
30 | Write-Host "You can now use Claude to interact with your Confluence instance."
31 | 
```

--------------------------------------------------------------------------------
/confluence-mcp-config.json:
--------------------------------------------------------------------------------

```json
 1 | {
 2 |   "name": "confluence-server",
 3 |   "displayName": "Confluence Data Center",
 4 |   "description": "MCP server for interacting with Confluence Data Center via REST API",
 5 |   "version": "1.0.0",
 6 |   "command": "uv",
 7 |   "args": ["run", "--with", "mcp", "mcp", "run", "server.py"],
 8 |   "env": {
 9 |     "CONFLUENCE_API_BASE": "http://localhost:8090/rest/api",
10 |     "CONFLUENCE_USERNAME": "admin",
11 |     "CONFLUENCE_PASSWORD": "@Qi85yhn",
12 |     "PYTHONUNBUFFERED": "1",
13 |     "PYTHONIOENCODING": "utf-8"
14 |   },
15 |   "workingDirectory": "${configDir}",
16 |   "transportType": "stdio",
17 |   "icon": "https://wac-cdn.atlassian.com/assets/img/favicons/confluence/favicon-32x32.png",
18 |   "capabilities": {
19 |     "tools": {
20 |       "listChanged": true
21 |     },
22 |     "resources": {
23 |       "subscribe": true,
24 |       "listChanged": true
25 |     },
26 |     "prompts": {
27 |       "listChanged": true
28 |     }
29 |   },
30 |   "metadata": {
31 |     "author": "Your Name",
32 |     "homepage": "https://github.com/yourusername/mcp-confluence-server",
33 |     "category": "Knowledge Management",
34 |     "tags": ["confluence", "documentation", "knowledge-base"]
35 |   }
36 | }
37 | 
```

--------------------------------------------------------------------------------
/claude-integration-guide.md:
--------------------------------------------------------------------------------

```markdown
  1 | # Integrating Confluence MCP Server with Claude Desktop
  2 | 
  3 | This guide explains how to integrate your Confluence MCP server with Claude Desktop, allowing Claude to interact with your Confluence instance through the MCP protocol.
  4 | 
  5 | ## Prerequisites
  6 | 
  7 | - Claude Desktop installed
  8 | - Python 3.9+ installed
  9 | - `uv` and `mcp` packages installed
 10 | - Confluence server accessible
 11 | 
 12 | ## Integration Methods
 13 | 
 14 | There are two ways to integrate your MCP server with Claude Desktop:
 15 | 
 16 | 1. **Simple Installation** - Quick but with limited configuration
 17 | 2. **JSON Configuration Installation** - More control and customization
 18 | 
 19 | ## Method 1: Simple Installation
 20 | 
 21 | For a quick installation with default settings:
 22 | 
 23 | ```bash
 24 | # Navigate to your project directory
 25 | cd path/to/mcp-confluence-server
 26 | 
 27 | # Install the server in Claude Desktop
 28 | mcp install server.py
 29 | ```
 30 | 
 31 | ## Method 2: JSON Configuration Installation (Recommended)
 32 | 
 33 | This method gives you more control over how your server is configured in Claude Desktop.
 34 | 
 35 | ### Step 1: Edit the Configuration File
 36 | 
 37 | The `confluence-mcp-config.json` file contains all the settings for your MCP server. Customize it as needed:
 38 | 
 39 | - Update the `CONFLUENCE_API_BASE` to point to your Confluence instance
 40 | - Set the correct `CONFLUENCE_USERNAME` and `CONFLUENCE_PASSWORD`
 41 | - Modify the `metadata` section with your information
 42 | 
 43 | ### Step 2: Install Using the Configuration File
 44 | 
 45 | ```bash
 46 | # Navigate to your project directory
 47 | cd path/to/mcp-confluence-server
 48 | 
 49 | # Install with the configuration file
 50 | mcp install --config confluence-mcp-config.json
 51 | ```
 52 | 
 53 | ### Step 3: Verify Installation
 54 | 
 55 | 1. Open Claude Desktop
 56 | 2. Click on the "..." menu in the top-right corner
 57 | 3. Select "Settings"
 58 | 4. Go to the "MCP Servers" tab
 59 | 5. Confirm that "Confluence Data Center" appears in the list of installed servers
 60 | 
 61 | ## Using Custom Commands
 62 | 
 63 | If you need to customize how the server is run, you can modify the `command` and `args` fields in the configuration file:
 64 | 
 65 | ```json
 66 | {
 67 |   "command": "python",
 68 |   "args": ["-m", "mcp", "run", "server.py"]
 69 | }
 70 | ```
 71 | 
 72 | Or for using a virtual environment:
 73 | 
 74 | ```json
 75 | {
 76 |   "command": ".venv/Scripts/python",
 77 |   "args": ["-m", "mcp", "run", "server.py"]
 78 | }
 79 | ```
 80 | 
 81 | ## Troubleshooting
 82 | 
 83 | If you encounter issues with the integration:
 84 | 
 85 | 1. **Check Logs**: Claude Desktop logs can help identify issues
 86 |    ```bash
 87 |    mcp logs
 88 |    ```
 89 | 
 90 | 2. **Test the Server**: Make sure the server runs correctly in development mode
 91 |    ```bash
 92 |    mcp dev server.py
 93 |    ```
 94 | 
 95 | 3. **Reinstall**: If needed, uninstall and reinstall the server
 96 |    ```bash
 97 |    mcp uninstall confluence-server
 98 |    mcp install --config confluence-mcp-config.json
 99 |    ```
100 | 
101 | ## Advanced Configuration Options
102 | 
103 | The JSON configuration supports additional options:
104 | 
105 | - `icon`: URL or path to an icon for the server in Claude Desktop
106 | - `capabilities`: Specify which MCP capabilities your server supports
107 | - `metadata`: Additional information about your server
108 | - `env`: Environment variables for the server process
109 | 
110 | ## Example: Complete Installation Command
111 | 
112 | Here's a complete example command that installs the server with all options:
113 | 
114 | ```bash
115 | mcp install --config confluence-mcp-config.json --name "Confluence Server" --description "Access Confluence content from Claude" --icon https://path/to/icon.png
116 | ```
117 | 
118 | However, when using a config file, the command-line options are usually not necessary as they're defined in the JSON file.
119 | 
```

--------------------------------------------------------------------------------
/example_usage.py:
--------------------------------------------------------------------------------

```python
  1 | """
  2 | Example script demonstrating how to use the Confluence MCP server.
  3 | This script uses the MCP client to connect to the server and execute operations.
  4 | """
  5 | import asyncio
  6 | import json
  7 | from mcp import ClientSession, StdioServerParameters, types
  8 | from mcp.client.stdio import stdio_client
  9 | 
 10 | # Create server parameters for stdio connection
 11 | server_params = StdioServerParameters(
 12 |     command="python",  # Executable
 13 |     args=["server.py"],  # Server script
 14 |     env=None,  # Use default environment
 15 | )
 16 | 
 17 | async def print_json(data):
 18 |     """Print data as formatted JSON."""
 19 |     print(json.dumps(data, indent=2))
 20 | 
 21 | async def run_examples():
 22 |     """Run example operations against the Confluence server."""
 23 |     print("Connecting to Confluence MCP server...")
 24 |     
 25 |     async with stdio_client(server_params) as (read, write):
 26 |         async with ClientSession(read, write) as session:
 27 |             # Initialize the connection
 28 |             await session.initialize()
 29 |             
 30 |             # List available tools
 31 |             print("\n=== Available Tools ===")
 32 |             tools = await session.list_tools()
 33 |             for tool in tools:
 34 |                 print(f"- {tool.name}: {tool.description}")
 35 |             
 36 |             # Example 1: Find pages by space
 37 |             print("\n=== Finding pages in space 'COD' ===")
 38 |             try:
 39 |                 result = await session.call_tool(
 40 |                     "find_pages_by_space", 
 41 |                     arguments={"spaceKey": "COD", "limit": 5}
 42 |                 )
 43 |                 await print_json(result)
 44 |             except Exception as e:
 45 |                 print(f"Error finding pages: {str(e)}")
 46 |             
 47 |             # Example 2: Execute CQL search
 48 |             print("\n=== Executing CQL search ===")
 49 |             try:
 50 |                 result = await session.call_tool(
 51 |                     "execute_cql_search", 
 52 |                     arguments={"cql": "space = COD", "limit": 3}
 53 |                 )
 54 |                 await print_json(result)
 55 |             except Exception as e:
 56 |                 print(f"Error executing search: {str(e)}")
 57 |             
 58 |             # Example 3: Find page by title
 59 |             print("\n=== Finding page by title ===")
 60 |             try:
 61 |                 result = await session.call_tool(
 62 |                     "find_page_by_title", 
 63 |                     arguments={"title": "demo page", "spaceKey": "COD"}
 64 |                 )
 65 |                 await print_json(result)
 66 |                 
 67 |                 # If we found a page, get its content
 68 |                 if result.get("results") and len(result["results"]) > 0:
 69 |                     page_id = result["results"][0]["id"]
 70 |                     
 71 |                     print(f"\n=== Getting content for page {page_id} ===")
 72 |                     content_result = await session.call_tool(
 73 |                         "get_page_with_body", 
 74 |                         arguments={"pageId": page_id}
 75 |                     )
 76 |                     
 77 |                     # Just print the title and a snippet of the body to avoid too much output
 78 |                     print(f"Title: {content_result.get('title', 'Unknown')}")
 79 |                     body = content_result.get("body", {}).get("storage", {}).get("value", "")
 80 |                     print(f"Body snippet: {body[:100]}...")
 81 |             except Exception as e:
 82 |                 print(f"Error finding page: {str(e)}")
 83 |             
 84 |             # Example 4: Create a new page (commented out to avoid creating pages accidentally)
 85 |             """
 86 |             print("\n=== Creating a new page ===")
 87 |             try:
 88 |                 result = await session.call_tool(
 89 |                     "create_page", 
 90 |                     arguments={
 91 |                         "title": "Test Page from MCP Client", 
 92 |                         "spaceKey": "COD",
 93 |                         "content": "<p>This is a test page created by the MCP client.</p>"
 94 |                     }
 95 |                 )
 96 |                 await print_json(result)
 97 |                 
 98 |                 # If we created a page, delete it to clean up
 99 |                 if "id" in result:
100 |                     page_id = result["id"]
101 |                     print(f"\n=== Deleting page {page_id} ===")
102 |                     delete_result = await session.call_tool(
103 |                         "delete_page", 
104 |                         arguments={"pageId": page_id}
105 |                     )
106 |                     await print_json(delete_result)
107 |             except Exception as e:
108 |                 print(f"Error creating/deleting page: {str(e)}")
109 |             """
110 | 
111 | if __name__ == "__main__":
112 |     asyncio.run(run_examples())
113 | 
```

--------------------------------------------------------------------------------
/confluence.py:
--------------------------------------------------------------------------------

```python
  1 | from typing import Any, Dict, List, Optional, Union
  2 | import os
  3 | import base64
  4 | import json
  5 | import logging
  6 | from contextlib import asynccontextmanager
  7 | from dataclasses import dataclass
  8 | from collections.abc import AsyncIterator
  9 | 
 10 | import httpx
 11 | from dotenv import load_dotenv
 12 | from mcp.server.fastmcp import Context, FastMCP
 13 | 
 14 | # Load environment variables from .env file
 15 | load_dotenv()
 16 | 
 17 | # Configure logging
 18 | logging.basicConfig(level=logging.INFO)
 19 | logger = logging.getLogger(__name__)
 20 | 
 21 | # Constants
 22 | CONFLUENCE_API_BASE = os.environ.get("CONFLUENCE_API_BASE", "http://localhost:8090/rest/api")
 23 | CONFLUENCE_USERNAME = os.environ.get("CONFLUENCE_USERNAME", "admin")
 24 | CONFLUENCE_PASSWORD = os.environ.get("CONFLUENCE_PASSWORD", "admin")
 25 | 
 26 | # Initialize FastMCP server
 27 | mcp = FastMCP("confluence")
 28 | 
 29 | @dataclass
 30 | class ConfluenceContext:
 31 |     """Context for Confluence API client."""
 32 |     client: httpx.AsyncClient
 33 | 
 34 | @asynccontextmanager
 35 | async def confluence_lifespan(server: FastMCP) -> AsyncIterator[ConfluenceContext]:
 36 |     """Manage Confluence API client lifecycle."""
 37 |     # Create auth header
 38 |     auth_str = f"{CONFLUENCE_USERNAME}:{CONFLUENCE_PASSWORD}"
 39 |     auth_bytes = auth_str.encode('ascii')
 40 |     auth_b64 = base64.b64encode(auth_bytes).decode('ascii')
 41 |     
 42 |     # Initialize HTTP client with authentication
 43 |     async with httpx.AsyncClient(
 44 |         base_url=CONFLUENCE_API_BASE,
 45 |         headers={
 46 |             "Authorization": f"Basic {auth_b64}",
 47 |             "Content-Type": "application/json",
 48 |             "Accept": "application/json"
 49 |         },
 50 |         timeout=30.0
 51 |     ) as client:
 52 |         logger.info(f"Initialized Confluence API client for {CONFLUENCE_API_BASE}")
 53 |         yield ConfluenceContext(client=client)
 54 | 
 55 | # Set up lifespan context
 56 | mcp = FastMCP("confluence", lifespan=confluence_lifespan)
 57 | 
 58 | # Helper functions
 59 | async def handle_response(response: httpx.Response) -> Dict[str, Any]:
 60 |     """Handle API response, raising appropriate exceptions for errors."""
 61 |     try:
 62 |         response.raise_for_status()
 63 |         return response.json()
 64 |     except httpx.HTTPStatusError as e:
 65 |         error_msg = f"HTTP error: {e.response.status_code}"
 66 |         try:
 67 |             error_data = e.response.json()
 68 |             error_msg = f"{error_msg} - {error_data.get('message', 'Unknown error')}"
 69 |         except Exception:
 70 |             error_msg = f"{error_msg} - {e.response.text or 'No error details'}"
 71 |         logger.error(error_msg)
 72 |         raise ValueError(error_msg)
 73 |     except Exception as e:
 74 |         logger.error(f"Error processing response: {str(e)}")
 75 |         raise ValueError(f"Failed to process response: {str(e)}")
 76 | 
 77 | # MCP Tools for Confluence API
 78 | @mcp.tool()
 79 | async def update_page(ctx: Context, pageId: str, content: str, title: str = None, spaceKey: str = None) -> Dict[str, Any]:
 80 |     """
 81 |     Update an existing page in Confluence.
 82 |     
 83 |     Args:
 84 |         pageId: Confluence Page ID
 85 |         content: New page content in storage format (HTML)
 86 |         title: Optional new title for the page
 87 |         spaceKey: Optional space key (only needed if changing space)
 88 |         
 89 |     Returns:
 90 |         Updated page details
 91 |     """
 92 |     client = ctx.request_context.lifespan_context.client
 93 |     
 94 |     # First, get the current page to obtain version number and other details
 95 |     logger.info(f"Getting current page details for page ID: {pageId}")
 96 |     response = await client.get(f"/content/{pageId}")
 97 |     current_page = await handle_response(response)
 98 |     
 99 |     # Prepare update data
100 |     update_data = {
101 |         "id": pageId,
102 |         "type": "page",
103 |         "title": title or current_page.get("title", ""),
104 |         "body": {
105 |             "storage": {
106 |                 "value": content,
107 |                 "representation": "storage"
108 |             }
109 |         },
110 |         "version": {
111 |             "number": current_page.get("version", {}).get("number", 0) + 1
112 |         }
113 |     }
114 |     
115 |     # Add space if specified
116 |     if spaceKey:
117 |         update_data["space"] = {"key": spaceKey}
118 |     elif "space" in current_page and "key" in current_page["space"]:
119 |         update_data["space"] = {"key": current_page["space"]["key"]}
120 |     
121 |     logger.info(f"Updating page with ID: {pageId}")
122 |     response = await client.put(f"/content/{pageId}", json=update_data)
123 |     return await handle_response(response)
124 | 
125 | @mcp.tool()
126 | async def execute_cql_search(ctx: Context, cql: str, limit: int = 10) -> Dict[str, Any]:
127 |     """
128 |     Execute a CQL query on Confluence to search pages.
129 |     
130 |     Args:
131 |         cql: CQL query string
132 |         limit: Number of results to return
133 |         
134 |     Returns:
135 |         Search results
136 |     """
137 |     client = ctx.request_context.lifespan_context.client
138 |     params = {"cql": cql, "limit": limit}
139 |     
140 |     logger.info(f"Executing CQL search: {cql} with limit {limit}")
141 |     response = await client.get("/content/search", params=params)
142 |     return await handle_response(response)
143 | 
144 | @mcp.tool()
145 | async def get_page_content(ctx: Context, pageId: str) -> Dict[str, Any]:
146 |     """
147 |     Get the content of a Confluence page.
148 |     
149 |     Args:
150 |         pageId: Confluence Page ID
151 |         
152 |     Returns:
153 |         Page content
154 |     """
155 |     client = ctx.request_context.lifespan_context.client
156 |     
157 |     logger.info(f"Getting page content for page ID: {pageId}")
158 |     response = await client.get(f"/content/{pageId}")
159 |     return await handle_response(response)
160 | 
161 | @mcp.tool()
162 | async def get_page_with_body(ctx: Context, pageId: str) -> Dict[str, Any]:
163 |     """
164 |     Get a page with its body content.
165 |     
166 |     Args:
167 |         pageId: Confluence Page ID
168 |         
169 |     Returns:
170 |         Page with body content
171 |     """
172 |     client = ctx.request_context.lifespan_context.client
173 |     
174 |     logger.info(f"Getting page with body for page ID: {pageId}")
175 |     response = await client.get(f"/content/{pageId}?expand=body.storage")
176 |     return await handle_response(response)
177 | 
178 | @mcp.tool()
179 | async def find_pages_by_space(ctx: Context, spaceKey: str, limit: int = 10, expand: Optional[str] = None) -> Dict[str, Any]:
180 |     """
181 |     Find pages by space key.
182 |     
183 |     Args:
184 |         spaceKey: Confluence Space Key
185 |         limit: Maximum number of results to return
186 |         expand: Optional comma-separated list of properties to expand
187 |         
188 |     Returns:
189 |         List of pages in the space
190 |     """
191 |     client = ctx.request_context.lifespan_context.client
192 |     
193 |     params = {"spaceKey": spaceKey, "limit": limit}
194 |     if expand:
195 |         params["expand"] = expand
196 |     
197 |     logger.info(f"Finding pages in space: {spaceKey}")
198 |     # Use scan endpoint for better performance in Confluence 7.18+
199 |     try:
200 |         response = await client.get("/content/scan", params=params)
201 |         return await handle_response(response)
202 |     except Exception as e:
203 |         logger.warning(f"Scan endpoint failed, falling back to standard endpoint: {str(e)}")
204 |         response = await client.get("/content", params=params)
205 |         return await handle_response(response)
206 | 
207 | @mcp.tool()
208 | async def find_page_by_title(ctx: Context, title: str, spaceKey: str) -> Dict[str, Any]:
209 |     """
210 |     Find a page by title and space key.
211 |     
212 |     Args:
213 |         title: Page title
214 |         spaceKey: Confluence Space Key
215 |         
216 |     Returns:
217 |         Page details if found
218 |     """
219 |     client = ctx.request_context.lifespan_context.client
220 |     
221 |     params = {"title": title, "spaceKey": spaceKey}
222 |     
223 |     logger.info(f"Finding page by title: {title} in space: {spaceKey}")
224 |     response = await client.get("/content", params=params)
225 |     return await handle_response(response)
226 | 
227 | @mcp.tool()
228 | async def create_page(
229 |     ctx: Context, 
230 |     title: str, 
231 |     spaceKey: str, 
232 |     content: str, 
233 |     parentId: Optional[str] = None
234 | ) -> Dict[str, Any]:
235 |     """
236 |     Create a new page in Confluence.
237 |     
238 |     Args:
239 |         title: Page title
240 |         spaceKey: Confluence Space Key
241 |         content: Page content in storage format (HTML)
242 |         parentId: Optional parent page ID
243 |         
244 |     Returns:
245 |         Created page details
246 |     """
247 |     client = ctx.request_context.lifespan_context.client
248 |     
249 |     # Prepare page data
250 |     page_data = {
251 |         "type": "page",
252 |         "title": title,
253 |         "space": {"key": spaceKey},
254 |         "body": {
255 |             "storage": {
256 |                 "value": content,
257 |                 "representation": "storage"
258 |             }
259 |         }
260 |     }
261 |     
262 |     # Add parent if specified
263 |     if parentId:
264 |         page_data["ancestors"] = [{"id": parentId}]
265 |     
266 |     logger.info(f"Creating page: {title} in space: {spaceKey}")
267 |     response = await client.post("/content", json=page_data)
268 |     return await handle_response(response)
269 | 
270 | @mcp.tool()
271 | async def delete_page(ctx: Context, pageId: str) -> Dict[str, Any]:
272 |     """
273 |     Delete a page by ID.
274 |     
275 |     Args:
276 |         pageId: Confluence Page ID
277 |         
278 |     Returns:
279 |         Deletion status
280 |     """
281 |     client = ctx.request_context.lifespan_context.client
282 |     
283 |     logger.info(f"Deleting page with ID: {pageId}")
284 |     response = await client.delete(f"/content/{pageId}")
285 |     
286 |     if response.status_code == 204:
287 |         return {"status": "success", "message": f"Page {pageId} deleted successfully"}
288 |     
289 |     return await handle_response(response)
290 | 
291 | # This server is designed to be run with the MCP CLI tool
292 | # To run it, use: mcp dev server.py
293 | 
294 | # If you want to run it directly (not recommended), you can use this code
295 | if __name__ == "__main__":
296 |     print("=== Confluence MCP Server ===")
297 |     print("This server is designed to be run with the MCP CLI tool.")
298 |     print("To run it properly, use: mcp dev server.py")
299 |     print("\nAttempting to start in direct mode (for testing only)...")
300 |     
301 |     try:
302 |         import asyncio
303 |         from mcp.server.stdio import stdio_server
304 |         
305 |         async def run():
306 |             # This is a simplified version that works for direct execution
307 |             async with stdio_server() as (read, write):
308 |                 await mcp.start(read, write)
309 |         
310 |         # Run the server
311 |         asyncio.run(run())
312 |     except Exception as e:
313 |         print(f"\nError starting server: {e}")
314 |         print("\nPlease use 'mcp dev server.py' instead.")
315 | 
```