|
- from typing import Any, Dict, Protocol
-
- from mcp import ClientSession
-
- from .utils import McpOperationError, extract_real_error, serialize_for_json
-
-
- class MCPEventHandler(Protocol):
- """Interface for handling MCP events"""
-
- async def on_initialized(self, session_id: str, capabilities: Any) -> None:
- """Called when MCP session is initialized"""
- ...
-
- async def on_operation_result(self, operation: str, data: Dict[str, Any]) -> None:
- """Called when an MCP operation completes successfully"""
- ...
-
- async def on_operation_error(self, operation: str, error: str) -> None:
- """Called when an MCP operation fails"""
- ...
-
- async def on_mcp_activity(self, activity_type: str, message: str, details: Dict[str, Any]) -> None:
- """Called for MCP protocol activity"""
- ...
-
- async def on_elicitation_request(self, request_id: str, message: str, requested_schema: Any) -> None:
- """Called when MCP requests user input"""
- ...
-
-
- class MCPClient:
- """Handles MCP protocol operations independently of transport"""
-
- def __init__(self, session: ClientSession, session_id: str, event_handler: MCPEventHandler):
- self.session = session
- self.session_id = session_id
- self.event_handler = event_handler
- self._initialized = False
- self._capabilities = None
-
- async def initialize(self) -> None:
- """Initialize the MCP session"""
- try:
- initialize_result = await self.session.initialize()
-
- if initialize_result:
- self._capabilities = initialize_result.capabilities
- else:
- self._capabilities = None
-
- self._initialized = True
-
- # Notify handler
- await self.event_handler.on_initialized(
- self.session_id, serialize_for_json(self._capabilities.model_dump()) if self._capabilities else None
- )
-
- except Exception as e:
- await self.event_handler.on_operation_error("initialize", str(e))
- raise
-
- async def handle_operation(self, operation: Dict[str, Any]) -> None:
- """Handle an MCP operation - this preserves the exact behavior of handle_mcp_operation"""
- operation_type = operation.get("operation")
-
- try:
- if operation_type == "list_tools":
- result = await self.session.list_tools()
- tools_data = [serialize_for_json(tool.model_dump()) for tool in result.tools]
- await self.event_handler.on_operation_result("list_tools", {"tools": tools_data})
-
- elif operation_type == "call_tool":
- tool_name = operation.get("tool_name")
- arguments = operation.get("arguments", {})
- if not tool_name:
- raise McpOperationError("Tool name is required")
-
- result = await self.session.call_tool(tool_name, arguments)
- await self.event_handler.on_operation_result(
- "call_tool", {"tool_name": tool_name, "result": serialize_for_json(result.model_dump())}
- )
-
- elif operation_type == "list_resources":
- result = await self.session.list_resources()
- await self.event_handler.on_operation_result("list_resources", serialize_for_json(result.model_dump()))
-
- elif operation_type == "read_resource":
- uri = operation.get("uri")
- if not uri:
- raise McpOperationError("Resource URI is required")
-
- result = await self.session.read_resource(uri)
- await self.event_handler.on_operation_result("read_resource", serialize_for_json(result.model_dump()))
-
- elif operation_type == "list_prompts":
- result = await self.session.list_prompts()
- prompts_data = [serialize_for_json(prompt.model_dump()) for prompt in result.prompts]
- await self.event_handler.on_operation_result("list_prompts", {"prompts": prompts_data})
-
- elif operation_type == "get_prompt":
- name = operation.get("name")
- arguments = operation.get("arguments")
- if not name:
- raise McpOperationError("Prompt name is required")
-
- result = await self.session.get_prompt(name, arguments)
- await self.event_handler.on_operation_result("get_prompt", serialize_for_json(result.model_dump()))
-
- else:
- await self.event_handler.on_operation_error(
- operation_type or "unknown", f"Unknown operation: {operation_type}"
- )
-
- except Exception as e:
- real_error = extract_real_error(e)
- await self.event_handler.on_operation_error(operation_type or "unknown", real_error)
-
- @property
- def capabilities(self):
- return self._capabilities
|