
Model Context Protocol (MCP) 是 Anthropic 推出的开放协议,旨在为 LLM 提供标准化的外部工具和数据源接入方式。本文将深入解析 MCP 协议的设计理念、核心架构,并带你从零实现一个完整的 MCP Server。
MCP (Model Context Protocol) 是一个开放的、基于 JSON-RPC 2.0 的协议,它定义了 LLM 应用(客户端)与外部数据源/工具(服务器)之间的标准化通信方式。
你可以把 MCP 理解为 AI 世界的 USB-C 接口:
| 原则 | 说明 |
|---|---|
| 标准化 | 统一的协议规范,任何客户端都能连接任何服务端 |
| 安全性 | 服务端主动暴露能力,客户端需授权才能调用 |
| 可组合性 | 一个客户端可连接多个服务端,能力可叠加 |
| 语言无关 | 协议基于 JSON-RPC,任何语言都能实现 |

在 MCP 出现之前,每个 AI 应用都需要为每个外部工具编写定制的集成代码。这导致了 M×N 问题:
没有 MCP:
应用 A ──定制代码──> 工具 1
应用 A ──定制代码──> 工具 2
应用 B ──定制代码──> 工具 1
应用 B ──定制代码──> 工具 2
→ 需要 M × N 个集成
有了 MCP:
应用 A ──MCP──> MCP Server 1 (封装工具 1)
应用 B ──MCP──> MCP Server 1 (封装工具 1)
应用 A ──MCP──> MCP Server 2 (封装工具 2)
→ 只需要 M + N 个实现
MCP 采用 客户端-服务器 (Client-Server) 架构:
┌─────────────────────────────────────────────────┐
│ MCP Host │
│ ┌───────────────┐ ┌───────────────────────┐ │
│ │ MCP Client │───>│ MCP Server A │ │
│ │ (协议层) │ │ (文件系统工具) │ │
│ └───────────────┘ └───────────────────────┘ │
│ ┌───────────────┐ ┌───────────────────────┐ │
│ │ MCP Client │───>│ MCP Server B │ │
│ │ (协议层) │ │ (数据库查询) │ │
│ └───────────────┘ └───────────────────────┘ │
│ ┌───────────────┐ ┌───────────────────────┐ │
│ │ MCP Client │───>│ MCP Server C │ │
│ │ (协议层) │ │ (API 集成) │ │
│ └───────────────┘ └───────────────────────┘ │
└─────────────────────────────────────────────────┘
| 角色 | 职责 |
|---|---|
| Host(宿主) | 运行 LLM 的应用环境,如 Claude Desktop、IDE 插件 |
| Client(客户端) | Host 内部的协议处理层,负责与 Server 建立连接和通信 |
| Server(服务端) | 暴露具体能力(工具、资源、提示词)的服务进程 |
MCP 所有消息都遵循 JSON-RPC 2.0 规范:
请求 (Request):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/tmp/hello.txt" }
}
}
响应 (Response):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{ "type": "text", "text": "Hello, MCP!" }
]
}
}
通知 (Notification):(无 id 字段,不需要响应)
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": { "progress": 50, "total": 100 }
}
MCP 支持两种标准传输方式:
Client Server
│ │
│──stdin: JSON-RPC Request─────>│
│ │
│<──stdout: JSON-RPC Response────│
│ │
Client Server
│──GET /sse────────────────────>│ (建立 SSE 连接)
│<──SSE: endpoint URL───────────│
│ │
│──POST /messages──────────────>│ (发送请求)
│<──SSE: JSON-RPC Response──────│ (通过 SSE 接收)
一个完整的 MCP 会话经历以下阶段:
Client Server
│ │
│ 1. 初始化 (Initialize) │
│──{ method: "initialize" }────────>│
│<──{ result: { capabilities } }────│
│ │
│ 2. 确认 (Initialized) │
│──{ method: "initialized" }───────>│
│ │
│ 3. 正常通信 │
│──{ method: "tools/list" }────────>│
│<──{ result: { tools: [...] } }────│
│ │
│──{ method: "tools/call" }────────>│
│<──{ result: { content: [...] } }──│
│ │
│ 4. 关闭 │
│──(进程退出 / 连接断开)────────────>│
Client 发送自身信息和能力:
{
"method": "initialize",
"params": {
"protocolVersion": "2025-03-26",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": {
"name": "MyApp",
"version": "1.0.0"
}
}
}
Server 响应自身能力:
{
"result": {
"protocolVersion": "2025-03-26",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "subscribe": true },
"prompts": { "listChanged": true },
"logging": {}
},
"serverInfo": {
"name": "MyServer",
"version": "1.0.0"
}
}
}
MCP 定义了三大核心能力(Primitives):
工具是 模型控制 的能力 —— LLM 决定何时调用。
{
"name": "get_weather",
"description": "获取指定城市的天气信息",
"inputSchema": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "城市名称,如 '北京'"
}
},
"required": ["city"]
}
}
资源是 应用控制 的能力 —— 应用代码决定何时读取。
{
"uri": "file:///project/README.md",
"name": "项目 README",
"description": "项目的说明文档",
"mimeType": "text/markdown"
}
提示词是 用户控制 的能力 —— 用户主动选择使用。
{
"name": "code_review",
"description": "代码审查提示词",
"arguments": [
{
"name": "language",
"description": "编程语言",
"required": true
},
{
"name": "code",
"description": "待审查的代码",
"required": true
}
]
}
| 能力 | 控制方 | 用途 | 类比 |
|---|---|---|---|
| Tools | LLM 模型 | 执行操作、调用 API | 函数调用 |
| Resources | 应用代码 | 提供上下文数据 | 文件读取 |
| Prompts | 用户 | 预设交互模板 | 快捷指令 |
接下来,我们用 TypeScript + Node.js 当前主流的方式从零实现一个功能完整的 MCP Server。这个 Server 将提供:
为什么选 TypeScript? MCP 协议本身由 Anthropic 用 TypeScript 开发,官方 SDK 最为成熟;Node.js 生态与 AI 应用天然契合,async/await 异步模型也与 MCP 的异步通信完美匹配。
mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
初始化 TypeScript 配置:
npx tsc --init
修改 tsconfig.json 关键配置:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"]
}
在 package.json 中添加启动脚本:
{
"type": "module",
"scripts": {
"build": "tsc",
"start": "node build/index.js",
"dev": "tsx src/index.ts"
}
}
要求: Node.js >= 18,TypeScript >= 5.0
先从一个最简单的 Server 开始,确保环境正确:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({
name: "my-first-server",
version: "1.0.0",
});
server.tool("hello", { name: { type: "string", description: "你的名字" } }, async ({ name }) => ({
content: [{ type: "text", text: `Hello, ${name}! 欢迎使用 MCP Server.` }],
}));
const transport = new StdioServerTransport();
await server.connect(transport);
运行测试:
npx tsx src/index.ts
此时 Server 已在 stdin 上监听 JSON-RPC 消息,等待 Client 连接。
下面我们扩展 Server,加入实际有用的功能:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "node:fs/promises";
import path from "node:path";
const server = new McpServer({
name: "demo-server",
version: "1.0.0",
});
server.tool(
"get_weather",
"获取指定城市的当前天气信息",
{ city: z.string().describe("城市名称,如 '北京'") },
async ({ city }) => {
const mockData: Record<string, { temp: string; weather: string; humidity: string }> = {
"北京": { temp: "22°C", weather: "晴", humidity: "45%" },
"上海": { temp: "25°C", weather: "多云", humidity: "60%" },
"深圳": { temp: "28°C", weather: "阵雨", humidity: "80%" },
};
const data = mockData[city];
if (data) {
return { content: [{ type: "text", text: JSON.stringify(data) }] };
}
return { content: [{ type: "text", text: `未找到城市 '${city}' 的天气数据` }] };
}
);
server.tool(
"calculate",
"安全地计算数学表达式",
{ expression: z.string().describe("数学表达式,如 '2 + 3 * 4'") },
async ({ expression }) => {
const allowedChars = /^[0-9+\-*/().% ]+$/;
if (!allowedChars.test(expression)) {
return { content: [{ type: "text", text: "错误:表达式包含不允许的字符" }] };
}
try {
const result = new Function(`"use strict"; return (${expression})`)();
return { content: [{ type: "text", text: String(result) }] };
} catch (e) {
return { content: [{ type: "text", text: `计算错误:${e}` }] };
}
}
);
server.tool(
"get_current_time",
"获取当前时间",
{ timezone: z.string().optional().describe("时区,默认为 Asia/Shanghai") },
async ({ timezone = "Asia/Shanghai" }) => {
const now = new Date();
const result = {
datetime: now.toLocaleString("zh-CN", { timeZone: timezone }),
timezone,
timestamp: Math.floor(now.getTime() / 1000),
};
return { content: [{ type: "text", text: JSON.stringify(result) }] };
}
);
server.resource(
"app-settings",
"config://app/settings",
{ description: "获取应用配置信息", mimeType: "application/json" },
async () => ({
contents: [
{
uri: "config://app/settings",
text: JSON.stringify(
{
app_name: "Demo MCP Server",
version: "1.0.0",
features: { weather: true, calculator: true, file_reader: true },
max_file_size_mb: 10,
},
null,
2
),
},
],
})
);
server.resource(
"read-file",
"file://{path}",
{ description: "读取本地文件内容" },
async (uri) => {
const filePath = uri.pathname;
const allowedDirs = [process.env.HOME + "/documents", "/tmp"];
const absPath = path.resolve(filePath);
if (!allowedDirs.some((dir) => absPath.startsWith(dir))) {
return { contents: [{ uri: uri.href, text: `错误:无权访问路径 '${filePath}'` }] };
}
try {
const content = await fs.readFile(absPath, "utf-8");
return { contents: [{ uri: uri.href, text: content }] };
} catch {
return { contents: [{ uri: uri.href, text: `错误:文件 '${filePath}' 不存在` }] };
}
}
);
server.prompt(
"code_review",
"代码审查提示词",
{
code: z.string().describe("待审查的代码"),
language: z.string().optional().describe("编程语言,默认为 typescript"),
},
async ({ code, language = "typescript" }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `请对以下 ${language} 代码进行审查,从这几个方面给出评价:
1. **代码质量**:命名规范、可读性、结构清晰度
2. **潜在 Bug**:是否有逻辑错误、边界条件未处理
3. **性能**:是否有明显的性能问题或可优化之处
4. **安全性**:是否存在安全隐患
5. **最佳实践**:是否符合 ${language} 的最佳实践
\`\`\`${language}
${code}
\`\`\`
请给出具体的改进建议和修改后的代码示例。`,
},
},
],
})
);
server.prompt(
"explain_error",
"错误分析提示词",
{
error_message: z.string().describe("错误信息"),
context: z.string().optional().describe("相关上下文代码"),
},
async ({ error_message, context }) => {
let text = `我遇到了以下错误,请帮我分析原因并给出解决方案:
**错误信息:**
\`\`\`
${error_message}
\`\`\``;
if (context) {
text += `
**相关代码:**
\`\`\`
${context}
\`\`\``;
}
text += `
请从以下角度分析:
1. 错误的根本原因
2. 可能的触发条件
3. 推荐的解决方案
4. 如何避免类似问题`;
return {
messages: [{ role: "user", content: { type: "text", text } }],
};
}
);
const transport = new StdioServerTransport();
await server.connect(transport);
server.tool(
"get_weather",
"获取指定城市的当前天气信息",
{ city: z.string().describe("城市名称,如 '北京'") },
async ({ city }) => { ... }
);
server.tool(name, description, schema, handler) 四个参数{ content: [...] } 格式JSON.stringify() 序列化为 text 类型server.resource(
"app-settings",
"config://app/settings",
{ description: "...", mimeType: "..." },
async (uri) => ({ contents: [...] })
);
file://{path})通过模板解析参数URL 对象,返回 { contents: [...] } 格式server.prompt(
"code_review",
"代码审查提示词",
{ code: z.string(), language: z.string().optional() },
async ({ code, language }) => ({ messages: [...] })
);
{ messages: [...] } 格式,支持多轮消息.optional() 标记可选参数对于需要初始化资源的 Server(如数据库连接),可以在连接前后管理生命周期:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import Database from "better-sqlite3";
const db = new Database(":memory:");
db.exec(`
CREATE TABLE notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
const server = new McpServer({ name: "lifecycle-demo", version: "1.0.0" });
server.tool(
"add_note",
"添加一条笔记",
{ content: z.string().describe("笔记内容") },
async ({ content }) => {
db.prepare("INSERT INTO notes (content) VALUES (?)").run(content);
return { content: [{ type: "text", text: `笔记已添加:${content}` }] };
}
);
server.tool("list_notes", "列出所有笔记", {}, async () => {
const rows = db.prepare("SELECT id, content, created_at FROM notes ORDER BY id DESC").all();
if (rows.length === 0) {
return { content: [{ type: "text", text: "暂无笔记" }] };
}
const text = rows.map((r: any) => `[${r.id}] ${r.content} (${r.created_at})`).join("\n");
return { content: [{ type: "text", text }] };
});
const transport = new StdioServerTransport();
await server.connect(transport);
process.on("SIGINT", () => {
db.close();
process.exit(0);
});
MCP TypeScript SDK 的 McpServer 实例提供了丰富的 API:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
const server = new McpServer({
name: "advanced-server",
version: "1.0.0",
});
server.tool(
"long_running_task",
"执行一个长时间运行的任务",
{ data: z.string().describe("输入数据") },
async ({ data }) => {
const totalSteps = 5;
for (let i = 0; i < totalSteps; i++) {
await new Promise((resolve) => setTimeout(resolve, 1000));
console.error(`正在处理步骤 ${i + 1}/${totalSteps}...`);
}
return { content: [{ type: "text", text: `任务完成,处理了 ${data}` }] };
}
);
SDK 核心 API 速览:
| API | 说明 |
|---|---|
server.tool(name, desc, schema, handler) |
注册工具 |
server.resource(id, uri, meta, handler) |
注册资源 |
server.prompt(name, desc, schema, handler) |
注册提示词模板 |
server.connect(transport) |
连接传输层启动服务 |
server.sendResourceListChanged() |
通知客户端资源列表变化 |
server.sendToolListChanged() |
通知客户端工具列表变化 |
URI 模板中的 {param} 会自动提取为参数:
server.resource(
"user-profile",
"users://{user_id}/profile",
{ description: "获取用户资料" },
async (uri) => {
const userId = uri.pathname.split("/")[1];
return {
contents: [
{
uri: uri.href,
text: JSON.stringify({
id: userId,
name: `用户${userId}`,
email: `user${userId}@example.com`,
}),
},
],
};
}
);
Client 可以订阅 Resource 的变更通知:
server.sendResourceListChanged();
Prompt 可以返回多条消息,支持更复杂的交互:
server.prompt(
"debug_session",
"启动一个调试会话",
{
error: z.string().describe("错误信息"),
logs: z.string().optional().describe("相关日志"),
},
async ({ error, logs }) => {
const messages: any[] = [
{
role: "user",
content: { type: "text", text: `我遇到了一个错误需要调试:\n\n${error}` },
},
];
if (logs) {
messages.push({
role: "user",
content: { type: "text", text: `以下是相关日志:\n\`\`\`\n${logs}\n\`\`\`` },
});
}
return { messages };
}
);
MCP 官方提供了一个可视化调试工具 Inspector:
npx @modelcontextprotocol/inspector npx tsx src/index.ts
Inspector 提供 Web UI,可以:
在 Claude Desktop 配置文件中添加你的 Server:
macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
Windows: %APPDATA%\Claude\claude_desktop_config.json
{
"mcpServers": {
"demo-server": {
"command": "node",
"args": ["/absolute/path/to/build/index.js"],
"env": {
"API_KEY": "your-api-key-if-needed"
}
}
}
}
开发阶段可以用 tsx 直接运行 TypeScript:
{
"mcpServers": {
"demo-server": {
"command": "npx",
"args": ["tsx", "/absolute/path/to/src/index.ts"]
}
}
}
重启 Claude Desktop 后,在对话中即可使用你的 Server 提供的工具。
使用 Vitest 进行测试:
npm install -D vitest
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
import { createServer } from "../server.js";
describe("MCP Server", () => {
let client: Client;
beforeAll(async () => {
const server = createServer();
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
await server.connect(serverTransport);
client = new Client({ name: "test-client", version: "1.0.0" });
await client.connect(clientTransport);
});
it("should call hello tool", async () => {
const result = await client.callTool({ name: "hello", arguments: { name: "MCP" } });
expect(result.content[0].text).toContain("Hello, MCP");
});
it("should calculate expression", async () => {
const result = await client.callTool({ name: "calculate", arguments: { expression: "2 + 3" } });
expect(result.content[0].text).toBe("5");
});
it("should reject invalid expression", async () => {
const result = await client.callTool({
name: "calculate",
arguments: { expression: "require('child_process').exec('ls')" },
});
expect(result.content[0].text).toContain("不允许");
});
it("should list resources", async () => {
const result = await client.listResources();
expect(result.resources.length).toBeGreaterThan(0);
});
});
在 package.json 中添加测试脚本:
{
"scripts": {
"test": "vitest run"
}
}
new Function() 或专用库,不要用 evalconsole.error() 输出调试信息(stdout 用于协议通信)
const server = new McpServer({
name: "my-server",
version: "1.0.0",
capabilities: {
tools: {},
resources: { subscribe: true },
prompts: {},
},
});
my-mcp-server/
├── src/
│ ├── index.ts # 主服务入口
│ ├── server.ts # Server 实例创建(便于测试)
│ ├── tools/ # 工具实现
│ │ ├── weather.ts
│ │ └── calculator.ts
│ ├── resources/ # 资源实现
│ │ └── files.ts
│ ├── prompts/ # 提示词模板
│ │ └── templates.ts
│ └── __tests__/ # 测试
│ └── server.test.ts
├── build/ # 编译输出
├── tsconfig.json # TypeScript 配置
├── package.json # 项目配置
└── README.md
{
"name": "my-mcp-server",
"version": "1.0.0",
"type": "module",
"scripts": {
"build": "tsc",
"start": "node build/index.js",
"dev": "tsx src/index.ts",
"test": "vitest run",
"inspector": "npx @modelcontextprotocol/inspector npx tsx src/index.ts"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^1.12.0",
"zod": "^3.23.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"tsx": "^4.19.0",
"typescript": "^5.6.0",
"vitest": "^3.0.0"
}
}
| 客户端 | 说明 |
|---|---|
| Claude Desktop | Anthropic 官方桌面应用 |
| Claude Code | Anthropic CLI 工具 |
| Cursor | AI 代码编辑器 |
| Windsurf | AI 代码编辑器 |
| Zed | 高性能代码编辑器 |
MCP 协议通过标准化的 JSON-RPC 通信,为 LLM 生态建立了统一的工具接入标准。本文从协议设计到实际实现,完整覆盖了 MCP 的核心概念:
掌握这些内容,你就可以为任何 LLM 应用构建标准化的扩展能力了。
参考资料: