agent之mcp
工具写一次,所有支持 MCP 的客户端都能用。** 这就像 HTTP——同一个服务器,浏览器、curl、Python 都能访问。
Leo
2026.08.25 · Updated 2026.09.13
MCP 协议:让工具写一次、处处可用
上一篇文章讲了 Agent 的工具系统。但每个应用(Cursor、Claude Code、自己写的程序)都要各自接入一遍工具,太累了。MCP 就是来解决这个问题的。
一、MCP 要解决什么问题?—— N × M 的集成地狱
假设你有 3 个工具(查用户、查地图、读文件),有 4 个客户端(Cursor、Claude Code、Claude Desktop、自己写的程序)。
没有标准协议时,每个客户端都要为每个工具写一套专属接入代码:
工具1 ──→ Cursor、Claude Code、Desktop、自研程序 (4 份代码)
工具2 ──→ Cursor、Claude Code、Desktop、自研程序 (4 份代码)
工具3 ──→ Cursor、Claude Code、Desktop、自研程序 (4 份代码)
3 × 4 = 12 份集成代码,且每对组合的 API 还不一样。工具一多就失控。
MCP(Model Context Protocol,模型上下文协议) 就是 AI 世界的"USB-C 接口"——它是 Anthropic 开源的一个开放标准,规定了一套统一的工具接入方式。有了它:
工具(MCP Server)──→ 任何 MCP Client(Cursor / Claude Code / 自研程序…)
工具写一次,所有支持 MCP 的客户端都能用。 这就像 HTTP——同一个服务器,浏览器、curl、Python 都能访问。
二、核心概念:Server / Client / Transport
MCP 架构里有三个角色:
| 角色 | 是什么 | 举例 |
|---|---|---|
| MCP Server | 提供能力的一方,暴露工具 / 资源 / 提示词 | mymcp.js、@modelcontextprotocol/server-filesystem |
| MCP Client | 消费能力的一方,连接 Server 并调用工具 | Cursor、Claude Code、自研 LangChain 程序 |
| Transport | 两者通信的通道 | stdio(本地子进程)、Streamable HTTP(远程) |
关键点:一个 Server 可以被多个 Client 连接。同一个 mymcp.js,Cursor 能用、Claude Code 能用、你写的程序也能用,Server 端一行代码都不用改。
三、stdio 传输:MCP 到底怎么通信?
最常用的传输方式是 stdio(标准输入/输出)。Client 以子进程方式启动 Server:
MCP Client(Cursor / Claude Code / 自研程序)
│ spawn 子进程: node src/mymcp.js
│ 通过 stdin 发送 JSON-RPC 请求
│ 从 stdout 读取 JSON-RPC 响应
▼
MCP Server(mymcp.js)
三个特点:
- 本地零配置:不需要开端口、不需要装服务,天然安全。
- 生命周期跟随 Client:Client 关了,Server 子进程自动退出,不留孤儿进程。
- 传输内容是 JSON-RPC 消息(请求 / 响应 / 通知)。
⚠️ 最大的坑:stdout 被协议占用。
console.log会把杂数据写进 stdout,污染协议,Client 解析会直接崩。 要打日志必须用console.error(走 stderr)或写文件。
四、第一个 MCP Server:四步骨架
以我写的 mymcp.js 为例,一个 MCP Server 只有四步:
// ① 导入
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// ② 创建实例:身份 + 能力清单
const server = new McpServer({ name: 'my-mcp-server', version: '1.0.0' });
// ③ 注册能力(工具)
server.registerTool('query_user', {
description: '查询数据库中的用户信息。输入用户 ID,返回该用户的详细信息(姓名、邮箱、角色)。',
inputSchema: {
userId: z.string().describe('用户 ID,例如: 001, 002, 003'),
},
}, async ({ userId }) => {
const user = database.users[userId];
if (!user) {
return { content: [{ type: 'text', text: `用户 ID ${userId} 不存在。可用的 ID: 001, 002, 003` }] };
}
return {
content: [{
type: 'text',
text: `用户信息:\n- ID: ${user.id}\n- 姓名: ${user.name}\n- 邮箱: ${user.email}\n- 角色: ${user.role}`,
}],
};
});
// ④ 创建传输并连接,开始等待请求
const transport = new StdioServerTransport();
await server.connect(transport);
骨架四步:导入 → 建实例 → 注册能力 → connect 监听。
工具四要素 = LangChain 四件套的翻版
对比上一篇讲的 LangChain 工具,你会发现结构惊人地一致:
| LangChain 工具 | MCP 工具 |
|---|---|
name | 工具名(registerTool 第一个参数) |
description(写给模型) | description(写给模型) |
schema(zod) | inputSchema(zod) |
func | 第三个参数(handler) |
理解了上一篇,MCP 的 Server 端你基本就会了——只是包装的框架不同。
注意:Server 不主动运行
connect 之后,Server 只是挂在 stdio 上等请求,没有界面、不打印任何东西。你直接 node src/mymcp.js 运行它,肉眼什么都看不到——因为它没收到请求,这是正常的。
五、除了工具,还有"资源"(Resource)
MCP 还能注册资源——给 Client 读取的静态数据 / 文档。可以这样理解:
- 工具是"动作":会改变世界或计算东西(查用户、读文件、执行命令)
- 资源是"数据/文档":只读,直接给 Client 看(使用指南、配置说明)
server.registerResource('使用指南', 'docs://guide', {
description: 'MCP Server 使用文档',
mimeType: 'text/plain',
}, async () => {
return {
contents: [{
uri: 'docs://guide',
mimeType: 'text/plain',
text: `MCP Server 使用指南\n\n功能:提供用户查询等工具。`,
}],
};
});
六、在 Claude Code / Cursor 里注册使用
写好的 Server 要怎么让 Claude Code 用?在项目根目录放一个 .mcp.json:
{
"mcpServers": {
"my-mcp-server": {
"type": "stdio",
"command": "node",
"args": ["src/mymcp.js"]
},
"filesystem": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "F:/agent-learn"]
}
}
}
要点:
- 每个 server = 一个名字 +
command/args启动命令。args里的相对路径相对于配置文件所在目录。 - Client 启动时读取配置,用子进程拉起 Server;注册后通常要重启会话或重新
/mcp连接,工具才会出现在工具列表里。 - 连接成功后,Claude Code 才真正"看得到"
query_user这个工具,才能在对话里问"查询用户 001 的信息"。
上面还配置了一个官方现成的
filesystemServer——一行命令启动,立刻获得读写文件的能力。这就是 MCP 生态:能力即插即用。
七、三种使用路线
同一个 MCP Server,可以走三条不同的路线:
路线一:作为 MCP Client 的配置使用(正常使用)
Client(Cursor / Claude Code)读 .mcp.json 自动拉起 Server 子进程。用户在对话里用自然语言提问,Client 自动调用 query_user 返回结果。
路线二:直接运行 + MCP Inspector 调试(开发调试)
node src/mymcp.js 能验证语法和启动,但看不到输出。要交互式调试,用官方可视化调试台 MCP Inspector:
npx @modelcontextprotocol/inspector node src/mymcp.js
启动后自动打开网页调试台,能做的事:
- 连接并列出 Server 暴露的工具 / 资源
- 手动选择工具、填参数、触发调用,直接看返回
- 查看两端往来的 JSON-RPC 原始消息(加深对协议的理解)
路线三:自己写代码当 MCP Client(MultiServerMCPClient)
用 @langchain/mcp-adapters 的 MultiServerMCPClient,在自己写的 LangChain 程序里连接 MCP Server,把它暴露的工具转成 LangChain 工具,直接并入工具数组:
import { MultiServerMCPClient } from '@langchain/mcp-adapters';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const mcpClient = new MultiServerMCPClient({
'my-mcp-server': {
transport: 'stdio',
command: 'node',
args: [path.join(__dirname, 'mymcp.js')], // 用绝对路径,不依赖 cwd
},
});
const mcpTools = await mcpClient.getTools(); // query_user 转成 LangChain 工具
const tools = [readFileTool, ..., ...mcpTools]; // 并入手写工具数组
要点:
getTools()返回标准StructuredTool,与手写工具同类,能bindTools()、invoke()。- 路径解析差异:
.mcp.json的args相对配置文件目录;MultiServerMCPClient的args相对进程 cwd,建议拼绝对路径(path.join(__dirname, ...))。 - 这样你自己的 Agent 就能像调用
read_file一样调用query_user,本地工具 + MCP 工具无缝混用。
八、远程 MCP:Streamable HTTP 传输
stdio 只适合本地。MCP 还支持 HTTP 传输,让 Server 部署在远端。比如接入高德地图的官方 MCP:
const mcpClient = new MultiServerMCPClient({
'amap-maps-streamableHTTP': {
url: 'https://mcp.amap.com/mcp?key=你的key',
},
});
一行配置,你的 Agent 立刻获得"查询周边酒店 / 位置搜索"的能力。这也是 MCP 生态的威力——别人写好的能力,接上就能用。
九、总结:MCP = AI 世界的 HTTP
- 解决 N×M 集成问题:工具写一次,所有 MCP Client 都能用。
- 三个角色:Server 提供能力、Client 消费能力、Transport 负责通信。
- stdio 本地通信:零配置、安全、生命周期跟随 Client;别用 console.log 污染 stdout。
- Server 骨架四步:导入 → 建实例 → 注册能力 → connect 监听。
- 工具是动作,资源是文档:工具四要素 = LangChain 四件套的翻版。
- 三条使用路线:Client 配置、Inspector 调试、MultiServerMCPClient 自研接入。
- 远程能力即插即用:HTTP 传输,一行配置接入高德地图等第三方 MCP。
MCP 正在成为 AI 应用的标准底座。学会它,你的工具就再也不用为每个应用重写一遍了。
Leo
BloggerIndependent developer / Blogger and the maintainer of the original blog “大道至简”. Migrating years of posts and shiyu from WordPress to Next.js.
Reader comments
COMMENTS · 0Leave a comment