"""Composite MCP server: proxies mcp-atlassian + adds section tools. Spawns mcp-atlassian as a subprocess (stdio), proxies all its tools, and registers the confluence_section_* tools from this package. Claude Code sees a single MCP server with all tools under one prefix. """ from __future__ import annotations import asyncio import json import logging import os import sys from mcp.server.fastmcp import FastMCP from mcp.client.session import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client from confluence_collab.server import ( confluence_section_list, confluence_section_get, confluence_section_update, confluence_section_append, confluence_section_delete, ) logger = logging.getLogger("confluence-collab-proxy") mcp = FastMCP("atlassian-with-sections") # Register section tools directly (they're already decorated with @mcp.tool in server.py, # but we need to re-register them on this new FastMCP instance) mcp.tool()(confluence_section_list) mcp.tool()(confluence_section_get) mcp.tool()(confluence_section_update) mcp.tool()(confluence_section_append) mcp.tool()(confluence_section_delete) async def _proxy_upstream_tools() -> None: """Connect to mcp-atlassian subprocess and proxy its tools.""" cmd = "uvx" args = ["--python", "3.13", "mcp-atlassian"] server_params = StdioServerParameters(command=cmd, args=args, env=dict(os.environ)) async with stdio_client(server_params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() # List upstream tools tools_result = await session.list_tools() logger.info("Proxying %d upstream tools from mcp-atlassian", len(tools_result.tools)) # Register each upstream tool as a proxy on our server for tool in tools_result.tools: _register_proxy_tool(tool, session) # Keep running until interrupted await asyncio.Event().wait() def _register_proxy_tool(tool, session: ClientSession) -> None: """Register a proxied tool from the upstream MCP server.""" async def proxy_handler(**kwargs): result = await session.call_tool(tool.name, kwargs) # Return concatenated text content texts = [] for content in result.content: if hasattr(content, "text"): texts.append(content.text) return "\n".join(texts) if texts else "" proxy_handler.__name__ = tool.name proxy_handler.__doc__ = tool.description or "" # Build parameter annotations from tool schema mcp.tool(name=tool.name, description=tool.description or "")(proxy_handler) def main(): """Run the composite MCP server. Note: The proxy approach requires running the upstream mcp-atlassian as a subprocess. For simpler deployment, use the standalone server.py which only provides section tools, and keep mcp-atlassian separate. """ mcp.run(transport="stdio") if __name__ == "__main__": main()