2026 年,AI Agent 已经从概念验证走向生产部署。但一个核心问题始终困扰着开发者:如何让 AI 模型高效、安全地连接到外部工具和数据源? MCP(Model Context Protocol,模型上下文协议)正是 Anthropic 在 2024 年底提出、如今已成为行业事实标准的解决方案。截至目前,MCP 已被 OpenAI、Google、Microsoft 等主流 AI 厂商采纳,GitHub 上的 MCP Server 数量超过 15,000 个。如果你正在构建 AI Agent 或者想让自己的工具被 AI 调用,理解 MCP 不是可选项,而是必修课。
🏗️ 一、MCP 架构设计与核心概念
为什么需要 MCP?
在 MCP 出现之前,每个 AI 应用都需要为每个外部工具编写定制的集成代码。假设你有一个 AI 助手需要连接 GitHub、Slack、数据库和文件系统,你就需要写 4 套完全不同的集成逻辑。更糟糕的是,换一个 AI 平台(比如从 Claude 换到 GPT),这些集成代码几乎要全部重写。
MCP 的核心思想借鉴了 LSP(Language Server Protocol)的成功经验:定义一个标准协议,让工具提供方(Server)和 AI 应用(Client)解耦。一个 MCP Server 写好之后,所有支持 MCP 的 AI 应用都能直接使用。
┌─────────────────┐ MCP 协议 ┌─────────────────┐
│ MCP Host │ ◄──────────────► │ MCP Server A │
│ (AI 应用) │ │ (GitHub 工具) │
│ │ ◄──────────────► ├─────────────────┤
│ - Claude Code │ │ MCP Server B │
│ - Cursor │ stdio / │ (数据库工具) │
│ - 自定义 Agent │ Streamable ├─────────────────┤
│ │ HTTP │ MCP Server C │
│ │ ◄──────────────► │ (文件系统) │
└─────────────────┘ └─────────────────┘
MCP 的三大原语
MCP 定义了三种核心能力原语,每种原语解决不同层面的问题:
| 原语 | 控制方 | 作用 | 类比 |
|---|---|---|---|
| Tools(工具) | 模型控制 | AI 模型决定何时调用的可执行函数 | REST API 端点 |
| Resources(资源) | 应用控制 | 应用主动读取的数据源 | GET 请求 / 文件读取 |
| Prompts(提示模板) | 用户控制 | 用户可选择的预定义交互模板 | Slash 命令 |
📌 记住: Tools 是模型主动调用的(Model-controlled),Resources 是应用代码主动获取的(Application-controlled),Prompts 是用户手动选择的(User-controlled)。三者的控制权归属完全不同,混用会导致架构混乱。
传输层:从 stdio 到 Streamable HTTP
MCP 支持两种传输方式:
stdio 传输:适用于本地工具,Host 进程通过 stdin/stdout 与 Server 子进程通信。延迟极低(< 1ms),但只能用于本地场景。
Streamable HTTP 传输(2025 年引入,替代旧的 HTTP+SSE):适用于远程部署,通过 HTTP POST 发送请求,Server 可选择返回 SSE 流或直接 JSON 响应。支持无状态和有状态两种模式。
// Streamable HTTP 传输 - 服务端实现(Node.js)
// 使用 Express 创建一个支持 MCP 的 HTTP 端点
import express from 'express';
import { randomUUID } from 'node:crypto';
const app = express();
app.use(express.json());
// 存储活跃的 SSE 连接(有状态模式)
const sessions = new Map();
app.post('/mcp', async (req, res) => {
const sessionId = req.headers['mcp-session-id'];
// 检查客户端是否请求 SSE 流式响应
const acceptHeader = req.headers['accept'] || '';
const wantsStream = acceptHeader.includes('text/event-stream');
if (wantsStream) {
// 有状态模式:建立 SSE 连接
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
});
const id = sessionId || randomUUID();
sessions.set(id, res);
res.write(`event: session\ndata: ${JSON.stringify({ sessionId: id })}\n\n`);
// 处理 JSON-RPC 请求并以 SSE 事件流返回
const result = await handleMcpRequest(req.body);
res.write(`event: message\ndata: ${JSON.stringify(result)}\n\n`);
} else {
// 无状态模式:直接返回 JSON
const result = await handleMcpRequest(req.body);
res.json(result);
}
});
app.listen(3001, () => console.log('MCP Server on :3001'));
💡 提示: Streamable HTTP 是 2025 年 3 月引入的新传输层,替代了之前的 HTTP+SSE 方式。如果你看到的教程还在用
/sse端点,大概率是旧版本。新项目请直接用 Streamable HTTP。
🔧 二、从零实现一个 MCP Server
使用官方 SDK 快速搭建
MCP 官方提供了 TypeScript 和 Python 两种 SDK。以 TypeScript 为例,一个最小可运行的 MCP Server 如下:
// mcp-server-demo/index.ts
// 一个完整的 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: 'jsjson-tools',
version: '1.0.0',
});
// ========== 注册 Tool: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 {
isError: true,
content: [{ type: 'text', text: `JSON 解析失败: ${err.message}` }],
};
}
}
);
// ========== 注册 Tool:JSON 差异对比 ==========
server.tool(
'diff_json',
'比较两个 JSON 对象的差异,返回详细的字段级变化',
{
source: z.string().describe('源 JSON'),
target: z.string().describe('目标 JSON'),
},
async ({ source, target }) => {
const src = JSON.parse(source);
const tgt = JSON.parse(target);
const diffs = findDiffs(src, tgt, '');
return {
content: [{
type: 'text',
text: diffs.length === 0
? '两个 JSON 完全相同'
: diffs.map(d => `${d.path}: ${d.type} | ${d.from} → ${d.to}`).join('\n'),
}],
};
}
);
// ========== 注册 Resource:项目配置 ==========
server.resource(
'config://project',
'config://project',
async (uri) => ({
contents: [{
uri: uri.href,
mimeType: 'application/json',
text: JSON.stringify({
name: 'jsjson.com',
tools: ['json-format', 'base64', 'md5', 'rsa', 'chinese-convert'],
version: '2.0.0',
}),
}],
})
);
// ========== 注册 Prompt:代码审查模板 ==========
server.prompt(
'review_json',
'生成 JSON 结构审查报告',
{ json: z.string().describe('待审查的 JSON 数据') },
async ({ json }) => ({
messages: [{
role: 'user',
content: {
type: 'text',
text: `请审查以下 JSON 数据的结构合理性、命名规范和潜在问题:\n\n${json}`,
},
}],
})
);
// 差异查找辅助函数
function findDiffs(src, tgt, path) {
const diffs = [];
const allKeys = new Set([...Object.keys(src), ...Object.keys(tgt)]);
for (const key of allKeys) {
const currentPath = path ? `${path}.${key}` : key;
if (!(key in src)) {
diffs.push({ path: currentPath, type: '新增', from: 'undefined', to: JSON.stringify(tgt[key]) });
} else if (!(key in tgt)) {
diffs.push({ path: currentPath, type: '删除', from: JSON.stringify(src[key]), to: 'undefined' });
} else if (typeof src[key] === 'object' && typeof tgt[key] === 'object') {
diffs.push(...findDiffs(src[key], tgt[key], currentPath));
} else if (src[key] !== tgt[key]) {
diffs.push({ path: currentPath, type: '修改', from: JSON.stringify(src[key]), to: JSON.stringify(tgt[key]) });
}
}
return diffs;
}
// 启动 Server
const transport = new StdioServerTransport();
await server.connect(transport);
Tool 定义的参数验证最佳实践
MCP SDK 使用 Zod 进行参数验证,但有几处容易踩坑的地方:
// ❌ 错误写法:直接用 z.any(),AI 模型不知道该传什么
server.tool('process_data', '处理数据', {
data: z.any(),
}, async ({ data }) => { /* ... */ });
// ✅ 正确写法:用 z.union() + describe() 明确告诉模型支持的类型
server.tool('process_data', '处理数据', {
data: z.union([z.string(), z.number(), z.boolean()])
.describe('要处理的数据,支持字符串、数字或布尔值'),
format: z.enum(['json', 'csv', 'xml'])
.default('json')
.describe('输出格式,默认 json'),
}, async ({ data, format }) => {
// 处理逻辑
return { content: [{ type: 'text', text: `Processed: ${data} as ${format}` }] };
});
⚠️ 警告:
description字段不是装饰品,它是 AI 模型判断「何时调用这个 Tool」的核心依据。一个没有 description 的 Tool,模型调用准确率会下降 40% 以上。务必用自然语言清楚描述工具的用途、适用场景和参数含义。
🚀 三、Client 集成与生产部署
如何在 AI 应用中作为 Client 连接 MCP Server
// mcp-client-demo/index.ts
// 作为 MCP Client 连接到 Server 并调用工具
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
// 创建 Client 实例
const client = new Client({
name: 'my-ai-agent',
version: '1.0.0',
});
// 通过 stdio 连接到 MCP Server(自动启动子进程)
const transport = new StdioClientTransport({
command: 'node',
args: ['./mcp-server-demo/dist/index.js'],
});
await client.connect(transport);
// 列出所有可用工具
const { tools } = await client.listTools();
console.log('可用工具:', tools.map(t => t.name));
// 输出: ['format_json', 'diff_json']
// 调用 format_json 工具
const result = await client.callTool({
name: 'format_json',
arguments: {
input: '{"name":"test","items":[1,2,3]}',
indent: 4,
},
});
console.log(result.content[0].text);
// 输出:
// {
// "name": "test",
// "items": [1, 2, 3]
// }
// 读取资源
const { contents } = await client.readResource({ uri: 'config://project' });
console.log(JSON.parse(contents[0].text));
// 使用 Prompt 模板
const prompt = await client.getPrompt({
name: 'review_json',
arguments: { json: '{"a": 1}' },
});
console.log(prompt.messages[0].content.text);
安全:OAuth 2.1 认证与权限控制
远程 MCP Server 必须实现认证。MCP 规范推荐使用 OAuth 2.1 + PKCE 流程:
// server-auth.ts - 带 OAuth 认证的 MCP Server
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
const server = new McpServer({ name: 'secure-server', version: '1.0.0' });
// 注册需要写权限的工具
server.tool(
'delete_record',
'删除数据库记录(需要 write 权限)',
{
table: z.string(),
id: z.string(),
},
async ({ table, id }, extra) => {
// 从 MCP 会话上下文中获取认证信息
const token = extra._meta?.authToken;
if (!token || !hasScope(token, 'write')) {
return {
isError: true,
content: [{ type: 'text', text: '权限不足:需要 write scope' }],
};
}
// 执行删除操作
await db.delete(table, id);
return { content: [{ type: 'text', text: `已删除 ${table}/${id}` }] };
}
);
生产部署架构对比
| 部署方式 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| stdio(本地) | IDE 插件、CLI 工具 | 零配置、延迟极低 | 仅限本地、无法远程访问 |
| Streamable HTTP(单实例) | 小团队内部工具 | 部署简单、支持远程 | 无高可用、会话丢失 |
| Streamable HTTP + Redis | 生产环境 | 永久会话、水平扩展 | 架构复杂、运维成本高 |
| Serverless(Lambda/Workers) | 低频调用工具 | 按量计费、免运维 | 冷启动延迟、无状态限制 |
⚠️ 警告: 生产环境中,永远不要在 MCP Server 里硬编码 API Key 或数据库密码。使用环境变量或密钥管理服务(如 AWS Secrets Manager、HashiCorp Vault)。MCP Server 本质上是一个可被 AI 模型任意调用的 API 网关,安全防线必须比普通 Web 服务更严格。
性能优化:有状态 vs 无状态
Streamable HTTP 传输支持两种模式,选择取决于你的场景:
// 无状态模式:每次请求独立,适合 Serverless
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined, // 不生成 session ID
});
// 有状态模式:维护会话,适合需要上下文的场景
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(), // 每个连接分配唯一 ID
enableJsonResponse: true, // 非流式场景用 JSON 响应更快
});
有状态模式的 Session 信息存储策略直接影响系统的可扩展性:
| 存储方式 | 持久性 | 扩展性 | 复杂度 |
|---|---|---|---|
| 内存 Map | ❌ 进程重启丢失 | ❌ 单实例 | ⭐ 最低 |
| Redis | ✅ 持久化 | ✅ 多实例共享 | ⭐⭐ 中等 |
| 数据库 | ✅ 持久化 | ✅ 多实例共享 | ⭐⭐⭐ 较高 |
💡 提示: 大多数场景下,MCP 工具调用是无状态的(输入→处理→输出),不需要 Session。只有 Resource 订阅(实时监听数据变化)和多轮 Prompt 交互才需要有状态模式。如果你不确定,从无状态开始。
✅ 四、实战经验与避坑指南
常见坑点总结
经过在生产环境使用 MCP 半年的经验,以下是最常见的坑:
❌ 坑 1:Tool 数量过多导致模型选择困难
当一个 MCP Server 注册超过 20 个 Tool 时,AI 模型的选择准确率会明显下降。解决方案是按功能域拆分 Server,或者使用「路由 Tool」模式——注册一个高层 Tool,由它内部调度子功能。
❌ 坑 2:忘记处理错误返回
MCP 的 callTool 返回值中有一个 isError 字段。很多开发者只检查 content,忽略了错误处理,导致 AI 模型拿到错误信息后继续「幻觉」执行。
// ❌ 错误写法:忽略 isError
const result = await client.callTool({ name: 'format_json', arguments: { input: badJson } });
console.log(result.content[0].text); // 可能是错误信息,被当成正常结果
// ✅ 正确写法:检查 isError
const result = await client.callTool({ name: 'format_json', arguments: { input: badJson } });
if (result.isError) {
console.error('工具调用失败:', result.content[0].text);
// 向 AI 模型返回明确的错误提示,而不是让模型猜测
} else {
console.log(result.content[0].text);
}
❌ 坑 3:Resource URI 设计混乱
Resource URI 应该遵循 RESTful 风格,但很多 Server 用了随意的 scheme(如 myapp://data),导致 Client 无法做通用路由。建议统一使用 protocol://domain/path 格式。
✅ 最佳实践清单:
- ✅ 每个 Tool 的 description 控制在 1-2 句话,说明「做什么」和「什么时候用」
- ✅ 使用 Zod 的
.describe()为每个参数写清楚说明 - ✅ 返回结果优先用结构化文本(Markdown),不要返回纯 JSON 给模型
- ✅ 对大结果集做分页,单次返回不超过 10KB
- ✅ 实现
listChanged能力,让 Client 感知工具变更 - ✅ 在 CI 中用
@modelcontextprotocol/inspector测试 Server
💡 五、总结与展望
MCP 的价值不在于它有多复杂,而在于它解决了 AI 工具生态的「N×M 问题」——N 个 AI 应用 × M 个工具,原本需要 N×M 套集成代码,现在只需要 N + M 个适配器。
对于开发者来说,MCP 带来的最大改变是:你的工具可以「一次实现,到处被调用」。无论用户在用 Claude Code、Cursor、Windsurf 还是自建的 AI Agent,只要你的 MCP Server 符合规范,就能无缝接入。
相关工具推荐:
- 🔧 MCP Inspector — 官方调试工具,可视化测试你的 Server
- 📦 MCP TypeScript SDK — 官方 TypeScript SDK
- 🐍 MCP Python SDK — 官方 Python SDK
- 🌐 MCP Server Registry — 官方 Server 合集
- 🛠️ jsjson.com JSON 工具 — 在线 JSON 格式化/验证/转换工具
⚡ 关键结论: MCP 不是又一个「大一统协议」的空想,它已经在 2026 年证明了自己的生命力。如果你是工具开发者,现在就该考虑为你的服务提供 MCP Server 接口;如果你是 AI 应用开发者,用 MCP Client 替代手写的工具集成代码,能让你的应用获得整个 MCP 生态的能力。