Model Context Protocol: 工具协议的技术本质与架构价值

Model Context Protocol: 工具协议的技术本质与架构价值

协议诞生的技术背景

当前 AI 工具的集成方式存在一个根本性问题:每个工具都在重复实现相同的功能

以"读取 Notion 数据库"为例,实现路径:

Clawdbot → 实现 Notion Tool
Claude Code → 实现 Notion Extension  
Cursor → 实现 Notion Plugin
Windsurf → 实现 Notion Integration

每个团队都要:

  1. 研究 Notion API 文档
  2. 处理认证(OAuth / API Token)
  3. 实现数据查询、创建、更新逻辑
  4. 处理错误和重试
  5. 写测试用例

这是 N × M 的复杂度(N 个 AI 工具 × M 个外部服务)。

Model Context Protocol (MCP) 试图将其降低到 N + M

协议的核心抽象

工具即服务

MCP 的核心思想:把工具变成一个独立的"服务",通过标准协议对外提供能力。

传统模式:
  AI Tool ──┬── Notion API (代码耦合)
           ├── GitHub API
           └── Gmail API

MCP 模式:
  AI Tool ── MCP Client ──┬── MCP Server (Notion)
                         ├── MCP Server (GitHub)
                         └── MCP Server (Gmail)

AI 工具只需要实现一次 MCP Client,就能连接所有 MCP Server。

协议规范

MCP 基于 JSON-RPC 2.0,通过 stdio 或 HTTP 通信。

Server 暴露的接口

// MCP Server 必须实现的方法
interface MCPServer {
  // 列出可用工具
  tools/list(): Tool[];
  
  // 调用工具
  tools/call(name: string, arguments: object): any;
  
  // 列出可用资源(可选)
  resources/list(): Resource[];
  
  // 读取资源
  resources/read(uri: string): string;
}

Tool 的数据结构

{
  "name": "notion_create_page",
  "description": "创建一个新的 Notion 页面",
  "inputSchema": {
    "type": "object",
    "properties": {
      "title": {"type": "string"},
      "content": {"type": "string"}
    },
    "required": ["title"]
  }
}

AI 通过 inputSchema 知道这个工具需要什么参数,可以自动生成调用代码。

技术架构剖析

Client 端实现

export class MCPClient {
  private transports: Map<string, Transport> = new Map();
  
  // 连接到一个 MCP Server
  async connect(serverConfig: ServerConfig): Promise<void> {
    const transport = serverConfig.type === 'stdio' 
      ? new StdioTransport(serverConfig.command)
      : new HTTPTransport(serverConfig.url);
    
    await transport.connect();
    this.transports.set(serverConfig.name, transport);
  }
  
  // 发现所有可用工具
  async listTools(serverName: string): Promise<Tool[]> {
    const transport = this.transports.get(serverName);
    const response = await transport.request({
      jsonrpc: '2.0',
      method: 'tools/list',
      id: generateId()
    });
    return response.result;
  }
  
  // 调用工具
  async callTool(
    serverName: string, 
    toolName: string, 
    args: object
  ): Promise<any> {
    const transport = this.transports.get(serverName);
    const response = await transport.request({
      jsonrpc: '2.0',
      method: 'tools/call',
      params: { name: toolName, arguments: args },
      id: generateId()
    });
    return response.result;
  }
}

Server 端实现

export abstract class MCPServer {
  protected tools: Map<string, ToolHandler> = new Map();
  
  // 子类注册工具
  protected registerTool(
    name: string, 
    schema: JSONSchema, 
    handler: ToolHandler
  ): void {
    this.tools.set(name, { schema, handler });
  }
  
  // 处理客户端请求
  async handleRequest(request: JSONRPCRequest): Promise<JSONRPCResponse> {
    switch (request.method) {
      case 'tools/list':
        return {
          jsonrpc: '2.0',
          id: request.id,
          result: Array.from(this.tools.entries()).map(([name, tool]) => ({
            name,
            description: tool.schema.description,
            inputSchema: tool.schema
          }))
        };
      
      case 'tools/call':
        const { name, arguments: args } = request.params;
        const tool = this.tools.get(name);
        if (!tool) {
          return {
            jsonrpc: '2.0',
            id: request.id,
            error: { code: -32601, message: 'Tool not found' }
          };
        }
        
        const result = await tool.handler(args);
        return {
          jsonrpc: '2.0',
          id: request.id,
          result
        };
      
      default:
        return {
          jsonrpc: '2.0',
          id: request.id,
          error: { code: -32601, message: 'Method not found' }
        };
    }
  }
}

示例:Notion MCP Server

export class NotionMCPServer extends MCPServer {
  private notionClient: NotionClient;
  
  constructor(apiKey: string) {
    super();
    this.notionClient = new NotionClient({ auth: apiKey });
    
    // 注册工具
    this.registerTool('create_page', {
      type: 'object',
      properties: {
        title: { type: 'string' },
        content: { type: 'string' }
      },
      required: ['title']
    }, this.createPage.bind(this));
    
    this.registerTool('search_pages', {
      type: 'object',
      properties: {
        query: { type: 'string' }
      },
      required: ['query']
    }, this.searchPages.bind(this));
  }
  
  private async createPage(args: any): Promise<any> {
    const page = await this.notionClient.pages.create({
      parent: { database_id: process.env.NOTION_DB_ID },
      properties: {
        title: { title: [{ text: { content: args.title } }] }
      },
      children: [{
        object: 'block',
        type: 'paragraph',
        paragraph: { rich_text: [{ text: { content: args.content } }] }
      }]
    });
    return { pageId: page.id, url: page.url };
  }
  
  private async searchPages(args: any): Promise<any> {
    const response = await this.notionClient.search({
      query: args.query,
      filter: { property: 'object', value: 'page' }
    });
    return response.results.map(page => ({
      id: page.id,
      title: page.properties.title?.title[0]?.plain_text || 'Untitled'
    }));
  }
}

// 启动 Server
const server = new NotionMCPServer(process.env.NOTION_API_KEY);
const transport = new StdioTransport();
transport.listen(server.handleRequest.bind(server));

与传统 API 的对比

REST API

// Clawdbot 直接调用 Notion API
const response = await fetch('https://api.notion.com/v1/pages', {
  method: 'POST',
  headers: {
    'Authorization': `Bearer ${apiKey}`,
    'Notion-Version': '2022-06-28',
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    parent: { database_id: dbId },
    properties: { ... }
  })
});

问题:

  • Clawdbot 必须硬编码 Notion API 的调用逻辑
  • 需要处理 Notion 特有的认证方式
  • Notion API 更新时,Clawdbot 必须跟着更新

MCP

// Clawdbot 通过 MCP 调用
const result = await mcpClient.callTool('notion', 'create_page', {
  title: '新页面',
  content: '内容'
});

优势:

  • Clawdbot 不需要知道 Notion API 细节
  • MCP Server 维护者负责适配 Notion API 变更
  • 同样的代码可以调用任何 MCP Server

技术优势分析

跨语言支持

MCP Server 可以用任何语言实现:

# Python 实现的 MCP Server
from mcp_server import MCPServer

class WeatherServer(MCPServer):
    def register_tools(self):
        self.add_tool('get_weather', {
            'city': {'type': 'string'}
        }, self.get_weather)
    
    def get_weather(self, args):
        city = args['city']
        # 调用天气 API
        return {'temperature': 25, 'condition': 'sunny'}

server = WeatherServer()
server.listen()  # 监听 stdio

Clawdbot (Node.js) 可以通过 child_process 启动这个 Python 服务,通过 stdin/stdout 通信。

动态工具发现

Clawdbot 启动时,可以自动发现所有可用工具:

async function discoverTools(mcpServers: ServerConfig[]) {
  const allTools = [];
  
  for (const server of mcpServers) {
    await mcpClient.connect(server);
    const tools = await mcpClient.listTools(server.name);
    allTools.push(...tools.map(t => ({
      ...t,
      serverName: server.name
    })));
  }
  
  return allTools;
}

AI Agent 在运行时动态获取工具列表,不需要重启就能使用新安装的 MCP Server。

权限隔离

每个 MCP Server 是独立进程,有自己的权限边界:

// MCP Server 运行在沙盒中
const server = spawn('mcp-server-notion', {
  env: {
    NOTION_API_KEY: '...',  // 只给需要的环境变量
    PATH: '/usr/bin'        // 限制可执行路径
  },
  cwd: '/var/mcp/notion',   // 限制工作目录
  stdio: ['pipe', 'pipe', 'pipe']
});

如果 MCP Server 被攻击,也只能影响自己的进程,无法波及主程序。

技术挑战

性能开销

每次工具调用的路径:

AI Agent 
  → MCP Client 
    → IPC (stdin/stdout) 
      → MCP Server 
        → 外部 API

相比直接调用 API,增加了两层间接调用和一次进程间通信。

实测数据(调用 Notion API):

直接调用: ~150ms
通过 MCP: ~180ms (+20%)

对于大部分场景可接受,但对延迟敏感的应用(比如实时对话)需要考虑。

错误传播

MCP Server 内部错误需要正确传播到 Client:

// Server 端
try {
  const result = await someAPI();
  return { success: true, data: result };
} catch (error) {
  return {
    jsonrpc: '2.0',
    error: {
      code: -32000,
      message: error.message,
      data: {
        stack: error.stack,
        type: error.name
      }
    }
  };
}

Client 需要解析这些错误,转换成用户友好的提示。

版本兼容性

MCP 协议本身在演进,可能出现:

Clawdbot MCP Client v1.0
  ↓ 连接
MCP Server v2.0 (使用新协议)
  ↓ 不兼容

解决方案:协议版本协商

{
  "jsonrpc": "2.0",
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "capabilities": {
      "roots": { "listChanged": true },
      "sampling": {}
    }
  }
}

Server 返回自己支持的协议版本,Client 适配。

Clawdbot 的集成路径

配置文件

{
  "mcpServers": {
    "notion": {
      "command": "node",
      "args": ["./mcp-servers/notion/index.js"],
      "env": {
        "NOTION_API_KEY": "${NOTION_API_KEY}"
      }
    },
    "filesystem": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
    }
  }
}

启动流程

export class ClawdbotMCPIntegration {
  private mcpClient: MCPClient;
  private toolRegistry: Map<string, MCPTool> = new Map();
  
  async initialize(config: MCPConfig): Promise<void> {
    this.mcpClient = new MCPClient();
    
    // 连接所有配置的 MCP Server
    for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
      await this.mcpClient.connect({
        name,
        type: 'stdio',
        command: serverConfig.command,
        args: serverConfig.args,
        env: serverConfig.env
      });
      
      // 发现工具
      const tools = await this.mcpClient.listTools(name);
      for (const tool of tools) {
        this.toolRegistry.set(tool.name, {
          serverName: name,
          schema: tool.inputSchema,
          description: tool.description
        });
      }
    }
  }
  
  // Agent 调用工具时的入口
  async executeTool(toolName: string, args: object): Promise<any> {
    const tool = this.toolRegistry.get(toolName);
    if (!tool) {
      throw new Error(`Tool not found: ${toolName}`);
    }
    
    return await this.mcpClient.callTool(
      tool.serverName,
      toolName,
      args
    );
  }
}

结论

MCP 不是一个新概念(RPC、插件系统早就存在),但它在 AI 工具场景下提供了恰当的抽象层次:

过于底层的抽象(如 HTTP API):

  • 需要每个 AI 工具实现完整的集成逻辑
  • 无法动态发现能力

过于高层的抽象(如通用插件系统):

  • 协议过于复杂
  • 对 AI 场景的优化不足

MCP 找到了中间点:

  • 协议足够简单(JSON-RPC)
  • 抽象足够高层(工具 + 资源)
  • 针对 LLM 场景优化(inputSchema 可直接用于 function calling)

随着更多 AI 工具支持 MCP,一个"工具生态系统"正在形成。开发者不再需要为每个 AI 工具单独实现集成,只需要写一次 MCP Server,就能被所有客户端使用。

这是协议标准化的价值。


技术参考

← 返回博客列表