# 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 | ```