MCP

MCP Client 实现详解:构建智能 Agent 的工具管理中枢

深入解析 MCP Client 的实现原理,包括 Server 连接管理、工具发现、调用路由和错误处理的完整实现方案。

MCP Client 是 AI Agent 与 MCP Server 之间的桥梁——它负责发现工具、路由调用、处理错误。一个健壮的 Client 实现是构建可靠 Agent 系统的基础。

Client 的职责

MCP Client 承担以下核心职责:

  1. 连接管理——建立和维护与多个 Server 的连接
  2. 能力发现——查询每个 Server 提供的 Tools、Resources 和 Prompts
  3. 调用路由——将 LLM 的工具调用请求路由到正确的 Server
  4. 错误处理——处理连接断开、调用失败等异常情况
  5. 生命周期管理——Server 的启动、监控和关闭

基础 Client 实现

import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';

interface ServerConfig {
  name: string;
  command: string;
  args: string[];
  env?: Record<string, string>;
}

class MCPClientManager {
  private clients: Map<string, Client> = new Map();
  private tools: Map<string, string> = new Map();  // tool name -> server name

  async addServer(config: ServerConfig): Promise<void> {
    const client = new Client({
      name: config.name,
      version: '1.0.0',
    });

    const transport = new StdioClientTransport({
      command: config.command,
      args: config.args,
      env: config.env,
    });

    await client.connect(transport);
    this.clients.set(config.name, client);

    // 发现并注册工具
    const { tools } = await client.listTools();
    for (const tool of tools) {
      this.tools.set(tool.name, config.name);
    }

    console.error(`[MCP] 已连接 ${config.name},注册 ${tools.length} 个工具`);
  }

  async callTool(name: string, args: Record<string, any>): Promise<any> {
    const serverName = this.tools.get(name);
    if (!serverName) {
      throw new Error(`工具 ${name} 未找到`);
    }

    const client = this.clients.get(serverName);
    if (!client) {
      throw new Error(`Server ${serverName} 未连接`);
    }

    return await client.callTool({ name, arguments: args });
  }

  getAllTools(): Array<{ name: string; description: string; inputSchema: any }> {
    const tools: any[] = [];
    // 需要缓存工具定义
    return tools;
  }

  async cleanup(): Promise<void> {
    for (const [name, client] of this.clients) {
      try {
        await client.close();
        console.error(`[MCP] 已断开 ${name}`);
      } catch (error) {
        console.error(`[MCP] 断开 ${name} 失败:`, error);
      }
    }
    this.clients.clear();
    this.tools.clear();
  }
}

完整的工具缓存实现

interface CachedTool {
  name: string;
  description: string;
  inputSchema: any;
  serverName: string;
}

class MCPClientManager {
  private clients: Map<string, Client> = new Map();
  private toolCache: Map<string, CachedTool> = new Map();

  async addServer(config: ServerConfig): Promise<void> {
    const client = new Client({ name: config.name, version: '1.0.0' });
    await client.connect(new StdioClientTransport({
      command: config.command,
      args: config.args,
      env: config.env,
    }));

    this.clients.set(config.name, client);

    const { tools } = await client.listTools();
    for (const tool of tools) {
      this.toolCache.set(tool.name, {
        name: tool.name,
        description: tool.description || '',
        inputSchema: tool.inputSchema,
        serverName: config.name,
      });
    }
  }

  getToolsForLLM(): Array<{
    name: string;
    description: string;
    input_schema: any;
  }> {
    return Array.from(this.toolCache.values()).map(tool => ({
      name: tool.name,
      description: tool.description,
      input_schema: tool.inputSchema,
    }));
  }

  async callTool(name: string, args: Record<string, any>): Promise<any> {
    const cached = this.toolCache.get(name);
    if (!cached) {
      throw new Error(`Tool ${name} not found`);
    }

    const client = this.clients.get(cached.serverName);
    if (!client) {
      throw new Error(`Server ${cached.serverName} not connected`);
    }

    return await client.callTool({ name, arguments: args });
  }
}

与 LLM 集成

Claude API 集成

import Anthropic from '@anthropic-ai/sdk';

class ClaudeAgent {
  private claude = new Anthropic();
  private mcpManager: MCPClientManager;

  constructor() {
    this.mcpManager = new MCPClientManager();
  }

  async chat(message: string): Promise<string> {
    const tools = this.mcpManager.getToolsForLLM();
    const messages: any[] = [{ role: 'user', content: message }];

    while (true) {
      const response = await this.claude.messages.create({
        model: 'claude-sonnet-4-20250514',
        max_tokens: 4096,
        tools,
        messages,
      });

      if (response.stop_reason === 'tool_use') {
        const toolResults = [];

        for (const block of response.content) {
          if (block.type === 'tool_use') {
            try {
              const result = await this.mcpManager.callTool(
                block.name,
                block.input
              );
              toolResults.push({
                type: 'tool_result',
                tool_use_id: block.id,
                content: result.content.map((c: any) => c.text || '').join('\n'),
              });
            } catch (error) {
              toolResults.push({
                type: 'tool_result',
                tool_use_id: block.id,
                content: `错误:${error.message}`,
                is_error: true,
              });
            }
          }
        }

        messages.push({ role: 'assistant', content: response.content });
        messages.push({ role: 'user', content: toolResults });
        continue;
      }

      return response.content
        .filter((b): b is any => b.type === 'text')
        .map((b: any) => b.text)
        .join('');
    }
  }
}

连接健康监控

class HealthMonitor {
  private clients: Map<string, Client>;
  private healthStatus: Map<string, boolean> = new Map();

  constructor(clients: Map<string, Client>) {
    this.clients = clients;
  }

  startMonitoring(intervalMs: number = 30000) {
    setInterval(() => this.checkAll(), intervalMs);
  }

  private async checkAll() {
    for (const [name, client] of this.clients) {
      try {
        // 尝试列出工具来验证连接
        await client.listTools();
        this.healthStatus.set(name, true);
      } catch {
        this.healthStatus.set(name, false);
        console.error(`[Health] Server ${name} 不可用`);
      }
    }
  }

  isHealthy(serverName: string): boolean {
    return this.healthStatus.get(serverName) ?? false;
  }
}

常见问题(FAQ)

Client 应该在什么时候初始化 Server 连接?

在应用启动时初始化所有连接。如果某个 Server 连接失败,记录错误但不阻塞其他 Server 的初始化。

如何处理 Server 崩溃?

实现重连机制——检测到连接断开后,等待一段时间后尝试重新连接。对于关键 Server,可以配置多个实例进行故障转移。

一个 Client 可以连接多少个 Server?

没有硬性限制,但建议不超过 20 个。过多的连接会增加管理复杂度和资源消耗。

总结

MCP Client 是 Agent 系统的中枢——它管理着与所有 Server 的连接,将 LLM 的意图转化为具体的工具调用。一个健壮的 Client 实现需要关注连接管理、工具缓存、错误处理和健康监控四个方面。