编程 MCP 深度实战:从 JSON-RPC 2.0 到工具生态——2026 年 AI 工具集成标准化协议的架构完全指南

2026-05-23 20:59:29 +0800 CST views 10

MCP 深度实战:从 JSON-RPC 2.0 到工具生态——2026 年 AI 工具集成标准化协议的架构完全指南

作者按:当你在第 20 次为不同的 AI 客户端重复编写同一套工具适配代码时,当你发现每个 LLM 都在用完全不同的方式调用外部数据源时,Anthropic 悄悄给出了答案——MCP(Model Context Protocol)。这不是又一个"AI 标准",而是真正解决工具碎片化问题的工业级协议。


目录

  1. 背景:AI 工具调用的碎片化危机
  2. MCP 核心概念与设计哲学
  3. 架构深度拆解:Client-Server-Host 三元组
  4. 通信协议:JSON-RPC 2.0 与传输层设计
  5. 三大核心能力:Resources、Tools、Prompts
  6. 实战一:从零编写文件系统 MCP Server
  7. 实战二:构建一个带 OAuth 2.0 的 GitHub MCP Server
  8. 实战三:MCP Client 集成与多 Server 编排
  9. 生产级考量:安全、性能与可观测性
  10. MCP vs Function Calling:本质差异与适用场景
  11. 生态全景:现有 MCP Server 与社区动态
  12. 2026 年展望:MCP 会成为 AI 的 HTTP 吗?
  13. 总结与行动建议

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 CallingMCP
定位模型能力声明标准化协议
跨模型复用❌ 每家格式不同✅ 一次实现,到处运行
生命周期管理❌ 无状态✅ 有状态会话
动态发现❌ 硬编码✅ 运行时枚举
安全沙箱❌ 依赖应用层✅ 协议层支持

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 设计中遵循了以下原则:

  1. 简单性优先:协议消息类型尽量少,降低实现门槛
  2. 可组合性:多个 MCP Server 可以同时挂载到同一个 Client
  3. 安全默认:工具调用需要用户确认(可配置自动批准)
  4. 传输无关:支持本地进程(stdio)和远程服务(SSE)两种模式
  5. 有状态会话:支持订阅-通知模式,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 的原因:

  1. 双向通信:JSON-RPC 支持 Server 主动调用 Client(通过 sampling/createMessage),REST 不支持
  2. 通知机制:无需响应的单向消息(如资源变更通知)
  3. 批量请求:多条消息可以合并在一个 HTTP 请求中发送
  4. 简单性:相比 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 CallingMCP
本质模型能力协议标准
跨模型❌ 每家的 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-githubGitHub API 集成代码审查、Issue 管理
@modelcontextprotocol/server-google-driveGoogle Drive 访问文档协作
@modelcontextprotocol/server-slackSlack 消息发送通知、团队协作
@modelcontextprotocol/server-postgresPostgreSQL 查询数据分析、业务洞察
@modelcontextprotocol/server-sqliteSQLite 查询本地数据存储

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 积极信号

  1. 多模型支持:除了 Claude,GPT-4、Gemini 也在适配 MCP
  2. 企业采用:Snowflake、Neon、Axiom 等数据公司已经开始提供官方 MCP Server
  3. 开发工具集成:VS Code、JetBrains 都在评估 MCP 支持

12.2 挑战

  1. 协议版本碎片化:如果社区 fork 协议,会导致互通性问题
  2. 安全审计成本:每个 MCP Server 都需要独立安全审查
  3. 性能瓶颈:JSON-RPC 2.0 在大文件传输时效率不如 gRPC

12.3 我的判断

MCP 有机会成为 AI 工具集成的标准,但需要解决两个关键问题

  1. 远程 Server 的认证标准化:目前各家自己实现 OAuth,需要统一的 Discovery 协议(类似 OpenID Connect)
  2. 工具市场的建立:需要类似"npm for MCP"的 Registry,支持版本管理、签名验证、依赖解析

13. 总结与行动建议

13.1 核心要点回顾

  1. MCP 解决的是工具碎片化问题,不是模型能力问题
  2. 协议基于 JSON-RPC 2.0,支持 stdio 和 SSE 两种传输层
  3. 三大核心能力:Resources(只读)、Tools(可执行)、Prompts(模板)
  4. 安全是第一位:路径检查、权限控制、输入净化缺一不可
  5. 生产部署需要可观测性:集成 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

参考资源


写这篇文章时,MCP 协议版本是 2024-11-05。如果阅读时协议已更新,请务必查阅最新官方文档。

Happy Building! 🚀

复制全文 生成海报 MCP AI 工具集成 协议标准 JSON-RPC

推荐文章

vue打包后如何进行调试错误
2024-11-17 18:20:37 +0800 CST
JavaScript设计模式:单例模式
2024-11-18 10:57:41 +0800 CST
html流光登陆页面
2024-11-18 15:36:18 +0800 CST
Nginx 性能优化有这篇就够了!
2024-11-19 01:57:41 +0800 CST
PHP 唯一卡号生成
2024-11-18 21:24:12 +0800 CST
程序员茄子在线接单