"""MCP Client - 调用外部 MCP 工具服务器""" import logging from typing import Any import httpx from agentkit.mcp.transport import HTTPTransport, Transport from agentkit.tools.base import Tool logger = logging.getLogger(__name__) class MCPClient: """MCP Client - 连接外部 MCP Server 并调用工具 支持两种模式: 1. 通过 Transport 层发送 JSON-RPC 请求(推荐) 2. 直接 HTTP 调用(向后兼容) """ def __init__( self, server_url: str, timeout: int = 30, transport: Transport | None = None, ): self._server_url = server_url.rstrip("/") self._timeout = timeout self._tools_cache: list[dict] | None = None self._transport = transport @classmethod def from_transport(cls, transport: Transport) -> "MCPClient": """从 Transport 实例创建 MCPClient""" if isinstance(transport, HTTPTransport): server_url = transport._endpoint else: server_url = "" return cls(server_url=server_url, transport=transport) async def list_tools(self) -> list[dict]: """列出远程 MCP Server 上的工具""" if self._transport is not None: if not self._transport.is_connected: await self._transport.connect() result = await self._transport.send_request("tools/list") tools = result.get("tools", []) if isinstance(result, dict) else [] self._tools_cache = tools return self._tools_cache async with httpx.AsyncClient(timeout=self._timeout) as client: response = await client.get(f"{self._server_url}/tools/list") response.raise_for_status() data = response.json() self._tools_cache = data.get("tools", []) return self._tools_cache async def call_tool(self, tool_name: str, arguments: dict) -> dict: """调用远程 MCP 工具""" if self._transport is not None: if not self._transport.is_connected: await self._transport.connect() return await self._transport.send_request( "tools/call", params={"name": tool_name, "arguments": arguments}, ) async with httpx.AsyncClient(timeout=self._timeout) as client: response = await client.post( f"{self._server_url}/tools/call", json={"name": tool_name, "arguments": arguments}, ) response.raise_for_status() return response.json() def as_tool(self, tool_name: str, description: str = "") -> "MCPTool": """将远程 MCP 工具包装为本地 Tool 对象""" return MCPTool( name=tool_name, description=description, client=self, ) class MCPTool(Tool): """MCP 工具 - 通过 MCP Client 调用远程工具""" def __init__( self, name: str, description: str, client: MCPClient, input_schema: dict[str, Any] | None = None, output_schema: dict[str, Any] | None = None, version: str = "1.0.0", tags: list[str] | None = None, ): super().__init__( name=name, description=description, input_schema=input_schema, output_schema=output_schema, version=version, tags=tags or ["mcp"], ) self._client = client async def execute(self, **kwargs) -> dict: result = await self._client.call_tool(self.name, kwargs) # 解析 MCP 响应格式 if "content" in result: for item in result["content"]: if item.get("type") == "text": import json try: return json.loads(item["text"]) except json.JSONDecodeError: return {"result": item["text"]} return result