MCP协议权威指南:自建Server与Agent集成实战
摘要
MCP协议是AI工具标准化通信标准,采用Host-Client-Server三层架构,支持Tools、Resources、Prompts三
MCP 完整指南:从协议理解到自建 Server 再到 Agent 集成
用三天时间,从零开始理解了 MCP 协议,动手构建了一个文件系统 MCP Server,最终将其集成到 AI Agent 中,让 LLM 通过标准化协议操作文件系统。这篇笔记,就是这次完整探索的总结。

一、MCP 是什么?为什么需要它?
1.1 一个真实的痛点
回想一下,我们之前学习 Tool Calling 时,工具定义直接写死在应用代码里。天气查询工具写在 Agent A 中,Agent B 也想用?对不起,复制粘贴一份吧。CatDesk 用了 10 个工具,Cursor 也想用?再实现一遍。
这就是 MCP 出现之前的现状——每个 AI 应用各自为政地实现工具调用,同样的能力被重复造轮子。
1.2 MCP 的类比
MCP(Model Context Protocol)就是 AI 工具的 USB 标准。
想想看 USB 出现之前:打印机用并口、鼠标用 PS/2、扫描仪用 SCSI,每种设备需要专门的接口。USB 出现后,一个接口标准统一了所有外设。
MCP 做的是同样的事:
之前:
┌────────┐ ┌──────┐
│ App A │───→│工具 1│ (私有接口)
└────────┘ └──────┘
┌────────┐ ┌──────┐
│ App B │───→│工具 1│ (又实现一遍)
└────────┘ └──────┘
现在:
┌────────┐ ┌────────────┐
│CatDesk │──MCP──→│MCP Server A│ 任何 Host 都能用任何 Server
│(Host) │──MCP──→│MCP Server B│
└────────┘ └────────────┘
┌────────┐ ┌────────────┐
│ Cursor │──MCP──→│MCP Server A│ 同一个 Server,多个 Host 复用
└────────┘ └────────────┘
1.3 协议核心定位
MCP 是 Anthropic 于 2024 年底发布的开放协议。它不是另一个 AI 框架,而是一个通信标准——定义了 AI 应用如何发现、连接、调用外部工具和数据源。
关键词:标准化、可发现、可复用、跨平台。
二、三层架构:Host → Client → Server
MCP 的架构像一个三明治,每层各司其职:
┌─────────────────────────────────────────────────────────┐
│ Host(宿主应用) │
│ ├── 用户面对的 AI 应用(CatDesk、Claude Desktop、Cursor)│
│ ├── 负责 UI、对话管理、LLM 调用 │
│ └── 内部包含一个或多个 Client │
│ │
│ Client(客户端) │
│ ├── 协议适配层,负责和 Server 通信 │
│ ├── 每个 Client 对应一个 Server 连接 │
│ └── 管理会话生命周期(connect / close) │
│ │
│ Server(服务端) │
│ ├── 能力提供者,暴露 Tools / Resources / Prompts │
│ ├── 独立进程运行,通过 Transport 通信 │
│ └── 可以是本地进程(stdio)或远端服务(HTTP) │
└─────────────────────────────────────────────────────────┘
类比理解:
- Host = 你的电脑操作系统
- Client = USB 驱动程序
- Server = USB 设备(键盘、鼠标、U 盘)
Host 不需要知道 Server 的内部实现细节,它只要通过 Client 说"你有什么能力?"、"帮我执行这个操作"——就像你不需要知道键盘的电路设计,只要插上 USB 就能用。
三、三种能力:Tools、Resources、Prompts
MCP Server 可以暴露三种不同类型的能力,它们的定位和使用方式各不相同。
3.1 Tools(工具)——"做什么"
Tools 是 LLM 主动调用的操作,类似 Function Calling 中的 function。
server.registerTool(
"calculate",
{
description: "执行数学计算",
inputSchema: {
expression: z.string().describe("数学表达式,如 '2 + 3 * 4'"),
},
},
async ({ expression }) => {
const result = Function(`"use strict"; return (${expression})`)();
return {
content: [{ type: "text", text: `${expression} = ${result}` }],
};
}
);
关键特征:有副作用(可能修改状态)、由 LLM 决定何时调用、参数用 Zod Schema 定义(自动生成 JSON Schema 给 LLM 看)。
3.2 Resources(资源)——"读什么"
Resources 是 LLM 被动读取的数据源,用来提供上下文。
server.registerResource(
"workspace-info",
"file://workspace/info",
{ description: "项目概要信息", mimeType: "application/json" },
async () => {
return {
contents: [{ uri: "file://workspace/info", text: JSON.stringify(info) }],
};
}
);
关键特征:无副作用(只读)、Host 可以在对话开始时自动注入、也支持动态 URI 模板(按路径读取不同文件)。
Tools vs Resources 的核心区别:
| 维度 | Tools | Resources |
|---|---|---|
| 触发方式 | LLM 主动调用 | Host 主动注入或 LLM 读取 |
| 副作用 | 有(写文件、发请求) | 无(只读) |
| 类比 | REST API 端点 | 文件系统 |
| 使用场景 | 执行操作 | 提供上下文 |
3.3 Prompts(提示模板)——"怎么问"
Prompts 是 Server 预定义的提示模板,Host 获取后可直接发送给 LLM。
server.registerPrompt(
"code-review",
{
description: "代码审查提示",
argsSchema: {
filepath: z.string().describe("文件路径"),
focus: z.string().optional().describe("审查重点"),
},
},
async ({ filepath, focus }) => {
return {
messages: [{
role: "user",
content: { type: "text", text: `请审查 ${filepath}...` },
}],
};
}
);
用户在 CatDesk 中输入 /code-review 就能触发预制的高质量 Prompt,比让 LLM 自己临时组织更可控、更一致。
四、传输层:进程间如何通信
MCP 的传输层目前支持两种方式:
stdio(标准输入/输出): Server 作为 Host 的子进程启动,通过 stdin/stdout 交换 JSON-RPC 消息。最简单,零网络配置,适合本地开发和桌面应用。
Streamable HTTP: Server 是一个独立的 HTTP 服务。适合远程部署、多用户共享、生产环境。
选择依据很简单:本地用 stdio,远程用 HTTP。就像选择 USB 线还是 Wi-Fi 连接外设。
五、动手构建:文件系统 MCP Server
理论讲完,来看实战。这个实现包含一个完整的文件系统 MCP Server,它能让 LLM 在指定目录下安全地操作文件。
5.1 整体架构
file-server/
├── index.ts ← 入口:创建 Server、注册能力、启动传输
├── tools.ts ← 5 个文件操作工具
└── resources.ts ← 3 个数据资源
5.2 安全第一:路径穿越防护
文件系统操作最怕的就是路径穿越攻击。如果用户让 LLM 读取 ../../../etc/passwd,Server 不做防护就会泄露系统文件。
解决方案是一个 resolveSafePath 函数:
function resolveSafePath(userPath: string, allowedRoot: string): string | null {
const resolved = path.resolve(allowedRoot, userPath);
// 解析后的路径必须在允许的根目录内
if (!resolved.startsWith(allowedRoot + path.sep) && resolved !== allowedRoot) {
return null; // 拒绝!
}
return resolved;
}
原理:path.resolve 会把 ../../../etc/passwd 解析为绝对路径,然后检查这个绝对路径是否以 allowedRoot 开头。如果不是,说明路径"越狱"了,直接拒绝。
每个 Tool 在执行前都先调用这个函数,确保操作不会逃出沙箱。
5.3 五个文件操作工具
Server 暴露了 5 个 Tools:
- read_file:读取文件内容,带编码支持和大小限制(1MB)
- write_file:写入文件,自动创建中间目录
- list_directory:列出目录结构,支持递归和深度限制
- search_files:按 glob 模式搜索文件(如
*.ts) - get_file_info:获取文件元信息(大小、时间、权限)
每个工具都有完善的错误处理——不是直接抛异常,而是返回 isError: true + 友好错误信息。这让 LLM 有机会"看到"错误并自行调整策略,而不是整个流程崩溃。
5.4 动态资源模板
除了静态资源(项目信息、依赖列表),还实现了一个动态 URI 模板:
server.registerResource(
"file-content",
new ResourceTemplate("file://workspace/content/{+filepath}", { ... }),
...
);
{+filepath} 是 URI Template 语法,+ 表示可以匹配路径分隔符。这意味着 会自动提取 filepath = "src/index.ts" 并读取对应文件。
六、MCP Client 封装:桥接 MCP 与 AI SDK
Server 建好了,怎么让 Agent 用起来?关键挑战是格式转换——MCP Tools 用 JSON Schema 描述参数,AI SDK 用 Zod Schema。
6.1 McpClientManager 设计
const manager = new McpClientManager();
// 连接到 Server(自动发现能力)
await manager.connectToServer("file-server", {
command: "npx",
args: ["tsx", "file-server/index.ts", projectRoot],
});
// 一键获取 AI SDK 格式的 tools
const tools = manager.getAISDKTools();
// 直接传给 generateText
const result = await generateText({ model, tools, ... });
6.2 JSON Schema → Zod 的转换策略
完美转换 JSON Schema 到 Zod 是个大工程(类型嵌套、$ref、allOf/anyOf...)。实际中采用"松散验证 + 透传"策略:
private jsonSchemaToZod(schema: Record<string, any>): z.ZodObject<any> {
const properties = schema.properties || {};
const required = new Set(schema.required || []);
const shape: Record<string, z.ZodTypeAny> = {};
for (const [key, prop] of Object.entries(properties)) {
switch (prop.type) {
case "string": shape[key] = z.string(); break;
case "number": shape[key] = z.number(); break;
case "boolean": shape[key] = z.boolean(); break;
default: shape[key] = z.any();
}
if (prop.description) shape[key] = shape[key].describe(prop.description);
if (!required.has(key)) shape[key] = shape[key].optional();
}
return z.object(shape);
}
处理常见类型就够了。边界 case 交给 MCP Server 端做最终验证——它本来就会校验参数合法性。
6.3 多 Server 连接
McpClientManager 支持同时连接多个 Server。当有多个 Server 时,Tool 名称自动加 Server 名作前缀避免冲突:
// 连接多个 Server
await manager.connectToServer("file-server", { ... });
await manager.connectToServer("db-server", { ... });
// 获取的 tools 会是:file-server__read_file、db-server__query 等
这意味着一个 Agent 可以同时拥有文件操作、数据库查询、API 调用等多种能力——来自不同 Server,通过统一协议串联。
七、完整集成:LLM 通过 MCP 操作文件系统
最终成果——让 LLM 通过 MCP 协议自主操作文件:
async function runAgent(userQuery: string) {
const mcpManager = new McpClientManager({ verbose: true });
// 1. 连接 MCP Server
await mcpManager.connectToServer("file-server", {
command: "npx",
args: ["tsx", "file-server/index.ts", projectRoot],
});
// 2. 读取初始上下文(Resource)
const workspaceInfo = await mcpManager.readResource("file-server", "file://workspace/info");
// 3. 获取 AI SDK 格式的 Tools
const tools = mcpManager.getAISDKTools();
// 4. 执行 Agent 循环
const result = await generateText({
model: deepseek("deepseek-chat"),
system: `你是文件系统助手。工作目录信息:${workspaceInfo}`,
prompt: userQuery,
tools,
maxSteps: 10,
});
}
用户说"帮我看看项目有哪些 TypeScript 文件,然后读取 package.json",Agent 会自动:
- 调用
search_files({ pattern: "*.ts" })搜索所有 TS 文件 - 调用
read_file({ path: "package.json" })读取配置 - 综合信息给出总结
整个过程中,Agent 的工具调用通过 MCP 协议传递给独立运行的 Server 进程,Server 执行后返回结果,Agent 继续推理。工具的实现完全解耦,可以替换、升级、跨应用复用。
八、用 MCP Inspector 调试
MCP 官方提供了一个可视化调试工具——Inspector,相当于 MCP 世界的 Postman。
npx @modelcontextprotocol/inspector npx tsx file-server/index.ts .
# 浏览器打开
Inspector 能做什么:
- 查看 Server 暴露的所有 Tools、Resources、Prompts
- 手动填参数调用 Tool 查看返回
- 查看完整的 JSON-RPC 通信日志(底层消息收发)
- 验证参数校验是否生效、错误处理是否正确
开发流程建议:先用 Inspector 验证 Server 每个能力是否正常,确认后再写 Client 代码集成。
九、协议层面的理解
透过 Inspector 的 Logs 面板,可以看到 MCP 通信遵循 JSON-RPC 2.0 格式:
连接建立后的消息流:
1. Client → Server: initialize(协商能力)
2. Server → Client: initialize result(声明支持的能力)
3. Client → Server: initialized(握手完成)
4. Client → Server: tools/list(发现工具)
5. Server → Client: tools result(返回工具列表)
6. Client → Server: tools/call(调用工具)
7. Server → Client: result(返回结果)
...
N. Client → Server: close(断开连接)
对应到代码:
await client.connect(transport); // 步骤 1-3 内部完成
const tools = await client.listTools(); // 步骤 4-5
const result = await client.callTool({ // 步骤 6-7
name: "calculate",
arguments: { expression: "1+1" },
});
十、MCP vs Function Calling:本质区别
很多人第一反应是"MCP 不就是换了个形式的 Function Calling 吗?"。表面看确实类似——都是给 LLM 提供可调用的工具。但本质上它们解决的问题层次不同:
| 维度 | Function Calling | MCP |
|---|---|---|
| 定义方式 | 代码中硬编码 | Server 独立声明 |
| 发现方式 | 开发者手动配置 | Client 自动 listTools |
| 复用性 | 应用内复用 | 跨 Host 通用 |
| 运行方式 | 同进程 | 独立进程/服务 |
| 生态 | 各家私有 | 统一标准 |
| 更新升级 | 需重新部署应用 | 只更新 Server |
Function Calling 是"教一个 App 用工具"的技术;MCP 是"让所有 App 共享同一套工具"的协议标准。前者是实现细节,后者是生态基础设施。
十一、关键实践经验
11.1 Server 设计原则
安全沙箱化:Server 的操作范围必须被约束。文件 Server 限制在指定目录、数据库 Server 限制在指定 Schema、API Server 限制在指定域名。永远假设调用方(LLM)可能传入恶意参数。
错误信息给 LLM 看:Tool 的错误返回不是给开发者看的 stack trace,而是给 LLM 看的可理解文本。LLM 看到"文件不存在"可以决定换个路径;看到"ja va.io.FileNotFoundException at line 42"则无所适从。
粒度适中:一个 Tool 做一件事。不要把"读文件+解析+转换+写回"打包成一个 Tool——LLM 需要细粒度的操作来组合完成复杂任务。
11.2 性能考量
stdio 传输的性能瓶颈在于进程启动。每次连接都 spawn 一个子进程,冷启动有几百毫秒开销。解决方案:
- 复用连接(一次 connect,多次 callTool)
- 对于频繁使用的 Server,保持长连接
- 生产环境考虑 Streamable HTTP,Server 常驻运行
11.3 TypeScript 全链路类型安全
MCP SDK + Zod 的组合让整个链路都有类型保护:
// Server 端:Zod 定义参数
inputSchema: { path: z.string(), recursive: z.boolean().optional() }// 自动推导 execute 函数的参数类型
async ({ path, recursive }) => { ... }
// path: string, recursive: boolean | undefined
// Client 端:callTool 的参数也有类型提示
await client.callTool({ name: "list_directory", arguments: { path: "src" } });
十二、MCP 生态现状与未来
当前生态
- Host 支持:Claude Desktop、CatDesk、Cursor、Continue、Zed 等已原生支持 MCP
- 官方 Server:GitHub、Slack、Google Drive、PostgreSQL、Puppeteer 等
- 社区 Server:数百个社区贡献的 Server(文件系统、Docker、Kubernetes、Notion...)
- SDK:TypeScript、Python、Ja va、Go 等多语言 SDK
发展趋势
MCP 正在从"可选的高级特性"变为"Agent 生态的基础设施"。几个观察:
- 越来越多 AI IDE 把 MCP 作为扩展机制(类似 VS Code 的 Extension API)
- Server 市场/注册中心开始出现(类似 npm registry)
- 身份认证和权限控制正在标准化(OAuth 2.0 集成)
- 远程 Server 的发现和部署工具链在完善
总结
三天的 MCP 学习,最大的收获不是"又学了一个新协议",而是理解了一个思维转变:
从"给我的 Agent 写工具" → "为整个 AI 生态建设可复用的能力模块"。
当你写一个 MCP Server 时,你不只是在为当前项目服务——你在为任何支持 MCP 的 Host 提供能力。今天写的文件系统 Server,明天可以被 Claude Desktop 直接使用,后天可以被同事的 Agent 项目复用。
这就是标准化的力量。USB 改变了硬件生态,MCP 正在改变 AI 工具生态。
来源:互联网
本网站新闻资讯均来自公开渠道,力求准确但不保证绝对无误,内容观点仅代表作者本人,与本站无关。若涉及侵权,请联系我们处理。本站保留对声明的修改权,最终解释权归本站所有。