MCP协议深度解析:让AI助手真正「操控」外部世界的架构实战
过去一年多,我们看到无数「AI助手」被包装成产品,但它们本质上还是 **文本进、文本出** 的哑终端——能聊天,但不能干活。Cursor 能读写文件,Claude Desktop 能用工具,但这背后依赖的通信协议长期以来都是各家自研、互不兼容。直到去年 Anthropic 正式推出 **MCP(Model Context Protocol)**,一个开放的标
# MCP协议深度解析:让AI助手真正「操控」外部世界的架构实战
过去一年多,我们看到无数「AI助手」被包装成产品,但它们本质上还是 文本进、文本出 的哑终端——能聊天,但不能干活。Cursor 能读写文件,Claude Desktop 能用工具,但这背后依赖的通信协议长期以来都是各家自研、互不兼容。直到去年 Anthropic 正式推出 MCP(Model Context Protocol),一个开放的标准开始改变这个局面。
本文从协议设计角度深入拆解 MCP,然后手把手实现一个能实际干活的 MCP 服务器,涵盖资源管理、工具调用、采样(Sampling)三大核心能力。
MCP 是什么?为什么需要它
MCP 的核心设计目标:让 LLM 通过统一接口访问外部世界——文件系统、数据库、API、代码仓库,所有这些原本需要人类手动操作的东西,AI 应该在指令下直接完成。
传统方案的问题:
- 每个 AI 平台自研一套 tool-calling 协议,Claude Tools、OpenAI Functions、ChatGPT Plugins 各不相通
- 开发者每次换模型都要重写 tool integration 层
- 工具注册分散,没有统一的生命周期管理
MCP 解决的是 协议标准化 + 双向通信 + 有状态会话 三个问题。它基于 JSON-RPC 2.0,采用 stdio(标准输入/输出)或 HTTP + SSE 两种传输层,其中 stdio 是本地 integration 的主流选择。
协议架构:三层抽象
MCP 协议分三层:
`
┌──────────────────────────────────────┐
│ MCP Client │ ← 运行在 AI 主机侧(Claude Desktop、Cursor 等)
│ (发送请求 / 接收响应 / 管理会话) │
└──────────────┬───────────────────────┘
│ JSON-RPC 2.0 over stdio or HTTP/SSE
┌──────────────▼───────────────────────┐
│ MCP Server │ ← 运行在你自己的基础设施上
│ (暴露 Tools / Resources / 采样逻辑) │
└──────────────┬───────────────────────┘
│
┌──────────────▼───────────────────────┐
│ Your Backend │ ← 文件系统、数据库、GitHub API...
│ (真正执行操作的地方) │
└──────────────────────────────────────┘
`
三个核心原语:
实现一个完整的 MCP Server
我们用 TypeScript + Node.js 实现一个管理 GitHub 仓库的 MCP Server,支持:
1. 列出仓库issues(Resource)
2. 创建 issue / 评论(Tool)
3. AI 请求 Server 代为生成回复草稿(Sampling)
项目结构
`
mcp-github-server/
├── package.json
├── tsconfig.json
└── src/
├── index.ts # MCP server 入口
├── server.ts # MCP 服务器逻辑
├── resources.ts # Resource 暴露
├── tools.ts # Tool 定义
└── sampling.ts # Sampling 处理
`
核心:Server 初始化
`typescript
// src/server.ts
import { Server } from '@modelcontextprotocol/sdk/server';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server-stdio';
import {
CallToolRequestSchema,
ListResourcesRequestSchema,
ListToolsRequestSchema,
ReadResourceRequestSchema,
} from '@modelcontextprotocol/sdk/types';
const server = new Server(
{
name: 'github-mcp-server',
version: '1.0.0',
},
{
capabilities: {
resources: {}, // 声明支持 resources
tools: {}, // 声明支持 tools
sampling: {}, // 声明支持 sampling
},
}
);
`
关键点:Capabilities 是在握手阶段告知客户端我们支持哪些能力,客户端据此决定暴露哪些 UI。缺少这个声明,Claude Desktop 不会显示对应的功能面板。
定义 Resources
`typescript
// src/resources.ts
import { Resource } from '@modelcontextprotocol/sdk/types';
export const githubResources: Resource[] = [
{
uri: 'github://repositories',
name: 'Repositories',
description: 'List of GitHub repositories for the authenticated user',
mimeType: 'application/json',
},
{
uri: 'github://issues/{owner}/{repo}',
name: 'Repository Issues',
description: 'Open issues for a specific repository',
mimeType: 'application/json',
},
];
// Resource handler
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const uri = request.params.uri;
if (uri === 'github://repositories') {
const repos = await listRepositories();
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(repos) }] };
}
if (uri.startsWith('github://issues/')) {
const [, , owner, repo] = uri.split('/');
const issues = await listIssues(owner, repo);
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(issues) }] };
}
throw new Error(Unknown resource URI: ${uri});
});
`
模板 URI 模式:github://issues/{owner}/{repo} 是模板,客户端需要根据用户上下文填充参数后请求。这比固定 URI 更灵活,但需要文档清晰说明参数格式。
定义 Tools
`typescript
// src/tools.ts
import { Tool } from '@modelcontextprotocol/sdk/types';
export const githubTools: Tool[] = [
{
name: 'create_issue',
description: 'Create a new issue in a GitHub repository',
inputSchema: {
type: 'object',
properties: {
owner: { type: 'string', description: 'Repository owner (user or org)' },
repo: { type: 'string', description: 'Repository name' },
title: { type: 'string', description: 'Issue title' },
body: { type: 'string', description: 'Issue body content (Markdown supported)' },
labels: { type: 'array', items: { type: 'string' }, description: 'Label names to apply' },
},
required: ['owner', 'repo', 'title'],
},
},
{
name: 'add_issue_comment',
description: 'Add a comment to an existing GitHub issue',
inputSchema: {
type: 'object',
properties: {
owner: { type: 'string' },
repo: { type: 'string' },
issue_number: { type: 'number' },
body: { type: 'string', description: 'Comment body (Markdown supported)' },
},
required: ['owner', 'repo', 'issue_number', 'body'],
},
},
];
// Tool handler
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
switch (name) {
case 'create_issue': {
const issue = await createGitHubIssue(args);
return {
content: [
{ type: 'text', text: JSON.stringify({ success: true, issue }) },
],
};
}
case 'add_issue_comment': {
const comment = await addComment(args);
return {
content: [{ type: 'text', text: JSON.stringify({ success: true, comment }) }],
};
}
default:
throw new Error(Unknown tool: ${name});
}
} catch (error) {
return {
content: [{ type: 'text', text: Error: ${error} }],
isError: true,
};
}
});
`
Sampling:让 Server 帮 AI 生成内容
这是 MCP 最有趣的能力。当 AI 发现自己在某些任务上不够擅长时(比如写英文营销文案),它可以请求 MCP Server 调用自己的 LLM 来生成:
`typescript
// src/sampling.ts
import { CreateMessageRequestSchema } from '@modelcontextprotocol/sdk/types';
server.setRequestHandler(CreateMessageRequestSchema, async (request) => {
const { prompt, systemPrompt, maxTokens, temperature } = request.params;
// Server用自己的LLM API生成内容,回传给AI主机
const response = await callMyLLM({
model: 'claude-sonnet-4-20250514',
messages: [
...(systemPrompt ? [{ role: 'system', content: systemPrompt }] : []),
{ role: 'user', content: prompt },
],
max_tokens: maxTokens ?? 1024,
temperature: temperature ?? 0.7,
});
return {
content: [{ type: 'text', text: response.content[0].text }],
model: 'claude-sonnet-4-20250514',
stopReason: 'end_turn',
};
});
`
Sampling 的实际价值场景:
- AI 收到一条中文用户反馈,需要翻译成英文后发到 GitHub Issue → 请求 Sampling 帮忙润色
- AI 需要对自己生成的技术文档做一次「资深工程师 review」→ Sampling 调用专用模型评分
- 多 Agent 协作时,主 Agent 分发任务给子 Agent,子 Agent 通过 Sampling 获取主 Agent 的补充上下文
入口文件
`typescript
// src/index.ts
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server-stdio';
import { server } from './server';
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('GitHub MCP Server running on stdio...');
}
main().catch(console.error);
`
配置文件(Claude Desktop 用)
`json
// ~/.config/claude-desktop.json 或者项目内的 .mcp.json
{
"mcpServers": {
"github": {
"command": "node",
"args": ["./dist/index.js"],
"env": {
"GITHUB_TOKEN": "ghp_xxxxxxx"
}
}
}
}
`
深度:协议生命周期与管理
MCP Session 的完整生命周期:
`
1. Transport 连接建立(stdio 或 HTTP/SSE)
2. Client 发送 initialize 请求(包含客户端名、版本、支持的 Capability)
3. Server 回复 initialize,声明自己的 Capability
4. 双方进入 ready 状态,可以互相发请求
5. 正常通信:Client 调用 tools / Server 推送 resources / 双向 sampling
6. 任意一方发送 terminate,或者 transport 断开 → Session 结束
`
Session 管理有几个关键设计:
- **幂等性**:所有请求都有 `method` + `id`,响应通过 `id` 匹配,允许重试
- **Progress Notification**:长时间操作(如克隆大型仓库)可以分片返回进度,避免超时
- **Error 格式统一**:所有错误都是 JSON-RPC Error Object,有 `code` 和 `message`,便于调试
实战:连接 Cursor 和 Claude Desktop
Cursor 配置(项目级 .cursor/mcp.json):
`json
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["tsx", "src/index.ts"],
"env": {
"GITHUB_TOKEN": "${GITHUB_TOKEN}"
}
}
}
}
`
配置完成后,在 Cursor 的 AI 聊天框里可以直接说:「帮我看看这个仓库最近有哪些未解决的 bug」,AI 会先通过 ListResources 获取 issues,再通过 ReadResource 读取详细内容,整个过程对用户透明。
调试技巧:在 Claude Desktop 里,按住 Option+Shift+双击 MCP Server 名称,可以打开内部日志面板,查看每条 JSON-RPC 消息的收发详情。这对排查 tool 调用失败特别有用。
为什么 MCP 比 OpenAI Functions 更适合复杂 Agent 场景
OpenAI Functions 是 函数调用的格式规范,解决的是「怎么描述一个函数」的问题。MCP 是 完整的 RPC 协议栈,解决的是「怎么构建一个工具生态系统」的问题。两者不在同一层次。
总结:MCP 的生态位
MCP 正在成为 AI Native Tooling 的 USB-C:以前各家设备的充电线和数据接口各不相同,现在一个标准接口搞定一切。对开发者而言,学一个协议,可以同时支持 Claude Desktop、Cursor、Zed、Warp 所有主流 AI 开发工具。
对架构师而言,MCP 的价值在于 把 AI 和真实世界解耦:AI 不需要内置所有工具的集成代码,它只需要会说话(Speak MCP),剩下的由 MCP Server 负责连接具体的业务系统。
这才是 Agent 架构该有的样子。