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()网络抖动或服务端重启后手动重连某个服务器try await agent.reconnectMcpServer(name: filesystem)内部实现断开旧连接 → 清理状态 → 用初始配置重新走一遍连接流程。MCPClientManager在首次连接时保存了原始配置originalConfigs重连时直接用它。开关toggleMcpServer()临时禁用某个服务器断开连接但保留配置之后还能再开// 禁用 try await agent.toggleMcpServer(name: filesystem, enabled: false) // 重新启用 try await agent.toggleMcpServer(name: filesystem, enabled: true)动态替换setMcpServers()运行时替换整个 MCP 服务器集合。SDK 做了 diff新增的连接、删除的断开、配置变化的重新连接let result try await agent.setMcpServers([ filesystem: .stdio(McpStdioConfig( command: npx, args: [-y, modelcontextprotocol/server-filesystem, /data] )), database: .stdio(McpStdioConfig( command: python3, args: [-m, my_db_server] )) ]) print(Added: \(result.added)) // [database] print(Removed: \(result.removed)) // 之前有但现在没有的 print(Errors: \(result.errors)) // 连接失败的MCPClientManager.setServers()的 diff 逻辑看一下public func setServers(_ servers: [String: McpServerConfig]) async - McpServerUpdateResult { let existingNames Set(originalConfigs.keys) let newNames Set(servers.keys) let addedNames newNames.subtracting(existingNames) let removedNames existingNames.subtracting(newNames) // 配置变化的视为 remove add let changedNames newNames.intersection(existingNames).filter { name in originalConfigs[name] ! servers[name] } let effectiveAdded addedNames.union(changedNames) // ...执行连接和断开 }先删除不再需要的再连接新增和变化的。变化的服务器会被完全重建不是热更新。这对于长运行的 Agent 应用很重要——你可以在不重启 Agent 的情况下调整 MCP 配置。MCP 资源不只是工具MCP 协议除了工具Tools还有资源Resources。工具是做事情资源是读数据——比如一个数据库 MCP Server 可以暴露一个query工具同时暴露tables资源让 Agent 看有哪些表。SDK 内置了两个资源相关工具ListMcpResources和ReadMcpResource。ListMcpResources列出所有已连接 MCP 服务器的可用资源// LLM 看到的工具描述 // List available resources from connected MCP servers. // Resources can include files, databases, and other data sources. // 可选参数server — 按服务器名过滤内部实现通过MCPResourceProvider协议查询每个连接public protocol MCPResourceProvider: Sendable { func listResources() async - [MCPResourceItem]? func readResource(uri: String) async throws - MCPReadResult }资源用MCPResourceItem表示——有名字、描述、URI。ReadMcpResource读取指定 URI 的资源内容// LLM 看到的工具 // Read a specific resource from an MCP server. // 参数server服务器名、uri资源 URI两个工具都是只读的通过ToolContext.mcpConnections拿到连接信息——不用全局变量线程安全。进程内 MCPInProcessMCPServerInProcessMCPServer是 SDK 里一个独特的设计。它让你用defineTool()创建工具然后包装成一个 MCP Server——但实际上不走 MCP 协议。为什么因为有些场景你只是想把自己的工具加到 Agent 的工具池里不需要跨进程通信。直接调函数比走 JSON-RPC 序列化高效得多。基本用法// 用 defineTool 创建工具 struct WeatherInput: Codable { let city: String } let weatherTool defineTool( name: get_weather, description: Get the current weather for a given city., inputSchema: [ type: object, properties: [ city: [type: string, description: The city name] ], required: [city] ], isReadOnly: true ) { (input: WeatherInput, context: ToolContext) - String in let data: [String: String] [ Beijing: Sunny, 22C, Tokyo: Cloudy, 18C, ] return data[input.city] ?? No data for \(input.city) } // 包装为 InProcessMCPServer let server InProcessMCPServer( name: weather, // 工具名将是 mcp__weather__get_weather version: 1.0.0, tools: [weatherTool], cwd: /tmp ) // 通过 asConfig() 生成配置注入 Agent let agent createAgent(options: AgentOptions( apiKey: sk-..., model: claude-sonnet-4-6, mcpServers: [weather: await server.asConfig()] ))内部实现InProcessMCPServer是一个 actor有两种工作模式SDK 内部模式常用processMcpConfigs()检测到.sdk配置时直接调用server.getTools()拿到工具列表用SdkToolWrapper加上命名空间前缀。整个过程中工具的call()方法直接被调用没有任何序列化开销private struct SdkToolWrapper: ToolProtocol, Sendable { let serverName: String let innerTool: ToolProtocol var name: String { mcp__\(serverName)__\(innerTool.name) } func call(input: Any, context: ToolContext) async - ToolResult { return await innerTool.call(input: input, context: context) } }注意SdkToolWrapper的call()直接转发到innerTool——没有 JSON-RPC没有 Value 转换就是直接调函数。外部客户端模式如果有外部 MCP Client 想连进来createSession()创建一个InMemoryTransport对跑完整的 MCP 握手。这种场景下才有协议开销public func createSession() async throws - (Server, InMemoryTransport) { let mcpServer await getOrCreateMCPServer() let session await mcpServer.createSession() let (clientTransport, serverTransport) await InMemoryTransport.createConnectedPair() try await session.start(transport: serverTransport) return (session, clientTransport) }InProcessMCPServer内部维护了一个MCPServer实例懒加载注册工具时把每个ToolProtocol的call()包装成 MCP 的 handler closure——处理参数格式转换[String: Value]到[String: Any]、构建ToolContext、处理错误结果。注意事项命名限制server name 不能包含__双下划线因为会跟命名空间前缀mcp__{server}__{tool}冲突。构造器里有precondition检查错误处理工具返回isError: true时MCP 层面会抛出ToolExecutionError让 MCP 协议返回isError: true工具注册失败会触发assertionFailure说明是代码 bug比如重复的工具名完整示例多工具 MCP 服务器这是 AdvancedMCPExample 示例的核心部分展示了多工具注册和错误处理// 天气工具 — 返回 String let weatherTool defineTool( name: get_weather, description: Get the current weather for a given city., inputSchema: [ type: object, properties: [ city: [type: string, description: The city name] ], required: [city] ], isReadOnly: true ) { (input: WeatherInput, context: ToolContext) - String in let data: [String: String] [ Beijing: Sunny, 22C, humidity 45%, Tokyo: Cloudy, 18C, humidity 65%, ] return data[input.city] ?? No data for \(input.city) } // 邮箱验证 — 返回 ToolExecuteResult包含错误处理 let validationTool defineTool( name: validate_email, description: Validate an email address., inputSchema: [ type: object, properties: [ email: [type: string, description: The email address] ], required: [email] ], isReadOnly: true ) { (input: ValidateInput, context: ToolContext) - ToolExecuteResult in if !input.email.contains() { return ToolExecuteResult( content: Invalid email: \(input.email) missing , isError: true ) } return ToolExecuteResult(content: Email \(input.email) is valid., isError: false) } // 打包为 MCP 服务器 let utilityServer InProcessMCPServer( name: utility, version: 1.0.0, tools: [weatherTool, validationTool], cwd: /tmp ) // 创建 Agent let agent createAgent(options: AgentOptions( apiKey: apiKey, model: claude-sonnet-4-6, systemPrompt: You have weather and email validation tools., permissionMode: .bypassPermissions, mcpServers: [utility: await utilityServer.asConfig()] )) // LLM 会自动调用 mcp__utility__get_weather 或 mcp__utility__validate_email let result await agent.prompt(Check weather in Tokyo and validate testexample.com) print(result.text)工具返回错误时Agent 不会崩溃。错误信息喂回 LLMLLM 看到后会调整策略——比如告诉用户邮箱格式不对。实战建议选传输方式。进程内的工具用InProcessMCPServerSDK 模式外部工具用 stdio本地或 HTTP/SSE远程。不要用 stdio 去连远程服务也不要用 HTTP 去连本地命令行工具。命名要规范。MCP 工具名是mcp__{server}__{tool}三段式。server name 简短有意义不要用双下划线。filesystem比fs-tools-v2好因为 LLM 看到mcp__filesystem__read_file能直接猜出含义。错误要包容。MCPClientManager的连接失败不会炸掉 Agent——失败的服务器 status 标记为error贡献零工具。Agent Loop 照样跑只是少了那些工具。设计你的系统时也应该遵循这个原则外部服务不可用时降级运行不要整体崩溃。运行时管理用好。长运行的 Agent 应用应该在启动后检查mcpServerStatus()失败的用reconnectMcpServer()重试。需要动态调整时用setMcpServers()而不是重建 Agent。

相关新闻

第2章 开发环境搭建与工程初始化|从零创建第一个AI对话项目

第2章 开发环境搭建与工程初始化|从零创建第一个AI对话项目

文章目录0- 前言导读0-1 专栏导航0-2 统一技术版本(全篇统一,杜绝版本坑)一、为什么 SpringBoot4-x 必须 JDK21?(面试原理)二、两种工程创建方式(企业选型对比)方式一:Sp…

2026/7/23 18:03:26 阅读更多 →
论分布式系统的半开闭问题

论分布式系统的半开闭问题

论分布式系统的半开闭问题 半开闭状态 节点 A 向节点 B 发出一个请求,然后等待响应。在等待期间,A 不知道 B 是否收到了请求,不知道 B 是否正在处理,不知道 B 是否已经发出了响应而响应在网络中丢失。A 只知道一件事:我…

2026/7/22 22:41:30 阅读更多 →
论分布式系统

论分布式系统

论分布式系统 分布式系统的行为规律,由正反馈和负反馈的博弈决定。 这不是隐喻。分布式系统在每一个层面——从网络传输到数据复制,从拥塞控制到共识协议——都运行在反馈回路上。正反馈是效率的来源,也是失稳的根源。负反馈是生存的条件&a…

2026/7/24 4:09:43 阅读更多 →

最新新闻

PPL-Factory:任务与预算感知的大模型数据选择框架解析

PPL-Factory:任务与预算感知的大模型数据选择框架解析

在实际的大语言模型训练和微调过程中,数据选择是一个至关重要却又常常被忽视的环节。面对海量的候选数据,如何高效地挑选出对特定任务最有益的子集,同时将数据获取和处理的成本控制在预算之内,是提升模型性能与训练效率的关键。PP…

2026/7/24 9:22:05 阅读更多 →
联邦学习系统构建指南:从原理到实践

联邦学习系统构建指南:从原理到实践

1. 联邦学习系统概述 联邦学习(Federated Learning)是一种分布式机器学习方法,它允许在多个分散的数据源上训练共享模型,而无需将原始数据集中存储。这种技术特别适合处理隐私敏感数据或受监管行业的数据,如医疗、金融…

2026/7/24 9:22:05 阅读更多 →
从几公斤到数吨级:高校/科研院所微量精油定制的柔性放大技术

从几公斤到数吨级:高校/科研院所微量精油定制的柔性放大技术

在植物精油研发领域,有一类需求技术门槛极高:科研团队手里往往只有几公斤特色芳香植物原料,却要求提出科研级、组分可溯源的精油。这类"微量、高要求"的订单,是检验植物提取技术企业柔性定制能力的试金石。 1. 微量精油…

2026/7/24 9:22:05 阅读更多 →
LVDS SerDes PCB设计实战:从阻抗连续到信号完整性的高速链路构建

LVDS SerDes PCB设计实战:从阻抗连续到信号完整性的高速链路构建

1. 项目概述:为什么LVDS SerDes的PCB设计是成败关键 在高速数字系统里摸爬滚打十几年,我见过太多因为PCB和互连设计不当,导致LVDS链路性能不达标甚至彻底失败的案例。LVDS(低压差分信号)SerDes(串行器/解串…

2026/7/24 9:22:05 阅读更多 →
YOLOv12在水稻病害智能检测中的应用与优化

YOLOv12在水稻病害智能检测中的应用与优化

1. 项目概述:基于YOLOv12的水稻病害智能检测系统 水稻作为全球半数人口的主粮,其病害防治直接关系到粮食安全。传统人工田间巡查方式效率低下且依赖经验,而基于深度学习的视觉检测技术正在改变这一现状。我们开发的这套系统采用YOLOv12这一前…

2026/7/24 9:22:04 阅读更多 →
TI ADS7851评估套件实战:双通道高速ADC性能验证与设计要点

TI ADS7851评估套件实战:双通道高速ADC性能验证与设计要点

1. 项目概述与核心价值如果你正在设计一个需要同时采集两路高速、高精度模拟信号的系统,比如电机控制、多通道数据采集卡或者医疗成像设备的前端,那么模数转换器(ADC)的选型和性能验证绝对是你绕不开的核心环节。在众多ADC架构中&…

2026/7/24 9:21:04 阅读更多 →

日新闻

用Highcharts 创建可拖拽三维散点立方体3D图表

用Highcharts 创建可拖拽三维散点立方体3D图表

该案例基于Highcharts scatter3d 三维散点图实现空间立方体散点可视化,核心特色:三维 X/Y/Z 三轴空间,所有散点分布在 0~10 立方体空间内;散点使用径向渐变实现立体 3D 圆球质感;支持鼠标 / 触屏拖拽画布,…

2026/7/24 0:00:29 阅读更多 →
AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口

AppCertDlls:进程创建路径上的 DLL 入口 AppCertDlls 位于 HKLM\System\CurrentControlSet\Control\Session Manager\AppCertDlls。本文的程序功能是只读列出这个键在 64 位和 32 位注册表视图中的全部值,并显示每条值的来源、名称、类型和可安全显示的数…

2026/7/24 0:00:29 阅读更多 →
我的编程之路:第一篇博客

我的编程之路:第一篇博客

大家好,我是一名编程初学者,同时这也是我编程学习之路上的第一篇博客。在这里,我想要向大家介绍我的一些想法和规划。a.自我介绍我是一个刚刚接触编程的新手,目前在学习c语言,我对编程世界充满了强烈的好奇。当然&…

2026/7/24 0:00:29 阅读更多 →

周新闻

Go语言静态资源打包方案对比与实践指南

Go语言静态资源打包方案对比与实践指南

1. 项目背景与核心需求在Go语言开发中,我们经常需要处理静态资源文件的打包问题。无论是Web应用的模板文件、前端资源,还是配置文件、证书等,都需要随程序一起分发。传统做法是将这些文件与编译后的二进制文件放在同一目录下,但这…

2026/7/24 3:59:20 阅读更多 →
Go语言实现高性能LDAP认证服务的架构与实践

Go语言实现高性能LDAP认证服务的架构与实践

1. 项目背景与核心价值LDAP(轻量级目录访问协议)作为企业级身份认证的黄金标准,已经服务了超过80%的财富500强公司。我在金融科技领域实施统一认证体系时,发现传统Java方案存在启动慢、内存占用高等痛点。而Go语言凭借其协程并发模…

2026/7/24 1:23:39 阅读更多 →
【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

【AI面试官实战指南】:用ChatGPT模拟10类高频技术岗面试,3天提升应答精准度92%

更多请点击: https://intelliparadigm.com 第一章:AI面试官实战指南的核心价值与适用场景 AI面试官并非替代人类HR的“黑箱工具”,而是以可解释、可审计、可迭代的方式,赋能招聘全链路的关键基础设施。其核心价值在于将主观经验沉…

2026/7/23 17:49:47 阅读更多 →

月新闻