:MCP 集成实战——让 Agent 连接万物)
MCP 协议是什么MCP 是 Anthropic 提出的一个开放协议定义了 LLM 应用和外部工具/数据源之间的通信标准。思路是工具端MCP Server暴露一组工具每个工具有名字、描述、输入 schema调用端MCP Client通过标准协议发现工具、调用工具、拿到结果通信基于 JSON-RPC传输层可以换为什么 Agent 需要它因为不可能把所有工具都写进 SDK。有了 MCP任何人都可以写一个 MCP Server比如modelcontextprotocol/server-filesystem任何 Agent 都能对接——不需要改 SDK 代码不需要写适配器配一行就接上了。Open Agent SDK 的 MCP 集成分两条路外部 MCP 服务器通过 stdio/HTTP/SSE 连接第三方 MCP Server走完整的 MCP 协议进程内 MCP 服务器用InProcessMCPServer把 SDK 工具包装成 MCP Server零协议开销下面逐个看。五种传输配置SDK 用McpServerConfig枚举统一了所有传输方式public enum McpServerConfig: Sendable, Equatable { case stdio(McpStdioConfig) // 子进程 stdin/stdout case sse(McpTransportConfig) // Server-Sent Events case http(McpTransportConfig) // HTTP POST case sdk(McpSdkServerConfig) // 进程内零开销 case claudeAIProxy(McpClaudeAIProxyConfig) // ClaudeAI 代理 }Stdio启动子进程最常用的方式。Agent 启动一个子进程通过 stdin/stdout 交换 JSON-RPC 消息。适用于 Node.js/Python 写的 MCP Serverlet servers: [String: McpServerConfig] [ filesystem: .stdio(McpStdioConfig( command: npx, args: [-y, modelcontextprotocol/server-filesystem, /tmp] )), git: .stdio(McpStdioConfig( command: uvx, args: [mcp-server-git], env: [GIT_REPO_PATH: /my/repo] )) ]MCPStdioTransport内部用 Foundation 的Process启动子进程用FileDescriptor做底层 I/O。几个细节命令解析如果 command 不是绝对路径会先which查找。找不到就当文件路径用消息分隔每条 JSON-RPC 消息以换行符分隔支持 CRLF安全过滤CODEANY_API_KEY默认不会传给子进程除非你在env里显式指定重连MCPClient 配置了最多 2 次自动重试初始间隔 1 秒指数退避到最大 10 秒SSE 和 HTTP连接远程服务远程 MCP Server 通过 HTTP 连接区分两种模式// SSE 模式长连接服务端推送 let sseServer: [String: McpServerConfig] [ remote-tools: .sse(McpTransportConfig( url: https://mcp.example.com/sse, headers: [Authorization: Bearer token123] )) ] // HTTP 模式请求-响应 let httpServer: [String: McpServerConfig] [ api-tools: .http(McpTransportConfig( url: https://mcp.example.com/api )) ]SSE 适合需要服务端主动推送的场景HTTP 适合简单的请求-响应。两者底层都用HTTPClientTransport区别在streaming参数。McpSseConfig和McpHttpConfig实际上是McpTransportConfig的别名public typealias McpSseConfig McpTransportConfig public typealias McpHttpConfig McpTransportConfigSDK进程内零开销不走任何网络协议直接在进程内把工具注册进去。后面第六部分单独讲。ClaudeAI Proxy连接 ClaudeAI 的代理端点用 server ID 做认证let proxyServer: [String: McpServerConfig] [ claude-tools: .claudeAIProxy(McpClaudeAIProxyConfig( url: https://claudeai.example.com/proxy, id: server-abc-123 )) ]内部实现就是 HTTP 传输加了一个X-ClaudeAI-Server-IDheader。连接流程从配置到工具池Agent 怎么把 MCP 工具合并到自己的工具池里从assembleFullToolPool()追踪func assembleFullToolPool() async - ([ToolProtocol], MCPClientManager?) { let baseTools options.tools ?? [] guard let mcpServers options.mcpServers, !mcpServers.isEmpty else { return (baseTools, nil) } // 第一步分离 SDK 配置和外部配置 let (sdkTools, externalServers) await Self.processMcpConfigs(mcpServers) // 第二步连接外部 MCP 服务器 var externalTools: [ToolProtocol] [] var manager: MCPClientManager? nil if !externalServers.isEmpty { let mcpManager MCPClientManager() await mcpManager.connectAll(servers: externalServers) externalTools await mcpManager.getMCPTools() manager mcpManager } // 第三步合并所有工具 let allMCPTools sdkTools externalTools let pool assembleToolPool( baseTools: getAllBaseTools(tier: .core) getAllBaseTools(tier: .specialist), customTools: baseTools, mcpTools: allMCPTools, allowed: options.allowedTools, disallowed: options.disallowedTools ) return (pool, manager) }三步走1. 分离配置。processMcpConfigs()把.sdk配置和外部配置stdio/sse/http分开。SDK 配置直接从InProcessMCPServer提取工具用SdkToolWrapper加上命名空间前缀外部配置留给MCPClientManager处理。2. 连接外部服务器。MCPClientManager是一个 actor用withTaskGroup并发连接所有服务器。每个连接经历四步创建 Transport → 启动连接 → MCP 握手 (initialize) → listTools() 发现工具发现的工具被包装成MCPToolDefinition——一个遵循ToolProtocol的结构体。工具名按mcp__{serverName}__{toolName}格式命名避免跟内置工具冲突。比如filesystem服务器上的read_file工具最终叫mcp__filesystem__read_file。3. 组装工具池。MCP 工具和内置工具、自定义工具合并经过allowedTools/disallowedTools过滤形成最终的工具池。LLM 看到的是过滤后的完整工具列表。完整的端到端使用代码let agent createAgent(options: AgentOptions( apiKey: sk-..., model: claude-sonnet-4-6, permissionMode: .bypassPermissions, mcpServers: [ filesystem: .stdio(McpStdioConfig( command: npx, args: [-y, modelcontextprotocol/server-filesystem, /tmp] )) ] )) // Agent Loop 启动时自动连接 MCP 服务器、发现工具、合并到工具池 let result await agent.prompt(List all files in /tmp and read the first one)运行时管理MCP 服务器不是连上就完事了。运行过程中你可能需要查状态、重连、开关、甚至动态替换服务器集合。SDK 提供了四个方法。查状态mcpServerStatus()let status await agent.mcpServerStatus() for (name, info) in status { print(\(name): \(info.status.rawValue)) // connected / failed / pending / disabled / needsAuth print( tools: \(info.tools)) // [read_file, write_file, ...] if let error info.error { print( error: \(error)) } }McpServerStatus有五个状态值跟 TypeScript SDK 对齐状态含义connected已连接工具可用failed连接失败pending正在连接disabled被用户禁用needsAuth需要认证重连reconnectMcpServer()