在 2026 年的 AI 开发生态中,MCP(Model Context Protocol,模型上下文协议)已经成为连接大语言模型与外部工具的事实标准。Anthropic 在 2024 年底开源该协议后,Claude Desktop、Cursor、Windsurf、VS Code 等主流开发工具纷纷接入,GitHub 上的 MCP Server 数量已突破 10,000 个。如果你还在为 AI 应用的工具集成方案纠结,这篇文章将从协议原理到实战代码,帮你彻底搞懂 MCP。
🔧 一、MCP 协议核心原理
1.1 为什么需要 MCP?
在 MCP 出现之前,每个 AI 应用都需要为每个外部工具编写定制化的集成代码。一个 AI 助手要连接 GitHub、Slack、数据库、文件系统,就需要维护 N × M 个集成适配器(N 个模型 × M 个工具)。MCP 的核心价值在于将这个 N × M 的问题简化为 N + M:工具提供方只需实现一个 MCP Server,模型提供方只需实现一个 MCP Client。
MCP 基于 JSON-RPC 2.0 协议,定义了三种核心能力:
- Tools(工具):模型可以调用的函数,比如查询数据库、发送邮件
- Resources(资源):模型可以读取的数据源,比如文件内容、API 响应
- Prompts(提示模板):预定义的交互模板,帮助模型更好地使用工具
💡 **提示:**MCP 不是又一个 Function Calling 方案。它是一个完整的协议层,解决了工具发现、能力协商、权限控制等 Function Calling 没有覆盖的问题。
1.2 传输层:三种通信方式
MCP 支持三种传输方式,开发者可以根据场景选择:
| 传输方式 | 协议 | 适用场景 | 延迟 | 复杂度 |
|---|---|---|---|---|
| stdio | 标准输入输出 | 本地 CLI 工具、桌面应用 | 极低 | 低 |
| HTTP + SSE | HTTP Server-Sent Events | 远程服务、Web 应用 | 低 | 中 |
| Streamable HTTP | HTTP + 双向流 | 高并发远程服务 | 低 | 高 |
stdio 是最简单的传输方式——MCP Client 启动 Server 进程,通过 stdin/stdout 交换 JSON-RPC 消息:
// Client → Server(通过 stdin)
{"jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {}}
// Server → Client(通过 stdout)
{"jsonrpc": "2.0", "id": 1, "result": {"tools": [{"name": "query_db", "description": "查询数据库"}]}}
HTTP + SSE 方式则适合远程部署的 MCP Server,Client 通过 HTTP POST 发送请求,Server 通过 SSE 推送响应。
⚠️ **警告:**stdio 传输方式下,Server 的 stdout 只能输出 JSON-RPC 消息。如果你的代码中有
console.log调试输出,会破坏协议通信。务必使用 stderr 输出日志。
1.3 生命周期:连接到断开
一次完整的 MCP 会话经历以下阶段:
Client Server
│ │
│── initialize ───────────────→│ 1. 能力协商
│←── initialize result ────────│
│── initialized ──────────────→│ 2. 确认连接
│ │
│── tools/list ───────────────→│ 3. 发现工具
│←── tools result ─────────────│
│ │
│── tools/call ───────────────→│ 4. 调用工具
│←── call result ──────────────│
│ │
│── notifications/cancelled ──→│ 5. 断开连接
initialize 阶段,双方交换支持的协议版本和能力(如是否支持 Resources、Prompts)。这个机制保证了向后兼容性——低版本 Client 可以安全连接高版本 Server。
🚀 二、实战:用 TypeScript 构建 MCP Server
2.1 项目初始化
# 初始化项目
mkdir mcp-server-demo && cd mcp-server-demo
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init
项目结构:
mcp-server-demo/
├── src/
│ └── index.ts # MCP Server 入口
├── tsconfig.json
└── package.json
📌 记住:
@modelcontextprotocol/sdk是官方 TypeScript SDK,封装了协议细节。生产环境务必使用 SDK 而非手动实现 JSON-RPC。
2.2 实现一个 JSON 格式化工具
我们来构建一个实用的 MCP Server,暴露 JSON 格式化和校验工具:
// src/index.ts — 一个提供 JSON 工具的 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: "json-tools",
version: "1.0.0",
});
// 工具1:JSON 格式化
server.tool(
"format_json",
"将 JSON 字符串格式化为可读的缩进格式",
{
input: z.string().describe("要格式化的 JSON 字符串"),
indent: z.number().default(2).describe("缩进空格数,默认2"),
},
async ({ input, indent }) => {
try {
const parsed = JSON.parse(input);
const formatted = JSON.stringify(parsed, null, indent);
return {
content: [{ type: "text", text: formatted }],
};
} catch (err) {
return {
content: [{ type: "text", text: `JSON 解析错误: ${(err as Error).message}` }],
isError: true,
};
}
}
);
// 工具2:JSON Schema 校验
server.tool(
"validate_json",
"校验 JSON 是否符合指定的 Schema",
{
data: z.string().describe("JSON 数据"),
schema: z.string().describe("JSON Schema 定义"),
},
async ({ data, schema }) => {
try {
JSON.parse(data);
JSON.parse(schema);
// 简化示例:实际项目应使用 ajv 等校验库
return {
content: [{ type: "text", text: "✅ JSON 数据格式正确" }],
};
} catch (err) {
return {
content: [{ type: "text", text: `校验失败: ${(err as Error).message}` }],
isError: true,
};
}
}
);
// 工具3:JSON 转 TypeScript 类型
server.tool(
"json_to_types",
"将 JSON 数据转换为 TypeScript 类型定义",
{
input: z.string().describe("JSON 数据"),
typeName: z.string().default("GeneratedType").describe("类型名称"),
},
async ({ input, typeName }) => {
try {
const parsed = JSON.parse(input);
const typeStr = generateType(parsed, typeName);
return {
content: [{ type: "text", text: typeStr }],
};
} catch (err) {
return {
content: [{ type: "text", text: `错误: ${(err as Error).message}` }],
isError: true,
};
}
}
);
function generateType(obj: any, name: string): string {
if (typeof obj !== "object" || obj === null) {
return `type ${name} = ${typeof obj};`;
}
const entries = Object.entries(obj)
.map(([key, value]) => {
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
return ` ${key}: ${generateInlineType(value)};`;
}
if (Array.isArray(value)) {
const elemType = value.length > 0 ? typeof value[0] : "unknown";
return ` ${key}: ${elemType}[];`;
}
return ` ${key}: ${typeof value};`;
})
.join("\n");
return `interface ${name} {\n${entries}\n}`;
}
function generateInlineType(obj: any): string {
const entries = Object.entries(obj)
.map(([key, value]) => `${key}: ${typeof value}`)
.join("; ");
return `{ ${entries} }`;
}
// 启动 Server(stdio 传输)
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("JSON Tools MCP Server 已启动");
}
main().catch(console.error);
编译并运行:
npx tsc
node dist/index.js
2.3 在 Claude Desktop 中配置
编辑 Claude Desktop 配置文件 claude_desktop_config.json:
{
"mcpServers": {
"json-tools": {
"command": "node",
"args": ["/path/to/mcp-server-demo/dist/index.js"]
}
}
}
重启 Claude Desktop 后,模型会自动发现这三个工具。当用户说「帮我格式化这段 JSON」时,Claude 会自动调用 format_json 工具。
💡 **提示:**配置文件的路径因操作系统而异。macOS 为
~/Library/Application Support/Claude/claude_desktop_config.json,Windows 为%APPDATA%\Claude\claude_desktop_config.json。
📊 三、MCP vs Function Calling:深度对比
很多开发者会问:「我已经在用 OpenAI Function Calling 了,为什么还需要 MCP?」这是一个好问题。两者确实解决类似的问题,但设计哲学和适用范围完全不同。
| 对比维度 | OpenAI Function Calling | MCP |
|---|---|---|
| 设计目标 | 模型调用单个函数 | 模型连接完整工具生态 |
| 工具发现 | 静态定义在请求中 | 动态发现(tools/list) |
| 传输方式 | HTTP(封装在 API 调用中) | stdio / HTTP+SSE / Streamable HTTP |
| 跨模型兼容 | 仅 OpenAI 兼容模型 | 模型无关的开放协议 |
| 资源读取 | 不支持 | 原生支持(Resources) |
| 提示模板 | 不支持 | 原生支持(Prompts) |
| 工具生态 | 各自为战 | 统一标准,可复用 |
| 安全控制 | 请求级别 | 协议级别(权限协商) |
| 本地工具 | 需要自建代理 | 原生支持 stdio |
什么时候用 Function Calling?
- ✅ 你的应用只对接一个模型(如 OpenAI)
- ✅ 工具数量少(< 5 个),不需要动态发现
- ✅ 追求最简集成,不想引入额外协议层
- ✅ 纯远程调用,不需要本地工具支持
什么时候用 MCP?
- ✅ 你开发的工具要被多个 AI 应用使用
- ✅ 需要支持本地 CLI 工具和文件系统访问
- ✅ 工具数量多,需要动态发现和能力协商
- ✅ 需要统一的安全控制和权限管理
⚡ **关键结论:**Function Calling 是「模型调函数」,MCP 是「模型连生态」。如果你在开发工具产品,选 MCP;如果你在开发 AI 应用,两者都可以用,但 MCP 的生态优势会越来越明显。
🛡️ 四、生产环境避坑指南
4.1 坑点一:错误处理不当
很多开发者在 MCP Server 中直接 throw Error,导致客户端收到的是协议层错误而非工具层错误。正确做法是通过 isError 字段返回业务错误:
// ❌ 错误写法 — 抛出异常,客户端收到协议错误
server.tool("bad_tool", {}, async () => {
throw new Error("数据格式错误");
});
// ✅ 正确写法 — 通过 isError 返回业务错误
server.tool("good_tool", {}, async () => {
return {
content: [{ type: "text", text: "数据格式错误:缺少必填字段 name" }],
isError: true,
};
});
4.2 坑点二:stdio 输出污染
stdio 模式下,stdout 是协议通道。任何意外输出都会导致 JSON-RPC 解析失败:
// ❌ 错误写法 — console.log 输出到 stdout,破坏协议
console.log("正在处理请求...");
// ✅ 正确写法 — 使用 stderr 输出日志
console.error("正在处理请求...");
process.stderr.write("调试信息\n");
建议在项目入口设置全局日志重定向:
// 重定向 console.log 到 stderr(仅 stdio 模式需要)
const originalLog = console.log;
console.log = (...args) => console.error("[LOG]", ...args);
4.3 坑点三:Zod Schema 定义不规范
MCP SDK 使用 Zod 进行参数校验。Schema 定义直接影响模型理解工具的方式:
// ❌ 不清晰的 Schema — 模型不知道该传什么
server.tool("query", {
q: z.string(),
}, handler);
// ✅ 清晰的 Schema — description 会传递给模型
server.tool("search_users", {
keyword: z.string().describe("搜索关键词,支持用户名或邮箱"),
limit: z.number().min(1).max(100).default(10).describe("返回结果数量"),
sort: z.enum(["name", "created_at"]).default("created_at").describe("排序字段"),
}, handler);
⚠️ 警告:
description字段不是给人看的,是给模型看的。写得越清晰,模型调用工具的准确率越高。这是 MCP 开发中最容易被忽视但影响最大的细节。
4.4 坑点四:超时和长时任务
MCP 工具调用默认有超时限制。如果工具执行时间超过 30 秒,客户端可能会断开连接。对于长时间任务(如文件处理、API 调用),应该使用进度通知:
server.tool("process_large_file", {
filePath: z.string().describe("文件路径"),
}, async ({ filePath }, extra) => {
const total = 100;
for (let i = 0; i < total; i++) {
// 发送进度通知
await extra.sendNotification({
method: "notifications/progress",
params: { progress: i + 1, total },
});
// 模拟处理
await new Promise(r => setTimeout(r, 100));
}
return {
content: [{ type: "text", text: `处理完成: ${filePath}` }],
};
});
🎯 五、高级模式:Resource 和 Prompt
5.1 Resource:暴露数据源
Resource 让 MCP Server 可以暴露可读取的数据。模型可以通过 URI 访问这些数据:
// 暴露一个配置文件作为 Resource
server.resource(
"app-config",
"config://app/settings",
async (uri) => ({
contents: [{
uri: uri.href,
text: JSON.stringify({
appName: "MyApp",
version: "2.0.0",
features: { darkMode: true, i18n: true },
}, null, 2),
}],
})
);
5.2 Prompt:预定义交互模板
Prompt 模板帮助模型更高效地使用工具:
server.prompt(
"analyze_json",
"分析 JSON 数据的结构和内容",
{ data: z.string().describe("JSON 数据") },
({ data }) => ({
messages: [{
role: "user",
content: {
type: "text",
text: `请分析以下 JSON 数据:\n1. 数据结构概览\n2. 数据类型统计\n3. 潜在问题\n\n${data}`,
},
}],
})
);
💡 **提示:**Resource 适合暴露相对静态的数据(配置、文档),Prompt 适合定义标准化的工作流程。三者配合使用才能发挥 MCP 的全部威力。
📋 总结与工具推荐
MCP 协议正在成为 AI 工具集成的通用语言。对于开发者而言,现在投入学习 MCP 正是最好的时机——协议已足够成熟,生态正在爆发增长。
核心建议:
- ✅ 如果你在开发开发者工具,优先考虑提供 MCP Server
- ✅ 使用官方 SDK(TypeScript / Python),不要手动实现协议
- ✅ 重视 Zod Schema 的 description,它直接决定模型调用质量
- ✅ 先用 stdio 传输开发和测试,生产环境再切换 HTTP
- ❌ 不要在 stdout 中输出非协议内容
- ❌ 不要忽略错误处理和超时控制
推荐工具与资源:
| 工具 / 资源 | 用途 | 链接 |
|---|---|---|
| MCP Inspector | 调试和测试 MCP Server | github.com/modelcontextprotocol/inspector |
| MCP TypeScript SDK | 官方 TypeScript 实现 | npm: @modelcontextprotocol/sdk |
| MCP Python SDK | 官方 Python 实现 | pip: mcp |
| Smithery | MCP Server 市场 | smithery.ai |
| MCP Hub | MCP Server 目录 | mcphub.io |
本文涉及的工具如 JSON 格式化、JSON 校验等,均可在 jsjson.com 在线免费使用,所有数据本地处理,不上传服务器。