MCP 协议深度实战:AI Agent 通用互操作协议的架构设计与工程落地

为什么需要 MCP?

2024 年末,Anthropic 发布 Model Context Protocol (MCP),试图解决 AI Agent 领域一个长期痛点:工具集成的 N×M 问题。传统模式下,每个 AI 应用需要对接 N 个外部工具(数据库、API、文件系统等),每个工具又有 M 个不同实现,导致集成复杂度为 O(N×M)。MCP 的目标是将这个问题简化为 O(N+M)——所有工具实现一次 MCP Server 协议,所有 AI Host 只需实现一次 MCP Client。

截至目前,OpenAI、Google DeepMind、Microsoft Copilot 等主流平台已宣布支持或兼容 MCP 协议。这场围绕 AI Agent 互操作标准的竞争,正在重塑整个 AI 工具链生态。

MCP 架构核心设计

2.1 客户端-服务器模型

MCP 采用经典的 C-S 架构,但有一个关键创新:通信是双向发现的。Server 不仅提供服务,还会向 Client 声明自己支持哪些工具(tools)、资源(resources) 和提示(prompts)。

// MCP Server 能力声明示例 (TypeScript)
const server = new McpServer({
  name: "knowledge-base",
  version: "1.0.0"
});

// 声明工具能力
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return {
    tools: [
      {
        name: "search_documents",
        description: "Search through knowledge base documents",
        inputSchema: {
          type: "object",
          properties: {
            query: { type: "string", description: "Search query" },
            limit: { type: "number", description: "Max results (default: 10)" }
          },
          required: ["query"]
        }
      }
    ]
  };
});

// 声明资源能力
server.setRequestHandler(ListResourcesRequestSchema, async () => {
  return {
    resources: [
      {
        uri: "kb://documents/recent",
        name: "Recent Documents",
        mimeType: "application/json"
      }
    ]
  };
});

2.2 传输层抽象

MCP 协议设计了可插拔的传输层,当前主要支持两种:

  • stdio 传输: 本地进程间通信,适合 CLI 工具和本地开发
  • SSE (Server-Sent Events) over HTTP: 远程服务通信,适合云端部署

这种抽象允许同一份 MCP Server 代码在不同传输层间切换,无需重写业务逻辑:

// 传输层可插拔设计
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";

class MyMcpServer {
  private server: McpServer;
  
  async connectStdio() {
    const transport = new StdioServerTransport();
    await this.server.connect(transport);
    console.error("MCP server running on stdio"); // stderr 用于调试日志
  }
  
  async connectSSE(app: Express) {
    const transport = new SSEServerTransport("/messages", response);
    await this.server.connect(transport);
  }
}

三大核心原语

MCP 定义了三个核心能力原语,每个都有独立的发现、调用和通知机制:

3.1 Tools (工具)

Tools 是 MCP 最核心的概念——代表 AI Agent 可以执行的操作。与 Function Calling 不同,Tools 的结果可以触发更复杂的交互流程:

// 带进度报告和取消机制的工具实现
server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
  const { name, arguments: args } = request.params;
  
  if (name === "analyze_codebase") {
    const { path, depth } = args;
    
    // 报告进度
   extra.onprogress = (progress: number, total?: number) => {
      server.notification({
        method: "notifications/progress",
        params: {
          progressToken: extra._meta?.progressToken,
          progress: progress,
          total: total
        }
      });
    };
    
    // 支持取消
    extra.signal.addEventListener("abort", () => {
      console.log("Analysis cancelled by client");
      // 清理资源
    });
    
    const result = await analyzeCodebase(path, depth, extra.onprogress);
    return {
      content: [
        {
          type: "text",
          text: JSON.stringify(result, null, 2)
        },
        {
          type: "resource",
          resource: {
            uri: "kb://analysis-reports/latest",
            mimeType: "application/json",
            text: JSON.stringify(result)
          }
        }
      ]
    };
  }
  
  throw new Error(`Unknown tool: ${name}`);
});

3.2 Resources (资源)

Resources 提供了一种让 AI Agent 访问外部数据的标准化方式,类似文件系统的 URI 寻址:

// 资源订阅机制——Server 可主动通知数据变更
class DatabaseMcpServer {
  private resourceSubscriptions = new Set<string>();
  
  setupResourceHandlers() {
    // 资源列表
    server.setRequestHandler(ListResourcesRequestSchema, async () => {
      const tables = await db.query("SHOW TABLES");
      return {
        resources: tables.map((t: any) => ({
          uri: `postgres://localhost/${t.table_name}`,
          name: `Table: ${t.table_name}`,
          description: `PostgreSQL table with ${t.row_count} rows`,
          mimeType: "application/json"
        }))
      };
    });
    
    // 资源读取
    server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
      const { uri } = req.params;
      const tableName = this.parseTableName(uri);
      const rows = await db.query(`SELECT * FROM ${tableName} LIMIT 100`);
      return {
        contents: [{
          uri,
          mimeType: "application/json",
          text: JSON.stringify(rows, null, 2)
        }]
      };
    });
    
    // 订阅管理
    server.setRequestHandler(SetResourcesRequestSchema, async (req) => {
      const { uri, subscribe } = req.params;
      if (subscribe) {
        this.resourceSubscriptions.add(uri);
        this.startWatching(uri);
      } else {
        this.resourceSubscriptions.delete(uri);
      }
    });
  }
  
  // 数据变更时通知 Client
  async notifyResourceChange(uri: string, newData: any) {
    if (this.resourceSubscriptions.has(uri)) {
      await server.notification({
        method: "notifications/resources/updated",
        params: { uri }
      });
    }
  }
}

3.3 Prompts (提示模板)

Prompts 允许 Server 向 Client 提供预定义的交互模板,实现跨工具的组合工作流:

// 提示模板——可参数化的多步骤工作流
server.setRequestHandler(ListPromptsRequestSchema, async () => {
  return {
    prompts: [
      {
        name: "code_review",
        description: "Comprehensive code review with style, security, and performance checks",
        arguments: [
          {
            name: "file_path",
            description: "Path to the code file to review",
            required: true
          },
          {
            name: "language",
            description: "Programming language",
            required: false
          }
        ]
      },
      {
        name: "incident_response",
        description: "Standard incident response workflow with log analysis and root cause",
        arguments: [
          { name: "service_name", required: true },
          { name: "time_window", required: false, default: "1h" }
        ]
      }
    ]
  };
});

生产环境工程实践

4.1 安全:Capabilities 与权限模型

MCP 在设计上天然支持最小权限原则。Server 通过 capabilities 声明暴露能力,Client 可选择性启用:

// 能力协商示意
const capabilities = {
  tools: {
    // 声明此 Server 的工具行为特性
    listChanged: true  // 支持工具的动态增删通知
  },
  resources: {
    subscribe: true,   // 支持资源订阅
    listChanged: true
  },
  prompts: {
    listChanged: true
  },
  logging: {}          // 支持日志输出到 Client
};

工程实践建议:

  • 沙箱隔离: 工具执行应在独立进程中,限制文件系统、网络访问范围
  • 参数校验: 使用 JSON Schema 严格校验输入,Zod 运行时验证做二道防线
  • 速率限制: 防止 AI Agent 触发工具调用风暴,建议实现 token bucket 限流
  • 审计日志: 所有工具调用应记录完整的 call chain,便于事后分析

4.2 可观测性:OpenTelemetry 集成

MCP Server 的可观测性对生产环境至关重要——你需要知道 AI Agent 调用了什么、花了多久、返回了什么:

import { NodeTracerProvider } from "@opentelemetry/node";
import { registerInstrumentations } from "@opentelemetry/instrumentation";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

// MCP Server 链路追踪包装
export function withTelemetry(server: McpServer): McpServer {
  const tracer = trace.getTracer("mcp-server");
  
  const originalHandler = server.setRequestHandler.bind(server);
  
  server.setRequestHandler = function(schema: any, handler: any) {
    return originalHandler(schema, async (request: any, extra: any) => {
      return tracer.startActiveSpan(`mcp.tool.${request.params.name}`, {
        attributes: {
          "mcp.tool.name": request.params.name,
          "mcp.tool.input_size": JSON.stringify(request.params.arguments).length,
          "mcp.client.id": extra.sessionId || "unknown"
        }
      }, async (span) => {
        try {
          const result = await handler(request, extra);
          span.setAttribute("mcp.tool.success", true);
          span.setAttribute(
            "mcp.tool.output_size",
            JSON.stringify(result).length
          );
          span.setStatus({ code: SpanStatusCode.OK });
          return result;
        } catch (error) {
          span.setStatus({
            code: SpanStatusCode.ERROR,
            message: (error as Error).message
          });
          span.recordException(error as Error);
          throw error;
        } finally {
          span.end();
        }
      });
    });
  };
  
  return server;
}

4.3 容错:超时、重试与降级

AI Agent 调用外部工具时,网络抖动和下游故障是常态。生产级 MCP Server 需要内置容错机制:

interface ResilientToolConfig {
  timeout: number;          // 单次调用超时(ms)
  maxRetries: number;       // 最大重试次数
  retryBackoff: 'fixed' | 'exponential';
  circuitBreaker: {
    failureThreshold: number;  // 熔断触发失败次数
    resetTimeout: number;      // 熔断后尝试恢复时间(ms)
  };
  fallback?: (args: any) => ToolResult;  // 降级响应
}

class ResilientToolWrapper {
  private circuitState: 'closed' | 'open' | 'half-open' = 'closed';
  private failures = 0;
  private lastFailureTime = 0;
  
  constructor(
    private toolName: string,
    private handler: ToolHandler,
    private config: ResilientToolConfig
  ) {}
  
  async execute(args: any): Promise<ToolResult> {
    // 熔断器检查
    if (this.circuitState === 'open') {
      if (Date.now() - this.lastFailureTime > this.config.circuitBreaker.resetTimeout) {
        this.circuitState = 'half-open';
      } else {
        return this.fallbackResponse(args, 'Circuit breaker is open');
      }
    }
    
    let lastError: Error | undefined;
    
    for (let attempt = 0; attempt <= this.config.maxRetries; attempt++) {
      try {
        const result = await Promise.race([
          this.handler(args),
          timeout(this.config.timeout)
        ]);
        
        // 成功后重置熔断器
        if (this.circuitState === 'half-open') {
          this.failures = 0;
          this.circuitState = 'closed';
        }
        
        return result;
      } catch (error) {
        lastError = error as Error;
        
        // 退避重试
        if (attempt < this.config.maxRetries) {
          const delay = this.calculateBackoff(attempt);
          await sleep(delay);
        }
      }
    }
    
    // 累计失败,触发熔断器
    this.failures++;
    this.lastFailureTime = Date.now();
    if (this.failures >= this.config.circuitBreaker.failureThreshold) {
      this.circuitState = 'open';
    }
    
    return this.fallbackResponse(args, lastError?.message || 'Unknown error');
  }
  
  private fallbackResponse(args: any, reason: string): ToolResult {
    if (this.config.fallback) {
      return this.config.fallback(args);
    }
    return {
      content: [{
        type: "text",
        text: JSON.stringify({
          error: true,
          reason,
          suggestion: `Tool ${this.toolName} is temporarily unavailable. Try again later or use alternative tools.`
        })
      }],
      isError: true
    };
  }
  
  private calculateBackoff(attempt: number): number {
    if (this.config.retryBackoff === 'fixed') return 1000;
    return Math.min(1000 * Math.pow(2, attempt), 30000);
  }
}

function timeout(ms: number): Promise<never> {
  return new Promise((_, reject) => 
    setTimeout(() => reject(new Error(`Tool execution timed out after ${ms}ms`)), ms)
  );
}

4.4 扩展性:gRPC 传输与负载均衡

当 MCP Server 需要服务多个 AI Agent 或处理高并发时,需要考虑水平扩展。虽然 MCP 协议原生不定义集群方案,但可以在传输层之上构建:

// MCP-over-gRPC 架构示意
service McpBridge {
  // 双向流式通信
  rpc StreamRequests(stream ClientMessage) returns (stream ServerMessage);
  
  // 工具的批量发现
  rpc BatchListTools(BatchListRequest) returns (BatchListResponse) {}
}

message ToolResult {
  string request_id = 1;
  repeated ContentBlock content = 2;
  bool is_error = 3;
}

// 负载均衡:Nginx 对 MCP Server 反向代理
// nginx.conf 片段
upstream mcp_backend {
  least_conn;
  server mcp-1.internal:3000;
  server mcp-2.internal:3000;
  server mcp-3.internal:3000;
  keepalive 32;
}

server {
  listen 443 ssl http2;
  server_name mcp.example.com;
  
  location /sse {
    proxy_pass http://mcp_backend;
    proxy_http_version 1.1;
    proxy_set_header Connection "";
    proxy_buffering off;           // SSE 必须关闭缓冲
    proxy_read_timeout 3600s;      // 长连接超时
  }
  
  location /messages {
    proxy_pass http://mcp_backend;
    proxy_http_version 1.1;
  }
}

MCP vs 现有生态:不是替代而是补充

5.1 与传统 RPA/Workflow 的对比

维度RPA/WorkflowMCP
编排主体预定义流程图LLM 动态决策
灵活性低(固定路径)高(运行时适配)
适用场景高频、确定性流程低频、需要判断力的任务
失败处理重试+人工LLM 自主重新规划

实战建议: MCP 和 RPA 不是替代关系。高频确定性操作(如每日报表生成)仍用 RPA;需要认知能力的工作(如代码审查、数据分析)则交给 MCP 驱动的 AI Agent。

5.2 MCP 与 A2A (Agent-to-Agent) 协议的关系

Google 提出的 A2A 协议关注 Agent 之间的协作,而 MCP 关注 Agent 与工具的交互。二者是互补的:

  • MCP: 解决"Agent 如何调用工具"(垂直方向)
  • A2A: 解决"Agent 如何协作"(水平方向)

一个合理的架构是:MCP 为 Agent 提供工具能力,A2A 为 Agent 提供协作能力。Agent 通过 MCP 操作数据库,通过 A2A 与其他 Agent 协商任务分配。

设计一个生产级 MCP Server:实战案例

以一个企业知识库 MCP Server 为例,展示完整的工程化实现:

// knowledge-base-server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { VectorStore } from "./vector-store";
import { withTelemetry, withResilience } from "./middleware";

const vectorStore = new VectorStore({
  endpoint: process.env.VECTOR_DB_ENDPOINT!,
  indexName: "knowledge-base",
  embeddingModel: "text-embedding-3-small"
});

const server = new McpServer({
  name: "enterprise-kb",
  version: "2.0.0"
});

// 工具:语义搜索
server.registerTool(
  "semantic_search",
  {
    title: "Semantic Search",
    description: "Search enterprise knowledge base using semantic similarity",
    inputSchema: {
      query: z.string().describe("Natural language search query"),
      topK: z.number().min(1).max(50).default(10),
      filters: z.object({
        department: z.string().enum(["engineering", "sales", "hr", "finance"]).optional(),
        dateRange: z.object({
          from: z.string().datetime(),
          to: z.string().datetime()
        }).optional()
      }).optional()
    }
  },
  withResilience(
    withTelemetry(async ({ query, topK, filters }) => {
      const results = await vectorStore.search(query, {
        topK,
        filter: filters ? buildFilterExpression(filters) : undefined,
        includeMetadata: true
      });
      
      return {
        content: [{
          type: "text" as const,
          text: results.map(r => 
            `## ${r.metadata.title}\n` +
            `**Score:** ${(r.score * 100).toFixed(1)}%\n` +
            `**Source:** ${r.metadata.source}\n` +
            `**Updated:** ${r.metadata.updatedAt}\n\n` +
            `${r.text.substring(0, 300)}...`
          ).join("\n\n---\n\n")
        }]
      };
    }),
    { timeout: 10000, maxRetries: 2 }
  )
);

// 工具:实时文档获取
server.registerTool(
  "fetch_document",
  {
    title: "Fetch Document",
    description: "Retrieve full document content by ID or URI",
    inputSchema: {
      docId: z.string(),
      format: z.enum(["markdown", "html", "json"]).default("markdown")
    }
  },
  withResilience(
    withTelemetry(async ({ docId, format }) => {
      const doc = await vectorStore.getDocument(docId, { format });
      return {
        content: [
          { type: "text" as const, text: doc.content },
          {
            type: "resource" as const,
            resource: {
              uri: `kb://documents/${docId}`,
              mimeType: format === 'html' ? 'text/html' : 'text/markdown',
              text: doc.content
            }
          }
        ]
      };
    }),
    { timeout: 5000, maxRetries: 3 }
  )
);

// 资源:动态文档列表
server.registerResource(
  "documents",
  "kb://documents/recent",
  {
    title: "Recent Documents",
    description: "Last 50 updated documents in the knowledge base",
    mimeType: "application/json"
  },
  async (uri) => {
    const docs = await vectorStore.getRecentDocuments(50);
    return {
      contents: [{
        uri: uri.href,
        mimeType: "application/json",
        text: JSON.stringify(docs, null, 2)
      }]
    };
  }
);

// 启动服务
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error("Enterprise KB MCP Server ready");
}

main().catch(console.error);

陷阱与最佳实践总结

基于多个生产环境部署经验,以下是一些值得注意的陷阱:

  1. 过度暴露工具能力: 不要把所有内部 API 都包成 MCP 工具。AI Agent 会从工具描述中选择,描述模糊会导致误选。遵循"最少必要工具"原则
  2. 忽略上下文膨胀: MCP 工具返回结果直接进入 LLM 上下文窗口。对大结果集做摘要,而不是返回原始数据。200K token 的文档会挤占推理空间
  3. 同步调用链过深: Tool A → Tool B → Tool C 的串行调用会让用户等待过久。尽量设计可并行的独立工具
  4. 错误信息不够 LLM 友好: 直接抛 HTTP 500 没用,要告诉 AI Agent 哪里出错了、如何修正、有什么替代方案
  5. 忽视幂等性: AI Agent 可能重试同一个工具调用(特别是超时重试)。写操作必须实现幂等键机制
  6. 未考虑模型工具使用限制: 大多数 LLM 有单次 turn 的工具数量上限(通常 20-30 个)。超过此限制会导致工具调用被静默丢弃

展望

MCP 协议目前仍处于早期阶段(2024 末发布 v1.0),未来值得关注的发展方向:

  • 复合原语 (Composables): 多个 MCP Server 声明能力依赖关系,Client 自动编排调用
  • 流式结果 (Streaming Results): 支持工具执行过程中持续推送部分结果
  • 联邦能力发现 (Federated Discovery): 跨组织的 MCP Server 能力注册与查询
  • 状态化会话 (Stateful Sessions): 工具调用间的状态保持与恢复

作为工程师,现在投入 MCP 生态建设是明智的选择。无论最终哪个协议胜出,"AI Agent 需要标准化的工具接口"这个趋势已经确立。理解并实践 MCP,就是在为未来 AI-native 应用架构构建认知基础。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部