Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
263 lines
8.0 KiB
Python
263 lines
8.0 KiB
Python
"""
|
|
Confluence Integration for MCP
|
|
|
|
Provides Confluence API tools for documentation and knowledge base.
|
|
"""
|
|
|
|
from typing import Dict, Any, List, Optional
|
|
from atlassian import Confluence
|
|
from mcp import types
|
|
|
|
from .base import BaseIntegration
|
|
|
|
|
|
# Confluence MCP Tool Definitions
|
|
CONFLUENCE_TOOLS = [
|
|
types.Tool(
|
|
name="confluence_create_page",
|
|
description="Create a new Confluence page",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"space_key": {
|
|
"type": "string",
|
|
"description": "Confluence space key"
|
|
},
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Page title"
|
|
},
|
|
"body": {
|
|
"type": "string",
|
|
"description": "Page content (HTML or wiki markup)"
|
|
},
|
|
"parent_id": {
|
|
"type": "string",
|
|
"description": "Optional parent page ID"
|
|
}
|
|
},
|
|
"required": ["space_key", "title", "body"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="confluence_get_page",
|
|
description="Get Confluence page by ID or title",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"page_id": {
|
|
"type": "string",
|
|
"description": "Page ID (use this OR title)"
|
|
},
|
|
"space_key": {
|
|
"type": "string",
|
|
"description": "Space key (required if using title)"
|
|
},
|
|
"title": {
|
|
"type": "string",
|
|
"description": "Page title (use this OR page_id)"
|
|
},
|
|
"expand": {
|
|
"type": "string",
|
|
"description": "Comma-separated list of expansions (body.storage, version, etc.)",
|
|
"default": "body.storage,version"
|
|
}
|
|
}
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="confluence_update_page",
|
|
description="Update an existing Confluence page",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"page_id": {
|
|
"type": "string",
|
|
"description": "Page ID to update"
|
|
},
|
|
"title": {
|
|
"type": "string",
|
|
"description": "New page title"
|
|
},
|
|
"body": {
|
|
"type": "string",
|
|
"description": "New page content"
|
|
}
|
|
},
|
|
"required": ["page_id", "title", "body"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="confluence_search",
|
|
description="Search Confluence pages using CQL",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"cql": {
|
|
"type": "string",
|
|
"description": "CQL query (e.g., 'space=DSS AND type=page')"
|
|
},
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Maximum number of results",
|
|
"default": 25
|
|
}
|
|
},
|
|
"required": ["cql"]
|
|
}
|
|
),
|
|
types.Tool(
|
|
name="confluence_get_space",
|
|
description="Get Confluence space details",
|
|
inputSchema={
|
|
"type": "object",
|
|
"properties": {
|
|
"space_key": {
|
|
"type": "string",
|
|
"description": "Space key"
|
|
}
|
|
},
|
|
"required": ["space_key"]
|
|
}
|
|
)
|
|
]
|
|
|
|
|
|
class ConfluenceIntegration(BaseIntegration):
|
|
"""Confluence API integration with circuit breaker"""
|
|
|
|
def __init__(self, config: Dict[str, Any]):
|
|
"""
|
|
Initialize Confluence integration.
|
|
|
|
Args:
|
|
config: Must contain 'url', 'username', 'api_token'
|
|
"""
|
|
super().__init__("confluence", config)
|
|
|
|
url = config.get("url")
|
|
username = config.get("username")
|
|
api_token = config.get("api_token")
|
|
|
|
if not all([url, username, api_token]):
|
|
raise ValueError("Confluence configuration incomplete: url, username, api_token required")
|
|
|
|
self.confluence = Confluence(
|
|
url=url,
|
|
username=username,
|
|
password=api_token,
|
|
cloud=True
|
|
)
|
|
|
|
async def create_page(
|
|
self,
|
|
space_key: str,
|
|
title: str,
|
|
body: str,
|
|
parent_id: Optional[str] = None
|
|
) -> Dict[str, Any]:
|
|
"""Create a new page"""
|
|
def _create():
|
|
return self.confluence.create_page(
|
|
space=space_key,
|
|
title=title,
|
|
body=body,
|
|
parent_id=parent_id,
|
|
representation="storage"
|
|
)
|
|
|
|
return await self.call_api(_create)
|
|
|
|
async def get_page(
|
|
self,
|
|
page_id: Optional[str] = None,
|
|
space_key: Optional[str] = None,
|
|
title: Optional[str] = None,
|
|
expand: str = "body.storage,version"
|
|
) -> Dict[str, Any]:
|
|
"""Get page by ID or title"""
|
|
def _get():
|
|
if page_id:
|
|
return self.confluence.get_page_by_id(
|
|
page_id=page_id,
|
|
expand=expand
|
|
)
|
|
elif space_key and title:
|
|
return self.confluence.get_page_by_title(
|
|
space=space_key,
|
|
title=title,
|
|
expand=expand
|
|
)
|
|
else:
|
|
raise ValueError("Must provide either page_id or (space_key + title)")
|
|
|
|
return await self.call_api(_get)
|
|
|
|
async def update_page(
|
|
self,
|
|
page_id: str,
|
|
title: str,
|
|
body: str
|
|
) -> Dict[str, Any]:
|
|
"""Update an existing page"""
|
|
def _update():
|
|
# Get current version
|
|
page = self.confluence.get_page_by_id(page_id, expand="version")
|
|
current_version = page["version"]["number"]
|
|
|
|
return self.confluence.update_page(
|
|
page_id=page_id,
|
|
title=title,
|
|
body=body,
|
|
parent_id=None,
|
|
type="page",
|
|
representation="storage",
|
|
minor_edit=False,
|
|
version_comment="Updated via DSS MCP",
|
|
version_number=current_version + 1
|
|
)
|
|
|
|
return await self.call_api(_update)
|
|
|
|
async def search(self, cql: str, limit: int = 25) -> Dict[str, Any]:
|
|
"""Search pages using CQL"""
|
|
def _search():
|
|
return self.confluence.cql(cql, limit=limit)
|
|
|
|
return await self.call_api(_search)
|
|
|
|
async def get_space(self, space_key: str) -> Dict[str, Any]:
|
|
"""Get space details"""
|
|
def _get():
|
|
return self.confluence.get_space(space_key)
|
|
|
|
return await self.call_api(_get)
|
|
|
|
|
|
class ConfluenceTools:
|
|
"""MCP tool executor for Confluence integration"""
|
|
|
|
def __init__(self, config: Dict[str, Any]):
|
|
self.confluence = ConfluenceIntegration(config)
|
|
|
|
async def execute_tool(self, tool_name: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
|
|
"""Execute Confluence tool"""
|
|
handlers = {
|
|
"confluence_create_page": self.confluence.create_page,
|
|
"confluence_get_page": self.confluence.get_page,
|
|
"confluence_update_page": self.confluence.update_page,
|
|
"confluence_search": self.confluence.search,
|
|
"confluence_get_space": self.confluence.get_space
|
|
}
|
|
|
|
handler = handlers.get(tool_name)
|
|
if not handler:
|
|
return {"error": f"Unknown Confluence tool: {tool_name}"}
|
|
|
|
try:
|
|
clean_args = {k: v for k, v in arguments.items() if not k.startswith("_")}
|
|
result = await handler(**clean_args)
|
|
return result
|
|
except Exception as e:
|
|
return {"error": str(e)}
|