不只是"又一个协议"------MCP 可能是 AI Agent 工具系统最重要的基础设施。本文从开发者视角,拆解它的架构、通信流程和关键设计。
一、AI Agent 需要一个"USB 协议"
2024 年以来,AI Agent 的形态逐渐清晰:一个 LLM 大脑,通过调用工具与外部世界交互。
但有一个尴尬的问题:每个框架、每个平台、每个 Agent 都定义了自己的一套工具格式 。LangChain 用 DynamicStructuredTool,OpenAI 用 function,Anthropic 用 tool_use。同一个"查数据库"的能力,在不同框架里要写不同的适配代码。
MCP(Model Context Protocol)的出现就是为了解决这个问题。它定义了一套标准,让:
- 工具提供者只需写一次 MCP Server
- 任何 MCP Client(Agent)都能直接使用
- 生态可以独立于具体框架生长
类比:
- USB 让任何外设能接入任何电脑
- HTTP 让任何浏览器能访问任何服务器
- MCP 让任何工具能被任何 AI Agent 调用
二、MCP 的核心架构:Client ↔ Server
2.1 基本模型
arduino
┌──────────────────────┐ ┌──────────────────────┐
│ MCP Client │ │ MCP Server │
│ (Host/Agent) │ JSON-RPC 2.0 │ (Tool Provider) │
│ │ ◄──────────────────►│ │
│ - 发起请求 │ via stdio/HTTP │ - 响应请求 │
│ - 管理连接 │ │ - 执行业务逻辑 │
│ - 消费结果 │ │ - 暴露能力 │
└──────────────────────┘ └──────────────────────┘
MCP 基于 JSON-RPC 2.0 ------ 一种轻量的远程过程调用协议。每条消息都是一个 JSON 对象:
json
// 请求
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "search_files",
"arguments": { "pattern": "*.ts" }
}
}
// 响应
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "src/main.ts\nsrc/config.ts" }]
}
}
2.2 两种传输方式
stdio(标准输入输出)
Server 作为 Client 的子进程运行,通过 stdin/stdout 通信。
arduino
Client ──spawn──► Server 子进程
stdin ──────► Server 接收请求
stdout ◄────── Server 返回响应
json
// mcp-servers.json 配置
{
"transport": "stdio",
"command": "node",
"args": ["./node_modules/xxx/dist/index.js", "--allowed-dir", "."],
"env": { "API_KEY": "xxx" }
}
- 优点:零网络配置,进程级隔离,安全
- 适用:本地工具(文件系统、Git、数据库 CLI)
Streamable HTTP
Server 作为独立 HTTP 服务运行,通过 POST 请求通信。
arduino
Client ──HTTP POST──► http://remote-server/mcp
json
{
"transport": "streamableHttp",
"url": "http://localhost:3000/mcp"
}
- 优点:跨机器访问,集中管理,团队共享
- 适用:远程服务、企业内部 API 网关
2.3 完整的通信生命周期
css
1. 握手(initialize)
Client → Server: { method: "initialize", params: { protocolVersion, capabilities, clientInfo } }
Server → Client: { result: { protocolVersion, capabilities, serverInfo } }
2. 发现工具(tools/list)
Client → Server: { method: "tools/list" }
Server → Client: { result: { tools: [{ name, description, inputSchema }, ...] } }
3. 调用工具(tools/call)
Client → Server: { method: "tools/call", params: { name, arguments } }
Server → Client: { result: { content: [{ type: "text", text: "..." }] } }
4. (可选)读取资源(resources/read)
Client → Server: { method: "resources/read", params: { uri } }
Server → Client: { result: { contents: [{ uri, mimeType, text }] } }
5. (可选)获取 Prompt(prompts/get)
Client → Server: { method: "prompts/get", params: { name } }
Server → Client: { result: { messages: [{ role, content }] } }
三、MCP 的三种原语
MCP 不只是"工具调用协议",它定义了三种原语:
3.1 Tools(工具)--- 最常用的
工具是 AI 可以调用的函数。Server 通过 tools/list 暴露工具,Client 通过 tools/call 调用。
javascript
// Server 端定义的 Tool Schema
{
name: "search_files",
description: "按 glob 模式搜索文件,返回匹配的路径列表",
inputSchema: {
type: "object",
properties: {
pattern: { type: "string", description: "glob 模式,如 *.ts" },
path: { type: "string", description: "搜索起始目录" }
},
required: ["pattern"]
}
}
3.2 Resources(资源)--- 可读取的数据
资源是 Server 暴露的只读数据。跟工具不同,资源是数据而不是函数。
ruby
// 资源 URI 示例
file:///project/README.md → 返回 README 内容
docs://api-reference/auth → 返回 API 认证文档
db://users/schema → 返回数据库表结构
Agent 可以在决策前读取相关资源作为上下文。比如在写代码前,先读取项目的 CLAUDE.md 了解规范。
3.3 Prompts(提示模板)--- 复用指令
Server 可以提供预定义的 Prompt 模板。
javascript
// Server 端
{
name: "write-commit-message",
description: "根据 git diff 生成规范化的 commit message",
arguments: [{ name: "diff", description: "git diff 输出", required: true }]
}
Agent 可以调用这个 Prompt 获取模板化的指令,而不是每次都从零写 Prompt。
四、关键 npm 包拆解
4.1 @modelcontextprotocol/sdk
这是 MCP 协议的 JS/TS 官方实现。核心模块:
arduino
@modelcontextprotocol/sdk
├── client/
│ ├── stdio.js stdio 传输的 Client 实现
│ ├── streamableHttp.js HTTP 传输的 Client 实现
│ └── auth.js OAuth 认证(Bearer token、API key)
├── server/
│ ├── stdio.js stdio 传输的 Server 实现
│ └── streamableHttp.js HTTP 传输的 Server 实现
└── types/ TypeScript 类型定义
你一般不需要直接使用这个包------除非你要自己写 MCP Server 或做一个框架级的集成。
4.2 @langchain/mcp-adapters
这是 LangChain 官方出品的桥接层。它做两件事:
① 连接 Server,加载工具
javascript
const client = new MultiServerMCPClient({
mcpServers: { filesystem: { transport: 'stdio', command: 'node', args: [...] } }
});
await client.initializeConnections();
const tools = await client.getTools();
// tools 是 DynamicStructuredTool[] --- 跟本地工具完全一样
② Schema 转换(MCP JSON Schema → LangChain Zod Schema)
scss
MCP Server 返回 inputSchema (JSON Schema)
│
▼
dereferenceJsonSchema() → 解引用 $ref、$defs
│
▼
simplifyJsonSchemaForLLM() → 去除 LLM 不需要的字段(如 $schema、const 等)
│
▼
new DynamicStructuredTool({ schema: simplified, func: callTool }) → LangChain 工具
这个过程对用户透明,你拿到的就是可以直接 bindTools 的 LangChain 工具。
五、设计 MCP 集成时的 4 个关键决策
基于我在 min-cursor 项目中的实践,以下是集成 MCP 时必须考虑的 4 个问题:
决策 1:本地工具有 MCP 还要不要?
要。 文件读写、命令执行这种高频、高权限操作,保留本地实现更好:
- 没有 IPC 开销
- 没有序列化/反序列化损耗
- 权限控制更灵活
MCP 工具做扩展:搜索、Git、GitHub、数据库等。
决策 2:重名工具如何处理?
MCP Server 提供的工具可能和本地工具同名(我就遇到 read_file、write_file、list_directory 三个冲突)。
策略:本地优先,MCP 重名的跳过。
javascript
const localNames = new Set(localTools.map(t => t.name));
const uniqueMcpTools = mcpTools.filter(t => !localNames.has(t.name));
决策 3:MCP Server 挂了怎么办?
降级,不要阻塞。 MCP 连接失败时,Agent 仍应以本地工具启动。
javascript
try {
await client.initializeConnections();
} catch (e) {
this.connected = false; // 仅本地工具,Agent 继续跑
}
MCP 是锦上添花,不是单点故障。
决策 4:stdio 还是 HTTP?
| stdio | HTTP | |
|---|---|---|
| 延迟 | 极低(进程内通信) | 取决于网络 |
| 部署 | 零配置 | 需要启动 HTTP 服务 |
| 安全 | 进程级隔离 | 需要认证/鉴权 |
| 跨机器 | ❌ | ✅ |
| 适用 | 本地工具 | 团队共享服务 |
对于个人开发者的 Agent,stdio 是默认选择。
六、一个最小可用的 MCP 集成
如果你也想给自己的 Agent 加 MCP 支持,这是最小化的集成代码:
javascript
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
// 1. 配置 Server
const servers = {
filesystem: {
transport: 'stdio',
command: 'node',
args: ['./node_modules/@modelcontextprotocol/server-filesystem/dist/index.js', '.'],
}
};
// 2. 初始化
const mcpClient = new MultiServerMCPClient({ mcpServers: servers });
try {
await mcpClient.initializeConnections();
} catch (e) {
console.warn('MCP 连接失败,仅用本地工具');
}
// 3. 获取工具
const localTools = [readFile, writeFile, listDir, execCommand];
const mcpTools = await mcpClient.getTools();
// 4. 去重 + 合并
const localNames = new Set(localTools.map(t => t.name));
const uniqueMcpTools = mcpTools.filter(t => !localNames.has(t.name));
const allTools = [...localTools, ...uniqueMcpTools];
// 5. 绑定到模型
const modelWithTools = model.bindTools(allTools);
// 6. 运行 Agent
const agent = new ReactAgent({ model: modelWithTools, tools: allTools, ... });
await agent.run(task);
// 7. 清理
await mcpClient.close();
核心就 7 步,不到 30 行代码。
七、MCP 的局限性(诚实地说)
-
生态还在早期。官方 Server 不到 20 个,质量参差不齐。但社区增长很快。
-
stdio 有平台兼容问题 。部分 Server 依赖
bun或 Unix 特性,Windows 上跑不了。选 Server 时注意检查。 -
没有版本协商。如果 Server 升级了 Tool Schema,Client 侧不会自动感知------需要重启。
-
调试困难。stdio 通信是进程内的,不像 HTTP 可以用浏览器抓包。debug 主要靠日志。
-
工具发现是静态的 。
tools/list只在初始化时调一次。如果 Server 动态增删工具,Client 感知不到(这是协议层面的设计选择------简单优于灵活)。
八、下一步:MCP 的想象空间
MCP 最大的价值不在于它现在能做什么,而在于它定义了一个标准接口。当足够多的工具都以 MCP Server 形式发布时:
- 个人开发者不需要为每个新工具写适配代码
- 工具作者只需写一次,就能被所有 MCP Client 使用
- Agent 框架不需要维护自己的工具生态
这有点像 npm 对 JavaScript 的意义------一个标准化的包管理协议,催生了一个生态系统。
目前,MCP 是 Agent 工具系统最接近"标准化"的协议。值得每一个做 AI Agent 的开发者关注。