"""MCP (Model Context Protocol) Server. Exposes this system's tools and resources to external MCP clients via stdio. """ import asyncio import json import sys from typing import Any class MCPServer: """Expose Agentic RAG tools as an MCP server. Runs as a stdio-based JSON-RPC server that external MCP clients can connect to. """ def __init__(self, tool_registry=None, name: str = "agentic_rag", version: str = "0.1.0"): self.name = name self.version = version self.tool_registry = tool_registry self._running = False async def run_stdio(self) -> None: """Run the MCP server over stdio (stdin/stdout). Reads JSON-RPC requests from stdin and writes responses to stdout. """ self._running = True reader = asyncio.StreamReader() protocol = asyncio.StreamReaderProtocol(reader) await asyncio.get_event_loop().connect_read_pipe(lambda: protocol, sys.stdin) while self._running: try: line = await asyncio.wait_for(reader.readline(), timeout=300.0) if not line: break request = json.loads(line.decode()) response = await self._handle_request(request) sys.stdout.write(json.dumps(response) + "\n") sys.stdout.flush() except asyncio.TimeoutError: break except json.JSONDecodeError: continue except Exception as e: error_response = { "jsonrpc": "2.0", "id": request.get("id") if "request" in dir() else None, "error": {"code": -32603, "message": str(e)}, } sys.stdout.write(json.dumps(error_response) + "\n") sys.stdout.flush() async def _handle_request(self, request: dict) -> dict: """Handle a single JSON-RPC request.""" method = request.get("method", "") req_id = request.get("id") handlers = { "initialize": self._handle_initialize, "tools/list": self._handle_tools_list, "tools/call": self._handle_tools_call, "resources/list": self._handle_resources_list, } handler = handlers.get(method) if handler: result = await handler(request.get("params", {})) return {"jsonrpc": "2.0", "id": req_id, "result": result} else: return { "jsonrpc": "2.0", "id": req_id, "error": {"code": -32601, "message": f"Method not found: {method}"}, } async def _handle_initialize(self, params: dict) -> dict: return { "protocolVersion": "2024-11-05", "capabilities": {"tools": {}}, "serverInfo": {"name": self.name, "version": self.version}, } async def _handle_tools_list(self, params: dict) -> dict: tools = self.tool_registry.get_all() if self.tool_registry else [] return { "tools": [ { "name": t.name, "description": t.description, "inputSchema": t.parameters_schema, } for t in tools ] } async def _handle_tools_call(self, params: dict) -> dict: tool_name = params.get("name", "") arguments = params.get("arguments", {}) tool = self.tool_registry.get(tool_name) if self.tool_registry else None if tool is None: return { "content": [{"type": "text", "text": f"Tool not found: {tool_name}"}], "isError": True, } try: result = await tool.execute(**arguments) return { "content": [{"type": "text", "text": str(result)}], } except Exception as e: return { "content": [{"type": "text", "text": f"Error: {e}"}], "isError": True, } async def _handle_resources_list(self, params: dict) -> dict: return {"resources": []} def stop(self) -> None: """Stop the MCP server.""" self._running = False