# 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:
--------------------------------------------------------------------------------
```
3.12
```
--------------------------------------------------------------------------------
/.env_example:
--------------------------------------------------------------------------------
```
CONFLUENCE_API_BASE=http://localhost:8090/rest/api
CONFLUENCE_USERNAME=admin
CONFLUENCE_PASSWORD=admin
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Python-generated files
__pycache__/
*.py[oc]
build/
dist/
wheels/
*.egg-info
# Virtual environments
.venv
.env
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# Confluence MCP Server
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.
## Features
This MCP server provides the following operations for Confluence:
- Execute CQL (Confluence Query Language) searches
- Get page content by ID
- Get page content with body
- Find pages by space key
- Find page by title and space key
- Create new pages (with optional parent page)
- Update existing pages
- Delete pages
## Installation
1. Clone this repository
2. Install dependencies:
```bash
pip install -r requirements.txt
```
## Configuration
Create a `.env` file in the project root with the following variables:
```
CONFLUENCE_API_BASE=http://localhost:8090/rest/api
CONFLUENCE_USERNAME=your_username
CONFLUENCE_PASSWORD=your_password
```
Adjust the values to match your Confluence instance.
## Running the Server
### Development Mode (Recommended)
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:
```bash
mcp dev confluence.py
```
This will start the MCP Inspector at http://127.0.0.1:6274 by default.
### Direct Execution (Not Recommended)
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:
```bash
python confluence.py
```
However, this mode has limited functionality and is only intended for basic testing.
### Installing in Claude Desktop
To install the server in Claude Desktop:
```bash
mcp install confluence.py
```
## API Reference
### execute_cql_search
Execute a CQL query on Confluence to search pages.
**Parameters:**
- `cql`: CQL query string
- `limit`: Number of results to return (default: 10)
### get_page_content
Get the content of a Confluence page.
**Parameters:**
- `pageId`: Confluence Page ID
### get_page_with_body
Get a page with its body content.
**Parameters:**
- `pageId`: Confluence Page ID
### find_pages_by_space
Find pages by space key.
**Parameters:**
- `spaceKey`: Confluence Space Key
- `limit`: Maximum number of results to return (default: 10)
- `expand`: Optional comma-separated list of properties to expand
### find_page_by_title
Find a page by title and space key.
**Parameters:**
- `title`: Page title
- `spaceKey`: Confluence Space Key
### create_page
Create a new page in Confluence.
**Parameters:**
- `title`: Page title
- `spaceKey`: Confluence Space Key
- `content`: Page content in storage format (HTML)
- `parentId`: Optional parent page ID
### update_page
Update an existing page in Confluence.
**Parameters:**
- `pageId`: Confluence Page ID
- `content`: New page content in storage format (HTML)
- `title`: Optional new title for the page
- `spaceKey`: Optional space key (only needed if changing space)
### delete_page
Delete a page by ID.
**Parameters:**
- `pageId`: Confluence Page ID
## Example Usage
Once the server is running and connected to an AI model, you can interact with Confluence using natural language. For example:
- "Find all pages in the DOCS space"
- "Get the content of page with ID 123456"
- "Create a new page titled 'Meeting Notes' in the TEAM space with content '<p>Notes from our meeting</p>'"
- "Update page with ID 123456 to have the content '<p>Updated meeting notes</p>'"
- "Update the title of page 123456 to 'Revised Meeting Notes'"
## License
MIT
```
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
```
httpx>=0.24.0
mcp>=0.4.0
uvicorn>=0.23.0
python-dotenv>=1.0.0
```
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
```toml
[project]
name = "mcp-confluence-server"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.12"
dependencies = [
"httpx>=0.28.1",
"mcp[cli]>=1.6.0",
"python-dotenv>=1.1.0",
"uvicorn>=0.34.2",
]
```
--------------------------------------------------------------------------------
/install-claude-integration.ps1:
--------------------------------------------------------------------------------
```
# PowerShell script to install the Confluence MCP server in Claude Desktop
# Run this script from the project directory
# Check if MCP is installed
try {
$mcpVersion = (uv run mcp --version) 2>&1
Write-Host "Found MCP: $mcpVersion"
} catch {
Write-Host "MCP CLI not found. Installing required packages..."
uv pip install "mcp[cli]"
}
# Ensure we're in the right directory
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
Set-Location $scriptDir
# Check if config file exists
$configFile = "confluence-mcp-config.json"
if (-not (Test-Path $configFile)) {
Write-Host "Configuration file not found: $configFile"
exit 1
}
# Install the MCP server in Claude Desktop
Write-Host "Installing Confluence MCP server in Claude Desktop..."
uv run mcp install --config $configFile
Write-Host "`nInstallation complete!"
Write-Host "To verify the installation, open Claude Desktop and check the MCP Servers section in Settings."
Write-Host "You can now use Claude to interact with your Confluence instance."
```
--------------------------------------------------------------------------------
/confluence-mcp-config.json:
--------------------------------------------------------------------------------
```json
{
"name": "confluence-server",
"displayName": "Confluence Data Center",
"description": "MCP server for interacting with Confluence Data Center via REST API",
"version": "1.0.0",
"command": "uv",
"args": ["run", "--with", "mcp", "mcp", "run", "server.py"],
"env": {
"CONFLUENCE_API_BASE": "http://localhost:8090/rest/api",
"CONFLUENCE_USERNAME": "admin",
"CONFLUENCE_PASSWORD": "@Qi85yhn",
"PYTHONUNBUFFERED": "1",
"PYTHONIOENCODING": "utf-8"
},
"workingDirectory": "${configDir}",
"transportType": "stdio",
"icon": "https://wac-cdn.atlassian.com/assets/img/favicons/confluence/favicon-32x32.png",
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
}
},
"metadata": {
"author": "Your Name",
"homepage": "https://github.com/yourusername/mcp-confluence-server",
"category": "Knowledge Management",
"tags": ["confluence", "documentation", "knowledge-base"]
}
}
```
--------------------------------------------------------------------------------
/claude-integration-guide.md:
--------------------------------------------------------------------------------
```markdown
# Integrating Confluence MCP Server with Claude Desktop
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.
## Prerequisites
- Claude Desktop installed
- Python 3.9+ installed
- `uv` and `mcp` packages installed
- Confluence server accessible
## Integration Methods
There are two ways to integrate your MCP server with Claude Desktop:
1. **Simple Installation** - Quick but with limited configuration
2. **JSON Configuration Installation** - More control and customization
## Method 1: Simple Installation
For a quick installation with default settings:
```bash
# Navigate to your project directory
cd path/to/mcp-confluence-server
# Install the server in Claude Desktop
mcp install server.py
```
## Method 2: JSON Configuration Installation (Recommended)
This method gives you more control over how your server is configured in Claude Desktop.
### Step 1: Edit the Configuration File
The `confluence-mcp-config.json` file contains all the settings for your MCP server. Customize it as needed:
- Update the `CONFLUENCE_API_BASE` to point to your Confluence instance
- Set the correct `CONFLUENCE_USERNAME` and `CONFLUENCE_PASSWORD`
- Modify the `metadata` section with your information
### Step 2: Install Using the Configuration File
```bash
# Navigate to your project directory
cd path/to/mcp-confluence-server
# Install with the configuration file
mcp install --config confluence-mcp-config.json
```
### Step 3: Verify Installation
1. Open Claude Desktop
2. Click on the "..." menu in the top-right corner
3. Select "Settings"
4. Go to the "MCP Servers" tab
5. Confirm that "Confluence Data Center" appears in the list of installed servers
## Using Custom Commands
If you need to customize how the server is run, you can modify the `command` and `args` fields in the configuration file:
```json
{
"command": "python",
"args": ["-m", "mcp", "run", "server.py"]
}
```
Or for using a virtual environment:
```json
{
"command": ".venv/Scripts/python",
"args": ["-m", "mcp", "run", "server.py"]
}
```
## Troubleshooting
If you encounter issues with the integration:
1. **Check Logs**: Claude Desktop logs can help identify issues
```bash
mcp logs
```
2. **Test the Server**: Make sure the server runs correctly in development mode
```bash
mcp dev server.py
```
3. **Reinstall**: If needed, uninstall and reinstall the server
```bash
mcp uninstall confluence-server
mcp install --config confluence-mcp-config.json
```
## Advanced Configuration Options
The JSON configuration supports additional options:
- `icon`: URL or path to an icon for the server in Claude Desktop
- `capabilities`: Specify which MCP capabilities your server supports
- `metadata`: Additional information about your server
- `env`: Environment variables for the server process
## Example: Complete Installation Command
Here's a complete example command that installs the server with all options:
```bash
mcp install --config confluence-mcp-config.json --name "Confluence Server" --description "Access Confluence content from Claude" --icon https://path/to/icon.png
```
However, when using a config file, the command-line options are usually not necessary as they're defined in the JSON file.
```
--------------------------------------------------------------------------------
/example_usage.py:
--------------------------------------------------------------------------------
```python
"""
Example script demonstrating how to use the Confluence MCP server.
This script uses the MCP client to connect to the server and execute operations.
"""
import asyncio
import json
from mcp import ClientSession, StdioServerParameters, types
from mcp.client.stdio import stdio_client
# Create server parameters for stdio connection
server_params = StdioServerParameters(
command="python", # Executable
args=["server.py"], # Server script
env=None, # Use default environment
)
async def print_json(data):
"""Print data as formatted JSON."""
print(json.dumps(data, indent=2))
async def run_examples():
"""Run example operations against the Confluence server."""
print("Connecting to Confluence MCP server...")
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# Initialize the connection
await session.initialize()
# List available tools
print("\n=== Available Tools ===")
tools = await session.list_tools()
for tool in tools:
print(f"- {tool.name}: {tool.description}")
# Example 1: Find pages by space
print("\n=== Finding pages in space 'COD' ===")
try:
result = await session.call_tool(
"find_pages_by_space",
arguments={"spaceKey": "COD", "limit": 5}
)
await print_json(result)
except Exception as e:
print(f"Error finding pages: {str(e)}")
# Example 2: Execute CQL search
print("\n=== Executing CQL search ===")
try:
result = await session.call_tool(
"execute_cql_search",
arguments={"cql": "space = COD", "limit": 3}
)
await print_json(result)
except Exception as e:
print(f"Error executing search: {str(e)}")
# Example 3: Find page by title
print("\n=== Finding page by title ===")
try:
result = await session.call_tool(
"find_page_by_title",
arguments={"title": "demo page", "spaceKey": "COD"}
)
await print_json(result)
# If we found a page, get its content
if result.get("results") and len(result["results"]) > 0:
page_id = result["results"][0]["id"]
print(f"\n=== Getting content for page {page_id} ===")
content_result = await session.call_tool(
"get_page_with_body",
arguments={"pageId": page_id}
)
# Just print the title and a snippet of the body to avoid too much output
print(f"Title: {content_result.get('title', 'Unknown')}")
body = content_result.get("body", {}).get("storage", {}).get("value", "")
print(f"Body snippet: {body[:100]}...")
except Exception as e:
print(f"Error finding page: {str(e)}")
# Example 4: Create a new page (commented out to avoid creating pages accidentally)
"""
print("\n=== Creating a new page ===")
try:
result = await session.call_tool(
"create_page",
arguments={
"title": "Test Page from MCP Client",
"spaceKey": "COD",
"content": "<p>This is a test page created by the MCP client.</p>"
}
)
await print_json(result)
# If we created a page, delete it to clean up
if "id" in result:
page_id = result["id"]
print(f"\n=== Deleting page {page_id} ===")
delete_result = await session.call_tool(
"delete_page",
arguments={"pageId": page_id}
)
await print_json(delete_result)
except Exception as e:
print(f"Error creating/deleting page: {str(e)}")
"""
if __name__ == "__main__":
asyncio.run(run_examples())
```
--------------------------------------------------------------------------------
/confluence.py:
--------------------------------------------------------------------------------
```python
from typing import Any, Dict, List, Optional, Union
import os
import base64
import json
import logging
from contextlib import asynccontextmanager
from dataclasses import dataclass
from collections.abc import AsyncIterator
import httpx
from dotenv import load_dotenv
from mcp.server.fastmcp import Context, FastMCP
# Load environment variables from .env file
load_dotenv()
# Configure logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Constants
CONFLUENCE_API_BASE = os.environ.get("CONFLUENCE_API_BASE", "http://localhost:8090/rest/api")
CONFLUENCE_USERNAME = os.environ.get("CONFLUENCE_USERNAME", "admin")
CONFLUENCE_PASSWORD = os.environ.get("CONFLUENCE_PASSWORD", "admin")
# Initialize FastMCP server
mcp = FastMCP("confluence")
@dataclass
class ConfluenceContext:
"""Context for Confluence API client."""
client: httpx.AsyncClient
@asynccontextmanager
async def confluence_lifespan(server: FastMCP) -> AsyncIterator[ConfluenceContext]:
"""Manage Confluence API client lifecycle."""
# Create auth header
auth_str = f"{CONFLUENCE_USERNAME}:{CONFLUENCE_PASSWORD}"
auth_bytes = auth_str.encode('ascii')
auth_b64 = base64.b64encode(auth_bytes).decode('ascii')
# Initialize HTTP client with authentication
async with httpx.AsyncClient(
base_url=CONFLUENCE_API_BASE,
headers={
"Authorization": f"Basic {auth_b64}",
"Content-Type": "application/json",
"Accept": "application/json"
},
timeout=30.0
) as client:
logger.info(f"Initialized Confluence API client for {CONFLUENCE_API_BASE}")
yield ConfluenceContext(client=client)
# Set up lifespan context
mcp = FastMCP("confluence", lifespan=confluence_lifespan)
# Helper functions
async def handle_response(response: httpx.Response) -> Dict[str, Any]:
"""Handle API response, raising appropriate exceptions for errors."""
try:
response.raise_for_status()
return response.json()
except httpx.HTTPStatusError as e:
error_msg = f"HTTP error: {e.response.status_code}"
try:
error_data = e.response.json()
error_msg = f"{error_msg} - {error_data.get('message', 'Unknown error')}"
except Exception:
error_msg = f"{error_msg} - {e.response.text or 'No error details'}"
logger.error(error_msg)
raise ValueError(error_msg)
except Exception as e:
logger.error(f"Error processing response: {str(e)}")
raise ValueError(f"Failed to process response: {str(e)}")
# MCP Tools for Confluence API
@mcp.tool()
async def update_page(ctx: Context, pageId: str, content: str, title: str = None, spaceKey: str = None) -> Dict[str, Any]:
"""
Update an existing page in Confluence.
Args:
pageId: Confluence Page ID
content: New page content in storage format (HTML)
title: Optional new title for the page
spaceKey: Optional space key (only needed if changing space)
Returns:
Updated page details
"""
client = ctx.request_context.lifespan_context.client
# First, get the current page to obtain version number and other details
logger.info(f"Getting current page details for page ID: {pageId}")
response = await client.get(f"/content/{pageId}")
current_page = await handle_response(response)
# Prepare update data
update_data = {
"id": pageId,
"type": "page",
"title": title or current_page.get("title", ""),
"body": {
"storage": {
"value": content,
"representation": "storage"
}
},
"version": {
"number": current_page.get("version", {}).get("number", 0) + 1
}
}
# Add space if specified
if spaceKey:
update_data["space"] = {"key": spaceKey}
elif "space" in current_page and "key" in current_page["space"]:
update_data["space"] = {"key": current_page["space"]["key"]}
logger.info(f"Updating page with ID: {pageId}")
response = await client.put(f"/content/{pageId}", json=update_data)
return await handle_response(response)
@mcp.tool()
async def execute_cql_search(ctx: Context, cql: str, limit: int = 10) -> Dict[str, Any]:
"""
Execute a CQL query on Confluence to search pages.
Args:
cql: CQL query string
limit: Number of results to return
Returns:
Search results
"""
client = ctx.request_context.lifespan_context.client
params = {"cql": cql, "limit": limit}
logger.info(f"Executing CQL search: {cql} with limit {limit}")
response = await client.get("/content/search", params=params)
return await handle_response(response)
@mcp.tool()
async def get_page_content(ctx: Context, pageId: str) -> Dict[str, Any]:
"""
Get the content of a Confluence page.
Args:
pageId: Confluence Page ID
Returns:
Page content
"""
client = ctx.request_context.lifespan_context.client
logger.info(f"Getting page content for page ID: {pageId}")
response = await client.get(f"/content/{pageId}")
return await handle_response(response)
@mcp.tool()
async def get_page_with_body(ctx: Context, pageId: str) -> Dict[str, Any]:
"""
Get a page with its body content.
Args:
pageId: Confluence Page ID
Returns:
Page with body content
"""
client = ctx.request_context.lifespan_context.client
logger.info(f"Getting page with body for page ID: {pageId}")
response = await client.get(f"/content/{pageId}?expand=body.storage")
return await handle_response(response)
@mcp.tool()
async def find_pages_by_space(ctx: Context, spaceKey: str, limit: int = 10, expand: Optional[str] = None) -> Dict[str, Any]:
"""
Find pages by space key.
Args:
spaceKey: Confluence Space Key
limit: Maximum number of results to return
expand: Optional comma-separated list of properties to expand
Returns:
List of pages in the space
"""
client = ctx.request_context.lifespan_context.client
params = {"spaceKey": spaceKey, "limit": limit}
if expand:
params["expand"] = expand
logger.info(f"Finding pages in space: {spaceKey}")
# Use scan endpoint for better performance in Confluence 7.18+
try:
response = await client.get("/content/scan", params=params)
return await handle_response(response)
except Exception as e:
logger.warning(f"Scan endpoint failed, falling back to standard endpoint: {str(e)}")
response = await client.get("/content", params=params)
return await handle_response(response)
@mcp.tool()
async def find_page_by_title(ctx: Context, title: str, spaceKey: str) -> Dict[str, Any]:
"""
Find a page by title and space key.
Args:
title: Page title
spaceKey: Confluence Space Key
Returns:
Page details if found
"""
client = ctx.request_context.lifespan_context.client
params = {"title": title, "spaceKey": spaceKey}
logger.info(f"Finding page by title: {title} in space: {spaceKey}")
response = await client.get("/content", params=params)
return await handle_response(response)
@mcp.tool()
async def create_page(
ctx: Context,
title: str,
spaceKey: str,
content: str,
parentId: Optional[str] = None
) -> Dict[str, Any]:
"""
Create a new page in Confluence.
Args:
title: Page title
spaceKey: Confluence Space Key
content: Page content in storage format (HTML)
parentId: Optional parent page ID
Returns:
Created page details
"""
client = ctx.request_context.lifespan_context.client
# Prepare page data
page_data = {
"type": "page",
"title": title,
"space": {"key": spaceKey},
"body": {
"storage": {
"value": content,
"representation": "storage"
}
}
}
# Add parent if specified
if parentId:
page_data["ancestors"] = [{"id": parentId}]
logger.info(f"Creating page: {title} in space: {spaceKey}")
response = await client.post("/content", json=page_data)
return await handle_response(response)
@mcp.tool()
async def delete_page(ctx: Context, pageId: str) -> Dict[str, Any]:
"""
Delete a page by ID.
Args:
pageId: Confluence Page ID
Returns:
Deletion status
"""
client = ctx.request_context.lifespan_context.client
logger.info(f"Deleting page with ID: {pageId}")
response = await client.delete(f"/content/{pageId}")
if response.status_code == 204:
return {"status": "success", "message": f"Page {pageId} deleted successfully"}
return await handle_response(response)
# This server is designed to be run with the MCP CLI tool
# To run it, use: mcp dev server.py
# If you want to run it directly (not recommended), you can use this code
if __name__ == "__main__":
print("=== Confluence MCP Server ===")
print("This server is designed to be run with the MCP CLI tool.")
print("To run it properly, use: mcp dev server.py")
print("\nAttempting to start in direct mode (for testing only)...")
try:
import asyncio
from mcp.server.stdio import stdio_server
async def run():
# This is a simplified version that works for direct execution
async with stdio_server() as (read, write):
await mcp.start(read, write)
# Run the server
asyncio.run(run())
except Exception as e:
print(f"\nError starting server: {e}")
print("\nPlease use 'mcp dev server.py' instead.")
```