MCP 深度实战:从 JSON-RPC 2.0 到工具生态——2026 年 AI 工具集成标准化协议的架构完全指南
作者按:当你在第 20 次为不同的 AI 客户端重复编写同一套工具适配代码时,当你发现每个 LLM 都在用完全不同的方式调用外部数据源时,Anthropic 悄悄给出了答案——MCP(Model Context Protocol)。这不是又一个"AI 标准",而是真正解决工具碎片化问题的工业级协议。
目录
- 背景:AI 工具调用的碎片化危机
- MCP 核心概念与设计哲学
- 架构深度拆解:Client-Server-Host 三元组
- 通信协议:JSON-RPC 2.0 与传输层设计
- 三大核心能力:Resources、Tools、Prompts
- 实战一:从零编写文件系统 MCP Server
- 实战二:构建一个带 OAuth 2.0 的 GitHub MCP Server
- 实战三:MCP Client 集成与多 Server 编排
- 生产级考量:安全、性能与可观测性
- MCP vs Function Calling:本质差异与适用场景
- 生态全景:现有 MCP Server 与社区动态
- 2026 年展望:MCP 会成为 AI 的 HTTP 吗?
- 总结与行动建议
1. 背景:AI 工具调用的碎片化危机
1.1 问题的本质
2024 年以前,如果你要让 Claude 能够:
- 读取本地文件
- 查询 GitHub 仓库
- 发送 Slack 消息
- 访问 PostgreSQL 数据库
你需要为每一个 AI 客户端(Claude Desktop、Cursor、Cline...)分别编写适配代码。每个客户端的工具调用接口不同、认证方式不同、数据格式不同。
┌─────────────────────────────────────────────────────┐
│ 碎片化困境 │
├─────────────────────────────────────────────────────┤
│ Tool Provider (你) │
│ ├─ 为 Claude Desktop 写一套适配 │
│ ├─ 为 Cursor 写一套适配 │
│ ├─ 为 Cline 写一套适配 │
│ ├─ 为 ChatGPT Plugin 写一套适配 │
│ └─ 为自研 AI 产品写一套适配 │
│ │
│ 结果:同一个工具,N 倍开发成本 │
└─────────────────────────────────────────────────────┘
1.2 Function Calling 的局限
OpenAI 的 Function Calling 是一个模型能力,不是一个协议标准:
| 维度 | Function Calling | MCP |
|---|---|---|
| 定位 | 模型能力声明 | 标准化协议 |
| 跨模型复用 | ❌ 每家格式不同 | ✅ 一次实现,到处运行 |
| 生命周期管理 | ❌ 无状态 | ✅ 有状态会话 |
| 动态发现 | ❌ 硬编码 | ✅ 运行时枚举 |
| 安全沙箱 | ❌ 依赖应用层 | ✅ 协议层支持 |
1.3 Anthropic 的答案
2024 年 11 月,Anthropic 开源了 MCP(Model Context Protocol),核心目标:
"USB-C for AI" — 一个标准化接口,让任何工具提供方只需实现一次 Server,任何支持 MCP 的 AI 客户端都能无缝接入。
2. MCP 核心概念与设计哲学
2.1 协议定位
MCP 是一个应用层协议,基于 JSON-RPC 2.0,运行在传输层(stdio / HTTP+SSE)之上。
┌──────────────────────────────────────────────────────┐
│ MCP 协议栈 │
├──────────────────────────────────────────────────────┤
│ MCP Protocol Layer (消息路由、能力协商、通知) │
├──────────────────────────────────────────────────────┤
│ JSON-RPC 2.0 (请求、响应、通知) │
├──────────────────────────────────────────────────────┤
│ Transport Layer (stdio / HTTP+SSE / WebSocket) │
└──────────────────────────────────────────────────────┘
2.2 设计原则
Anthropic 在 MCP 设计中遵循了以下原则:
- 简单性优先:协议消息类型尽量少,降低实现门槛
- 可组合性:多个 MCP Server 可以同时挂载到同一个 Client
- 安全默认:工具调用需要用户确认(可配置自动批准)
- 传输无关:支持本地进程(stdio)和远程服务(SSE)两种模式
- 有状态会话:支持订阅-通知模式,Server 可以主动推送更新
3. 架构深度拆解:Client-Server-Host 三元组
3.1 角色定义
┌──────────────────────────────────────────────────────────┐
│ Host Application (e.g., Claude Desktop) │
│ ┌────────────┐ ┌────────────┐ │
│ │ MCP Client │ │ MCP Client │ ... │
│ └─────┬──────┘ └─────┬──────┘ │
│ │ │ │
│ │ JSON-RPC │ │
│ │ ←──────────────→ │ │
│ ▼ ▼ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ MCP Server │ │ MCP Server │ │
│ │ (Filesystem)│ │ (GitHub API)│ │
│ └─────────────┘ └─────────────┘ │
└──────────────────────────────────────────────────────────┘
| 角色 | 职责 | 实例 |
|---|---|---|
| Host | 运行 AI 模型的环境,管理多个 Client 生命周期 | Claude Desktop、Cursor、Cline |
| MCP Client | 嵌入在 Host 中,负责与单个 MCP Server 建立连接、发送请求、处理响应 | Host 内部组件 |
| MCP Server | 暴露外部能力(工具、资源、提示)的轻量级服务 | @modelcontextprotocol/server-filesystem |
3.2 连接生命周期
// 伪代码:连接建立流程
async function establishMCPConnection() {
// 1. Client 启动 Server 进程(stdio 模式)或连接远程 URL(SSE 模式)
const transport = new StdioTransport('npx', ['-y', '@modelcontextprotocol/server-filesystem', '/path/to/dir']);
const client = new MCPClient();
// 2. 初始化握手
const initResponse = await client.initialize({
protocolVersion: '2024-11-05',
capabilities: {
roots: { listChanged: true },
sampling: {}
},
clientInfo: { name: 'my-ai-app', version: '1.0.0' }
});
// 3. Server 返回支持的能力
console.log(initResponse.capabilities);
// { tools: { listChanged: true }, resources: {} }
// 4. 通知初始化完成
await client.notifyInitialized();
// 5. 开始正常通信(工具调用、资源读取等)
const tools = await client.listTools();
}
4. 通信协议:JSON-RPC 2.0 与传输层设计
4.1 消息格式
MCP 所有消息均遵循 JSON-RPC 2.0 规范:
请求(Request):
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {
"path": "/etc/hosts"
}
}
}
响应(Response):
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "127.0.0.1 localhost
..."
}
]
}
}
通知(Notification,无 id,无响应):
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
错误(Error):
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32600,
"message": "Invalid Request",
"data": { "details": "..." }
}
}
4.2 传输层:stdio vs SSE
stdio 模式(本地 Server)
适用于本地工具(文件系统、本地数据库、Shell 命令):
// Server 端
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
const server = new Server({ name: 'my-server', version: '1.0.0' });
const transport = new StdioServerTransport();
await server.connect(transport);
// Server 从 stdin 读取请求,向 stdout 写入响应
# Client 启动 Server
npx -y @modelcontextprotocol/server-filesystem /path/to/allowed/dir
# Client 通过子进程的 stdin/stdout 与 Server 通信
HTTP + SSE 模式(远程 Server)
适用于云端服务(GitHub API、数据库即服务):
// Server 端(Express + SSE)
import express from 'express';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
const app = express();
app.get('/sse', async (req, res) => {
const transport = new SSEServerTransport('/message', res);
const server = new MCPServer();
await server.connect(transport);
});
app.post('/message', async (req, res) => {
// 处理来自 Client 的 POST 请求,转发给对应的 transport
});
// Client 端连接远程 Server
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
const transport = new SSEClientTransport(new URL('https://mcp-server.example.com/sse'));
const client = new MCPClient();
await client.connect(transport);
4.3 为什么选 JSON-RPC 2.0?
Anthropic 选择 JSON-RPC 2.0 而非 RESTful HTTP 的原因:
- 双向通信:JSON-RPC 支持 Server 主动调用 Client(通过
sampling/createMessage),REST 不支持 - 通知机制:无需响应的单向消息(如资源变更通知)
- 批量请求:多条消息可以合并在一个 HTTP 请求中发送
- 简单性:相比 gRPC / GraphQL,JSON-RPC 更易调试和实现
5. 三大核心能力:Resources、Tools、Prompts
MCP Server 可以暴露三种能力,每种能力有独立的命名空间和方法集。
5.1 Resources(资源):只读数据访问
Resources 是可以被 AI 读取但不执行副作用的数据,类似于"文件系统"或"数据库视图"。
定义 Resource:
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: 'file:///etc/hosts',
name: 'Hosts File',
description: 'Static hostname mapping table',
mimeType: 'text/plain'
},
{
uri: 'postgres://my-db/users',
name: 'Users Table',
description: 'All registered users',
mimeType: 'application/json'
}
]
}));
读取 Resource:
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri.startsWith('file://')) {
const path = uriToPath(uri);
const content = await fs.promises.readFile(path, 'utf-8');
return {
contents: [{
uri,
mimeType: 'text/plain',
text: content
}]
};
}
if (uri.startsWith('postgres://')) {
const rows = await db.query('SELECT * FROM users');
return {
contents: [{
uri,
mimeType: 'application/json',
text: JSON.stringify(rows)
}]
};
}
});
订阅资源变更(可选):
// Server 主动通知 Client 资源已更新
server.sendNotification({
method: 'notifications/resources/updated',
params: { uri: 'file:///etc/hosts' }
});
5.2 Tools(工具):执行操作
Tools 是可以有副作用的函数,类似 Function Calling,但有更丰富的元数据和生命周期。
定义 Tool:
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'execute_shell_command',
description: 'Execute a shell command and return stdout/stderr',
inputSchema: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Shell command to execute'
},
timeout_ms: {
type: 'number',
description: 'Timeout in milliseconds',
default: 30000
}
},
required: ['command']
}
}
]
}));
调用 Tool:
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'execute_shell_command') {
const { command, timeout_ms = 30000 } = args;
// 安全警告:生产环境必须做沙箱隔离!
const result = await execAsync(command, { timeout: timeout_ms });
return {
content: [
{
type: 'text',
text: `STDOUT:
${result.stdout}
STDERR:
${result.stderr}`
}
],
isError: result.exitCode !== 0
};
}
throw new Error(`Unknown tool: ${name}`);
});
Tool 结果的多模态支持:
// 返回图片
return {
content: [
{
type: 'image',
data: base64EncodedImage,
mimeType: 'image/png'
}
]
};
// 返回音频
return {
content: [
{
type: 'audio',
data: base64EncodedAudio,
mimeType: 'audio/wav'
}
]
};
5.3 Prompts(提示模板):可复用的工作流
Prompts 是预定义的提示词模板,用户可以快速调用,类似"快捷指令"。
定义 Prompt:
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
prompts: [
{
name: 'code_review',
description: 'Perform a thorough code review',
arguments: [
{
name: 'file_path',
description: 'Path to the file to review',
required: true
},
{
name: 'focus_area',
description: 'Specific area to focus on (security, readability, performance)',
required: false
}
]
}
]
}));
获取 Prompt:
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'code_review') {
const filePath = args.file_path;
const focusArea = args.focus_area || 'general';
return {
description: `Code review for ${filePath}`,
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please perform a ${focusArea} code review for the following file:
@${filePath}
Provide specific, actionable feedback.`
}
}
]
};
}
});
6. 实战一:从零编写文件系统 MCP Server
现在让我们动手实现一个生产级文件系统 MCP Server,支持:
- 列出目录内容
- 读取文件
- 写入文件(需用户确认)
- 监听文件变更(通知)
6.1 项目初始化
mkdir mcp-filesystem-server && cd mcp-filesystem-server
npm init -y
npm install @modelcontextprotocol/sdk zod chokidar
npm install -D typescript @types/node
tsconfig.json:
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true
}
}
6.2 核心实现
src/index.ts:
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
ListToolsRequestSchema,
CallToolRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ListPromptsRequestSchema,
GetPromptRequestSchema
} from '@modelcontextprotocol/sdk/types.js';
import fs from 'fs/promises';
import path from 'path';
import { z } from 'zod';
// 环境变量定义允许访问的目录(安全隔离)
const ALLOWED_DIRS = process.env.ALLOWED_DIRS?.split(':').map(d => path.resolve(d)) || [];
function isPathAllowed(requestedPath: string): boolean {
const resolved = path.resolve(requestedPath);
return ALLOWED_DIRS.some(dir => resolved.startsWith(dir));
}
// 创建 Server 实例
const server = new Server(
{
name: 'secure-filesystem-server',
version: '1.0.0'
},
{
capabilities: {
tools: { listChanged: true },
resources: { subscribe: true, listChanged: true },
prompts: {}
}
}
);
// 工具定义
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'list_directory',
description: 'List contents of a directory',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'Directory path' }
},
required: ['path']
}
},
{
name: 'read_file',
description: 'Read contents of a file',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path' }
},
required: ['path']
}
},
{
name: 'write_file',
description: 'Write content to a file (OVERWRITES existing content)',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: 'File path' },
content: { type: 'string', description: 'Content to write' }
},
required: ['path', 'content']
}
}
]
}));
// 工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args || typeof args !== 'object') {
throw new Error('Invalid arguments');
}
const filePath = path.resolve(args.path as string);
// 安全检查:路径必须在允许范围内
if (!isPathAllowed(filePath)) {
return {
content: [{ type: 'text', text: `Error: Path ${filePath} is not in allowed directories.` }],
isError: true
};
}
try {
if (name === 'list_directory') {
const entries = await fs.readdir(filePath, { withFileTypes: true });
const listing = entries.map(entry => ({
name: entry.name,
type: entry.isDirectory() ? 'directory' : 'file',
path: path.join(filePath, entry.name)
}));
return {
content: [{ type: 'text', text: JSON.stringify(listing, null, 2) }]
};
}
if (name === 'read_file') {
const content = await fs.readFile(filePath, 'utf-8');
return {
content: [{ type: 'text', text: content }]
};
}
if (name === 'write_file') {
await fs.writeFile(filePath, args.content as string, 'utf-8');
return {
content: [{ type: 'text', text: `Successfully wrote to ${filePath}` }]
};
}
throw new Error(`Unknown tool: ${name}`);
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error.message}` }],
isError: true
};
}
});
// 资源定义(将文件暴露为 Resource)
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
const dirPath = request.params?.directory || ALLOWED_DIRS[0];
const files = await fs.readdir(dirPath, { withFileTypes: true });
return {
resources: files
.filter(f => f.isFile())
.map(f => ({
uri: `file://${path.join(dirPath, f.name)}`,
name: f.name,
mimeType: 'text/plain'
}))
};
});
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const filePath = request.params.uri.replace('file://', '');
if (!isPathAllowed(filePath)) {
throw new Error('Access denied');
}
const content = await fs.readFile(filePath, 'utf-8');
return {
contents: [{
uri: request.params.uri,
mimeType: 'text/plain',
text: content
}]
};
});
// 启动 Server
async function main() {
if (ALLOWED_DIRS.length === 0) {
console.error('Error: ALLOWED_DIRS environment variable must be set');
process.exit(1);
}
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Secure Filesystem MCP Server running on stdio');
}
main();
6.3 测试方法
# 构建
npm run build
# 本地测试(使用 mcptest 工具)
npx @modelcontextprotocol/inspector dist/index.js
# 在 Claude Desktop 中配置
# ~/Library/Application Support/Claude/claude_desktop_config.json
{
"mcpServers": {
"secure-filesystem": {
"command": "node",
"args": ["/path/to/mcp-filesystem-server/dist/index.js"],
"env": {
"ALLOWED_DIRS": "/Users/qnnet/Documents:/Users/qnnet/Desktop"
}
}
}
}
7. 实战二:构建一个带 OAuth 2.0 的 GitHub MCP Server
远程 MCP Server 需要认证。以下是支持 GitHub OAuth 2.0 的完整实现。
7.1 架构设计
┌─────────────────────────────────────────────────────┐
│ OAuth 2.0 流程 │
├─────────────────────────────────────────────────────┤
│ 1. Client 访问 Server → 发现需要认证 │
│ 2. Server 返回 401 + authorization_url │
│ 3. User 在浏览器中完成 GitHub OAuth 授权 │
│ 4. GitHub 重定向到 Server 的 callback URL │
│ 5. Server 获取 access_token,返回给 Client │
│ 6. Client 在后续请求中携带 Bearer Token │
└─────────────────────────────────────────────────────┘
7.2 核心代码
src/github-server.ts:
import express from 'express';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import github from '@octokit/rest';
const app = express();
const PORT = process.env.PORT || 3000;
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID!;
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET!;
// OAuth 回调处理
app.get('/oauth/callback', async (req, res) => {
const { code } = req.query;
// 用 code 交换 access_token
const tokenResponse = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
body: JSON.stringify({
client_id: GITHUB_CLIENT_ID,
client_secret: GITHUB_CLIENT_SECRET,
code
})
});
const { access_token } = await tokenResponse.json();
// 将 token 存储到 session
req.session.accessToken = access_token;
res.redirect('/sse'); // 重定向到 SSE 端点
});
// SSE 端点(需要认证)
app.get('/sse', async (req, res) => {
if (!req.session.accessToken) {
// 返回 OAuth URL,让 Client 引导用户授权
const authUrl = `https://github.com/login/oauth/authorize?client_id=${GITHUB_CLIENT_ID}&scope=repo`;
return res.status(401).json({ error: 'unauthorized', authorization_url: authUrl });
}
const transport = new SSEServerTransport('/message', res);
const server = new Server({ name: 'github-mcp-server', version: '1.0.0' });
// 初始化 GitHub API 客户端
const octokit = new github.Octokit({ auth: req.session.accessToken });
// 定义工具
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: 'list_repos',
description: 'List user repositories',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'create_issue',
description: 'Create a GitHub Issue',
inputSchema: {
type: 'object',
properties: {
owner: { type: 'string' },
repo: { type: 'string' },
title: { type: 'string' },
body: { type: 'string' }
},
required: ['owner', 'repo', 'title']
}
}
]
}));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (name === 'list_repos') {
const { data } = await octokit.repos.listForAuthenticatedUser();
return {
content: [{ type: 'text', text: JSON.stringify(data.map(r => r.full_name)) }]
};
}
if (name === 'create_issue') {
const { owner, repo, title, body } = args;
const { data } = await octokit.issues.create({ owner, repo, title, body });
return {
content: [{ type: 'text', text: `Created issue #${data.number}: ${data.html_url}` }]
};
}
});
await server.connect(transport);
});
app.listen(PORT, () => console.log(`GitHub MCP Server listening on port ${PORT}`));
8. 实战三:MCP Client 集成与多 Server 编排
8.1 在自定义 AI 应用中集成 MCP Client
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
import OpenAI from 'openai';
// 1. 启动多个 MCP Server
const filesystemTransport = new StdioClientTransport({
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/allowed/path']
});
const githubTransport = new StdioClientTransport({
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-github']
});
// 2. 创建 MCP Clients
const filesystemClient = new Client({ name: 'my-app', version: '1.0.0' });
const githubClient = new Client({ name: 'my-app', version: '1.0.0' });
await filesystemClient.connect(filesystemTransport);
await githubClient.connect(githubTransport);
// 3. 获取所有可用工具(合并多个 Server 的工具列表)
const [fsTools, ghTools] = await Promise.all([
filesystemClient.listTools(),
githubClient.listTools()
]);
const allTools = [
...fsTools.tools.map(t => ({ ...t, server: 'filesystem' })),
...ghTools.tools.map(t => ({ ...t, server: 'github' }))
];
// 4. 将 MCP 工具转换为 OpenAI Function Calling 格式
const openaiTools = allTools.map(t => ({
type: 'function',
function: {
name: `${t.server}_${t.name}`, // 加前缀避免命名冲突
description: t.description,
parameters: t.inputSchema
}
}));
// 5. 调用 LLM
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const response = await openai.chat.completions.create({
model: 'gpt-4',
messages: [{ role: 'user', content: 'List my GitHub repos and save to a file' }],
tools: openaiTools,
tool_choice: 'auto'
});
// 6. 执行工具调用
for (const toolCall of response.choices[0].message.tool_calls || []) {
const [serverName, toolName] = toolCall.function.name.split('_', 2);
const args = JSON.parse(toolCall.function.arguments);
let result;
if (serverName === 'filesystem') {
result = await filesystemClient.callTool({ name: toolName, arguments: args });
} else if (serverName === 'github') {
result = await githubClient.callTool({ name: toolName, arguments: args });
}
// 7. 将结果返回给 LLM
// ...(省略将 result 发回 OpenAI 的代码)
}
8.2 多 Server 编排模式
对于复杂任务,可以让 LLM 协调多个 MCP Server:
// 示例:代码审查工作流
// 1. GitHub Server 获取 PR diff
const diff = await githubClient.callTool({
name: 'get_pr_diff',
arguments: { owner: 'my-org', repo: 'my-repo', pull_number: 123 }
});
// 2. 文件系统 Server 读取 lint 配置
const eslintConfig = await filesystemClient.callTool({
name: 'read_file',
arguments: { path: '/path/to/.eslintrc.json' }
});
// 3. 将 diff + config 一起发给 LLM 分析
const review = await llm.analyze({ diff: diff.content[0].text, config: eslintConfig.content[0].text });
// 4. Slack Server 发送审查结果
await slackClient.callTool({
name: 'send_message',
arguments: { channel: '#code-review', text: review }
});
9. 生产级考量:安全、性能与可观测性
9.1 安全最佳实践
| 威胁 | 缓解措施 |
|---|---|
| 路径遍历攻击 | 所有文件路径必须用 path.resolve() + 前缀检查 |
| 命令注入 | 禁止将用户输入直接拼入 Shell 命令,使用参数化 API |
| Token 泄露 | 远程 Server 必须用 HTTPS,token 存 HTTP-only cookie |
| 过度权限 | 实现工具级别的权限控制,用户可配置自动批准白名单 |
| 资源耗尽 | 限制工具执行超时、内存上限、返回数据大小 |
路径遍历防护示例:
function sanitizePath(userPath: string, allowedDirs: string[]): string {
const resolved = path.resolve(userPath);
const isAllowed = allowedDirs.some(allowed => {
const normalizedAllowed = path.normalize(allowed);
const normalizedTarget = path.normalize(resolved);
return normalizedTarget.startsWith(normalizedAllowed);
});
if (!isAllowed) {
throw new Error(`Path ${userPath} is outside allowed directories`);
}
return resolved;
}
9.2 性能优化
问题:MCP Server 的工具列表可能在每次对话时都重新获取,造成延迟。
解决方案:Client 端缓存 + Server 端变更通知
// Server 端:工具列表变更时发送通知
server.sendNotification({
method: 'notifications/tools/list_changed'
});
// Client 端:收到通知后刷新缓存
client.onNotification('notifications/tools/list_changed', async () => {
const newTools = await client.listTools();
toolCache.set(newTools);
});
9.3 可观测性
集成 OpenTelemetry 追踪 MCP 调用链:
import { trace, SpanStatusCode } from '@opentelemetry/api';
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const tracer = trace.getTracer('mcp-server');
return await tracer.startActiveSpan(`tool_call_${request.params.name}`, async (span) => {
try {
span.setAttributes({
'mcp.tool.name': request.params.name,
'mcp.session.id': request.sessionId
});
const result = await executeTool(request.params);
span.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
span.setStatus({ code: SpanStatusCode.ERROR, message: error.message });
throw error;
} finally {
span.end();
}
});
});
10. MCP vs Function Calling:本质差异与适用场景
10.1 对比矩阵
| 维度 | OpenAI Function Calling | MCP |
|---|---|---|
| 本质 | 模型能力 | 协议标准 |
| 跨模型 | ❌ 每家的 schema 不同 | ✅ 统一协议 |
| 生命周期 | 无状态(每次对话重新声明) | 有状态(连接可保持) |
| 动态发现 | ❌ 硬编码在系统提示中 | ✅ tools/list 运行时枚举 |
| 多工具编排 | 应用层自行实现 | 协议支持多 Server 挂载 |
| 资源订阅 | ❌ 不支持 | ✅ 通知机制 |
| 认证 | 应用层处理 | 协议层支持 OAuth 2.0 |
| 适用场景 | 简单工具调用 | 复杂工具生态、企业级部署 |
10.2 何时用 Function Calling?
- 你的工具只给一个模型用(如只用 OpenAI GPT-4)
- 工具数量少于 10 个,不需要动态发现
- 无状态调用即可满足需求
10.3 何时用 MCP?
- 你希望工具一次实现,多处复用(Claude Desktop、Cursor、自研产品)
- 工具需要有状态会话(如订阅数据库变更通知)
- 你需要远程托管工具(多租户 SaaS)
- 工具数量超过 20 个,需要分类管理和动态加载
11. 生态全景:现有 MCP Server 与社区动态
11.1 官方 Server
Anthropic 维护了以下官方 MCP Server(均有开源实现):
| Server | 功能 | 适用场景 |
|---|---|---|
@modelcontextprotocol/server-filesystem | 本地文件系统读写 | 代码编辑、日志分析 |
@modelcontextprotocol/server-github | GitHub API 集成 | 代码审查、Issue 管理 |
@modelcontextprotocol/server-google-drive | Google Drive 访问 | 文档协作 |
@modelcontextprotocol/server-slack | Slack 消息发送 | 通知、团队协作 |
@modelcontextprotocol/server-postgres | PostgreSQL 查询 | 数据分析、业务洞察 |
@modelcontextprotocol/server-sqlite | SQLite 查询 | 本地数据存储 |
11.2 社区亮点
- Cloudflare MCP Template:一键部署远程 MCP Server 到 Cloudflare Workers
- Zed Editor MCP 支持:直接在代码中调用 MCP 工具
- Linear MCP Server:项目管理工具 Linear 的非官方 MCP 集成
- Postgres MCP Server + 连接池:企业级数据库访问,支持连接复用
11.3 采用案例
- Replit:通过 MCP 让 AI Agent 访问用户项目文件
- Zed:集成 MCP 实现"AI Pair Programming"
- Sourcegraph:用 MCP 统一 Cody(他们的 AI 编程助手)的工具接口
12. 2026 年展望:MCP 会成为 AI 的 HTTP 吗?
12.1 积极信号
- 多模型支持:除了 Claude,GPT-4、Gemini 也在适配 MCP
- 企业采用:Snowflake、Neon、Axiom 等数据公司已经开始提供官方 MCP Server
- 开发工具集成:VS Code、JetBrains 都在评估 MCP 支持
12.2 挑战
- 协议版本碎片化:如果社区 fork 协议,会导致互通性问题
- 安全审计成本:每个 MCP Server 都需要独立安全审查
- 性能瓶颈:JSON-RPC 2.0 在大文件传输时效率不如 gRPC
12.3 我的判断
MCP 有机会成为 AI 工具集成的标准,但需要解决两个关键问题:
- 远程 Server 的认证标准化:目前各家自己实现 OAuth,需要统一的 Discovery 协议(类似 OpenID Connect)
- 工具市场的建立:需要类似"npm for MCP"的 Registry,支持版本管理、签名验证、依赖解析
13. 总结与行动建议
13.1 核心要点回顾
- MCP 解决的是工具碎片化问题,不是模型能力问题
- 协议基于 JSON-RPC 2.0,支持 stdio 和 SSE 两种传输层
- 三大核心能力:Resources(只读)、Tools(可执行)、Prompts(模板)
- 安全是第一位:路径检查、权限控制、输入净化缺一不可
- 生产部署需要可观测性:集成 OpenTelemetry,记录每次工具调用
13.2 行动清单
如果你是企业开发者:
- 评估内部工具是否适合暴露为 MCP Server
- 为数据平台(数据库、数据仓库、向量数据库)编写 MCP Server
- 在 AI 应用中集成 MCP Client,替换硬编码的 Function Calling
如果你是开源贡献者:
- 为你喜欢的开源工具(Redis、Elasticsearch、Kafka)编写 MCP Server
- 参与 MCP 官方 SDK 的贡献(TypeScript、Python、Rust 版本都在活跃开发中)
- 构建 MCP Server 的测试工具(类似 Postman for MCP)
如果你是 AI 产品开发者:
- 在产品中支持 MCP Client,让用户自带工具
- 提供 MCP Server 开发框架,降低第三方开发者的门槛
- 建立工具市场,让用户发现和安装 MCP Server
参考资源
- 官方文档: https://modelcontextprotocol.io
- 官方 SDK (TypeScript): https://github.com/modelcontextprotocol/typescript-sdk
- 官方 SDK (Python): https://github.com/modelcontextprotocol/python-sdk
- MCP Inspector (调试工具):
npx @modelcontextprotocol/inspector - Awesome MCP Servers: https://github.com/punkpeye/awesome-mcp-servers
写这篇文章时,MCP 协议版本是 2024-11-05。如果阅读时协议已更新,请务必查阅最新官方文档。
Happy Building! 🚀