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 集成分两条路:

  1. 外部 MCP 服务器:通过 stdio/HTTP/SSE 连接第三方 MCP Server,走完整的 MCP 协议
  2. 进程内 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 Server:

let 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 = McpTransportConfig

SDK:进程内零开销

不走任何网络协议,直接在进程内把工具注册进去。后面第六部分单独讲。

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-ID header。

连接流程:从配置到工具池

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()

Logo

欢迎加入 MCP 技术社区!与志同道合者携手前行,一同解锁 MCP 技术的无限可能!

更多推荐