mcp-client-builder

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

MCP Client Development Guide

MCP客户端开发指南

Core Mental Model

核心思维模型

Host Application (user-facing app like Claude Desktop)
  └─> Creates 1+ MCP Clients (protocol components)
       └─> Each Client connects to exactly 1 Server (1:1 mapping)
            └─> Server exposes Tools/Resources/Prompts
                 └─> LLM decides which to use
                      └─> Client executes, returns results
Key Principle: Client = stateful messenger, NOT decision maker. LLM chooses tools, client facilitates execution.

Host Application (user-facing app like Claude Desktop)
  └─> Creates 1+ MCP Clients (protocol components)
       └─> Each Client connects to exactly 1 Server (1:1 mapping)
            └─> Server exposes Tools/Resources/Prompts
                 └─> LLM decides which to use
                      └─> Client executes, returns results
核心原则:客户端是有状态的信使,而非决策者。由LLM选择工具,客户端负责执行。

Development Workflow

开发流程

Phase 1: Architecture Design

阶段1:架构设计

1.1 Determine Requirements

1.1 确定需求

Client Capabilities (what client provides TO servers):
  • sampling
    - Allow server to request LLM completions
  • roots
    - Declare filesystem boundaries
  • elicitation
    - Allow server to request user input
Expected Server Capabilities (what servers provide TO client):
  • tools
    - Execute operations
  • resources
    - Access data
  • prompts
    - Use templates
客户端能力(客户端向服务器提供的功能):
  • sampling
    - 允许服务器请求LLM补全
  • roots
    - 声明文件系统边界
  • elicitation
    - 允许服务器请求用户输入
预期服务器能力(服务器向客户端提供的功能):
  • tools
    - 执行操作
  • resources
    - 访问数据
  • prompts
    - 使用模板

1.2 Select Transport Strategy

1.2 选择传输策略

TransportUse WhenProsCons
stdioServer on same machineFast, simpleLocal only
HTTP StreamRemote server, modernBidirectional, sessionsMore complex
SSELegacy compatibilitySimpleUnidirectional
Decision Rule: stdio for local development/testing, HTTP Stream for production remote servers.
传输方式使用场景优势劣势
stdio服务器在本地机器快速、简单仅支持本地
HTTP Stream远程服务器、现代场景双向通信、支持会话复杂度更高
SSE遗留系统兼容简单单向通信
决策规则:本地开发/测试使用stdio,生产环境远程服务器使用HTTP Stream。

1.3 Plan Connection Management

1.3 规划连接管理

1:1 Mapping Pattern:
typescript
// CORRECT: One client per server
const weatherClient = new Client(/* weather server config */);
const calendarClient = new Client(/* calendar server config */);

// INCORRECT: One client trying to talk to multiple servers
const multiClient = new Client(/* won't work */);
Host Manages Multiple Clients:
typescript
class HostApplication {
  private clients: Map<string, Client> = new Map();

  connectToServer(serverConfig) {
    const client = new Client(config);
    this.clients.set(serverConfig.id, client);
  }
}

1:1映射模式:
typescript
// CORRECT: One client per server
const weatherClient = new Client(/* weather server config */);
const calendarClient = new Client(/* calendar server config */);

// INCORRECT: One client trying to talk to multiple servers
const multiClient = new Client(/* won't work */);
宿主应用管理多客户端:
typescript
class HostApplication {
  private clients: Map<string, Client> = new Map();

  connectToServer(serverConfig) {
    const client = new Client(config);
    this.clients.set(serverConfig.id, client);
  }
}

Phase 2: Implementation

阶段2:实现

2.1 Project Structure

2.1 项目结构

TypeScript:
src/
  client.ts          # Main Client class
  transports/        # stdio, http, sse implementations
  types.ts           # Zod schemas
  errors.ts          # Error handling
  session.ts         # Session management
Python:
client.py            # Main Client class
transports/          # stdio, http, sse
schemas.py           # Pydantic models
errors.py            # Error handling
session.py           # Session management
TypeScript:
src/
  client.ts          # Main Client class
  transports/        # stdio, http, sse implementations
  types.ts           # Zod schemas
  errors.ts          # Error handling
  session.ts         # Session management
Python:
client.py            # Main Client class
transports/          # stdio, http, sse
schemas.py           # Pydantic models
errors.py            # Error handling
session.py           # Session management

2.2 Connection Lifecycle Implementation

2.2 连接生命周期实现

Three-Phase Pattern:
typescript
// Phase 1: Initialize
async connect(transport: Transport) {
  await transport.connect();

  const initResponse = await this.sendRequest({
    method: "initialize",
    params: {
      protocolVersion: "2025-06-18",
      capabilities: {
        sampling: {},          // If client supports sampling
        roots: { listChanged: true },  // If client supports roots
        elicitation: {}        // If client supports elicitation
      },
      clientInfo: { name: "my-client", version: "1.0.0" }
    }
  });

  this.serverCapabilities = initResponse.capabilities;

  // Phase 2: Confirm
  await this.sendNotification({ method: "initialized" });

  // Phase 3: Ready for operations
}
Server Capabilities Extraction:
typescript
interface ServerCapabilities {
  tools?: { listChanged?: boolean };
  resources?: { subscribe?: boolean, listChanged?: boolean };
  prompts?: { listChanged?: boolean };
  logging?: {};
}
三阶段模式:
typescript
// Phase 1: Initialize
async connect(transport: Transport) {
  await transport.connect();

  const initResponse = await this.sendRequest({
    method: "initialize",
    params: {
      protocolVersion: "2025-06-18",
      capabilities: {
        sampling: {},          // If client supports sampling
        roots: { listChanged: true },  // If client supports roots
        elicitation: {}        // If client supports elicitation
      },
      clientInfo: { name: "my-client", version: "1.0.0" }
    }
  });

  this.serverCapabilities = initResponse.capabilities;

  // Phase 2: Confirm
  await this.sendNotification({ method: "initialized" });

  // Phase 3: Ready for operations
}
服务器能力提取:
typescript
interface ServerCapabilities {
  tools?: { listChanged?: boolean };
  resources?: { subscribe?: boolean, listChanged?: boolean };
  prompts?: { listChanged?: boolean };
  logging?: {};
}

2.3 Transport Abstraction

2.3 传输抽象

Interface Pattern:
typescript
interface Transport {
  connect(): Promise<void>;
  send(message: JSONRPCMessage): Promise<void>;
  receive(): AsyncIterator<JSONRPCMessage>;
  close(): Promise<void>;
}

class StdioTransport implements Transport { /* ... */ }
class HTTPStreamTransport implements Transport { /* ... */ }
class SSETransport implements Transport { /* ... */ }
Usage:
typescript
const transport = config.remote
  ? new HTTPStreamTransport(config.url)
  : new StdioTransport(config.command, config.args);

await client.connect(transport);
接口模式:
typescript
interface Transport {
  connect(): Promise<void>;
  send(message: JSONRPCMessage): Promise<void>;
  receive(): AsyncIterator<JSONRPCMessage>;
  close(): Promise<void>;
}

class StdioTransport implements Transport { /* ... */ }
class HTTPStreamTransport implements Transport { /* ... */ }
class SSETransport implements Transport { /* ... */ }
使用方式:
typescript
const transport = config.remote
  ? new HTTPStreamTransport(config.url)
  : new StdioTransport(config.command, config.args);

await client.connect(transport);

2.4 Tool Orchestration Pattern

2.4 工具编排模式

Critical: LLM Decides, Client Executes:
typescript
async processUserQuery(query: string): Promise<string> {
  // 1. Get available tools from server
  const toolsResponse = await this.request({ method: "tools/list" });
  const tools = toolsResponse.tools;

  // 2. Present tools to LLM in structured format
  const llmTools = tools.map(tool => ({
    name: tool.name,
    description: tool.description,
    input_schema: tool.inputSchema
  }));

  // 3. LLM DECIDES which tools to use
  const llmResponse = await anthropic.messages.create({
    model: "claude-3-5-sonnet-20241022",
    messages: [{ role: "user", content: query }],
    tools: llmTools  // LLM sees available tools
  });

  // 4. Execute LLM-requested tool calls
  for (const toolUse of llmResponse.content) {
    if (toolUse.type === 'tool_use') {
      const result = await this.request({
        method: "tools/call",
        params: {
          name: toolUse.name,
          arguments: toolUse.input
        }
      });

      // 5. Return results to LLM for final response
      // ... continuation logic
    }
  }
}
Key Point: Client never chooses tools. Client only:
  1. Discovers available tools
  2. Presents them to LLM
  3. Executes LLM's choices
  4. Returns results to LLM
关键规则:LLM决策,客户端执行:
typescript
async processUserQuery(query: string): Promise<string> {
  // 1. Get available tools from server
  const toolsResponse = await this.request({ method: "tools/list" });
  const tools = toolsResponse.tools;

  // 2. Present tools to LLM in structured format
  const llmTools = tools.map(tool => ({
    name: tool.name,
    description: tool.description,
    input_schema: tool.inputSchema
  }));

  // 3. LLM DECIDES which tools to use
  const llmResponse = await anthropic.messages.create({
    model: "claude-3-5-sonnet-20241022",
    messages: [{ role: "user", content: query }],
    tools: llmTools  // LLM sees available tools
  });

  // 4. Execute LLM-requested tool calls
  for (const toolUse of llmResponse.content) {
    if (toolUse.type === 'tool_use') {
      const result = await this.request({
        method: "tools/call",
        params: {
          name: toolUse.name,
          arguments: toolUse.input
        }
      });

      // 5. Return results to LLM for final response
      // ... continuation logic
    }
  }
}
核心要点:客户端从不选择工具。客户端仅需:
  1. 发现可用工具
  2. 以结构化格式呈现给LLM
  3. 执行LLM选择的工具
  4. 将结果返回给LLM

2.5 Error Handling Strategy

2.5 错误处理策略

JSON-RPC Error Codes:
typescript
enum ErrorCode {
  ParseError = -32700,      // Invalid JSON
  InvalidRequest = -32600,  // Malformed request
  MethodNotFound = -32601,  // Tool doesn't exist
  InvalidParams = -32602,   // Wrong arguments
  InternalError = -32603,   // Server failure

  // Custom range: -32000 to -32099
  Timeout = -32001,
  ResourceNotFound = -32002,
  Unauthorized = -32003
}
Error Classification & Retry Logic:
typescript
class ErrorHandler {
  async handleError(error: JSONRPCError): Promise<'retry' | 'fail' | 'escalate'> {
    // Transient errors: retry with exponential backoff
    if ([ErrorCode.InternalError, ErrorCode.Timeout].includes(error.code)) {
      return 'retry';
    }

    // Permanent errors: fail immediately
    if ([ErrorCode.MethodNotFound, ErrorCode.InvalidParams].includes(error.code)) {
      return 'fail';
    }

    // Security errors: escalate to user
    if (error.code === ErrorCode.Unauthorized) {
      return 'escalate';
    }
  }
}
Retry Pattern:
typescript
async executeWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const action = await this.errorHandler.handleError(error);

      if (action === 'retry' && attempt < maxRetries - 1) {
        await sleep(baseDelay * Math.pow(2, attempt));
        continue;
      }
      throw error;
    }
  }
}
JSON-RPC错误码:
typescript
enum ErrorCode {
  ParseError = -32700,      // Invalid JSON
  InvalidRequest = -32600,  // Malformed request
  MethodNotFound = -32601,  // Tool doesn't exist
  InvalidParams = -32602,   // Wrong arguments
  InternalError = -32603,   // Server failure

  // Custom range: -32000 to -32099
  Timeout = -32001,
  ResourceNotFound = -32002,
  Unauthorized = -32003
}
错误分类与重试逻辑:
typescript
class ErrorHandler {
  async handleError(error: JSONRPCError): Promise<'retry' | 'fail' | 'escalate'> {
    // Transient errors: retry with exponential backoff
    if ([ErrorCode.InternalError, ErrorCode.Timeout].includes(error.code)) {
      return 'retry';
    }

    // Permanent errors: fail immediately
    if ([ErrorCode.MethodNotFound, ErrorCode.InvalidParams].includes(error.code)) {
      return 'fail';
    }

    // Security errors: escalate to user
    if (error.code === ErrorCode.Unauthorized) {
      return 'escalate';
    }
  }
}
重试模式:
typescript
async executeWithRetry<T>(
  fn: () => Promise<T>,
  maxRetries = 3,
  baseDelay = 1000
): Promise<T> {
  for (let attempt = 0; attempt < maxRetries; attempt++) {
    try {
      return await fn();
    } catch (error) {
      const action = await this.errorHandler.handleError(error);

      if (action === 'retry' && attempt < maxRetries - 1) {
        await sleep(baseDelay * Math.pow(2, attempt));
        continue;
      }
      throw error;
    }
  }
}

2.6 Session Management (HTTP Transport)

2.6 会话管理(HTTP传输)

Session ID Propagation:
typescript
class HTTPStreamTransport {
  private sessionId?: string;

  async send(message: JSONRPCMessage) {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json'
    };

    // Propagate session ID bidirectionally
    if (this.sessionId) {
      headers['Mcp-Session-Id'] = this.sessionId;
    }

    const response = await fetch(this.url, {
      method: 'POST',
      headers,
      body: JSON.stringify(message)
    });

    // Extract session ID from response
    const receivedSessionId = response.headers.get('Mcp-Session-Id');
    if (receivedSessionId) {
      this.sessionId = receivedSessionId;
    }
  }
}
Session State Management:
typescript
interface SessionState {
  id: string;
  lastActivity: Date;
  conversationHistory: Message[];
  resources: Map<string, ResourceState>;
}

会话ID传播:
typescript
class HTTPStreamTransport {
  private sessionId?: string;

  async send(message: JSONRPCMessage) {
    const headers: Record<string, string> = {
      'Content-Type': 'application/json'
    };

    // Propagate session ID bidirectionally
    if (this.sessionId) {
      headers['Mcp-Session-Id'] = this.sessionId;
    }

    const response = await fetch(this.url, {
      method: 'POST',
      headers,
      body: JSON.stringify(message)
    });

    // Extract session ID from response
    const receivedSessionId = response.headers.get('Mcp-Session-Id');
    if (receivedSessionId) {
      this.sessionId = receivedSessionId;
    }
  }
}
会话状态管理:
typescript
interface SessionState {
  id: string;
  lastActivity: Date;
  conversationHistory: Message[];
  resources: Map<string, ResourceState>;
}

Phase 3: Security Implementation

阶段3:安全实现

3.1 Multi-Layer Defense

3.1 多层防御

Layer 1: Network Security:
typescript
class SecureTransport {
  validateTLS(url: string) {
    if (!url.startsWith('https://') && !this.isLocalhost(url)) {
      throw new Error('Remote servers must use HTTPS');
    }
  }

  validateOrigin(origin: string) {
    // DNS rebinding protection
    if (!this.allowedOrigins.includes(origin)) {
      throw new Error(`Untrusted origin: ${origin}`);
    }
  }
}
Layer 2: Authentication:
typescript
interface AuthProvider {
  authenticate(): Promise<Credentials>;
  refresh(credentials: Credentials): Promise<Credentials>;
}

class OAuth2PKCEProvider implements AuthProvider {
  async authenticate(): Promise<Credentials> {
    const codeVerifier = generateCodeVerifier();
    const codeChallenge = await generateCodeChallenge(codeVerifier);

    // OAuth 2.1 with PKCE flow
    const authUrl = buildAuthUrl({ challenge: codeChallenge });
    const code = await getUserConsent(authUrl);

    return await exchangeCodeForToken(code, codeVerifier);
  }
}
Layer 3: Authorization:
typescript
class AuthorizationManager {
  async checkPermissions(toolName: string, params: any): Promise<boolean> {
    const tool = await this.getToolMetadata(toolName);

    // Destructive operations require explicit user consent
    if (tool.destructiveHint === true) {
      return await this.requestUserApproval(
        `Allow ${toolName}? This will modify data.`
      );
    }

    // Check scopes
    const requiredScopes = tool.requiredScopes || [];
    return this.hasScopes(requiredScopes);
  }
}
Layer 4: Validation:
typescript
async callTool(name: string, args: unknown) {
  // 1. Schema validation
  const tool = await this.getTool(name);
  const validatedArgs = tool.inputSchema.parse(args);  // Zod/Pydantic

  // 2. Sanitization
  const sanitized = sanitizeInputs(validatedArgs);

  // 3. Authorization check
  const authorized = await this.authz.checkPermissions(name, sanitized);
  if (!authorized) throw new UnauthorizedError();

  // 4. Execute
  return await this.executeToolCall(name, sanitized);
}
第一层:网络安全:
typescript
class SecureTransport {
  validateTLS(url: string) {
    if (!url.startsWith('https://') && !this.isLocalhost(url)) {
      throw new Error('Remote servers must use HTTPS');
    }
  }

  validateOrigin(origin: string) {
    // DNS rebinding protection
    if (!this.allowedOrigins.includes(origin)) {
      throw new Error(`Untrusted origin: ${origin}`);
    }
  }
}
第二层:身份认证:
typescript
interface AuthProvider {
  authenticate(): Promise<Credentials>;
  refresh(credentials: Credentials): Promise<Credentials>;
}

class OAuth2PKCEProvider implements AuthProvider {
  async authenticate(): Promise<Credentials> {
    const codeVerifier = generateCodeVerifier();
    const codeChallenge = await generateCodeChallenge(codeVerifier);

    // OAuth 2.1 with PKCE flow
    const authUrl = buildAuthUrl({ challenge: codeChallenge });
    const code = await getUserConsent(authUrl);

    return await exchangeCodeForToken(code, codeVerifier);
  }
}
第三层:授权:
typescript
class AuthorizationManager {
  async checkPermissions(toolName: string, params: any): Promise<boolean> {
    const tool = await this.getToolMetadata(toolName);

    // Destructive operations require explicit user consent
    if (tool.destructiveHint === true) {
      return await this.requestUserApproval(
        `Allow ${toolName}? This will modify data.`
      );
    }

    // Check scopes
    const requiredScopes = tool.requiredScopes || [];
    return this.hasScopes(requiredScopes);
  }
}
第四层:验证:
typescript
async callTool(name: string, args: unknown) {
  // 1. Schema validation
  const tool = await this.getTool(name);
  const validatedArgs = tool.inputSchema.parse(args);  // Zod/Pydantic

  // 2. Sanitization
  const sanitized = sanitizeInputs(validatedArgs);

  // 3. Authorization check
  const authorized = await this.authz.checkPermissions(name, sanitized);
  if (!authorized) throw new UnauthorizedError();

  // 4. Execute
  return await this.executeToolCall(name, sanitized);
}

3.2 Credential Management

3.2 凭证管理

NEVER:
typescript
// ❌ WRONG: Hardcoded credentials
const client = new Client({ apiKey: "sk-1234..." });

// ❌ WRONG: Environment variables (visible to process)
const client = new Client({ apiKey: process.env.API_KEY });
ALWAYS:
typescript
// ✅ CORRECT: OS keychain
import { getSecret } from '@keychain/secure-store';
const apiKey = await getSecret('mcp-server-credentials');

// ✅ CORRECT: Vault service
const credentials = await vault.getCredentials('mcp-server');

严禁:
typescript
// ❌ WRONG: Hardcoded credentials
const client = new Client({ apiKey: "sk-1234..." });

// ❌ WRONG: Environment variables (visible to process)
const client = new Client({ apiKey: process.env.API_KEY });
正确做法:
typescript
// ✅ CORRECT: OS keychain
import { getSecret } from '@keychain/secure-store';
const apiKey = await getSecret('mcp-server-credentials');

// ✅ CORRECT: Vault service
const credentials = await vault.getCredentials('mcp-server');

Phase 4: Performance & Optimization

阶段4:性能与优化

4.1 Token Efficiency (Primary Goal)

4.1 令牌效率(首要目标)

Problem: Every token in tool I/O consumes LLM context window.
Solution Pattern:
typescript
interface ToolResponse {
  format: 'concise' | 'detailed';  // Let LLM choose
}

async executeTool(name: string, args: { format?: string }) {
  const result = await this.server.callTool(name, args);

  // Default to concise
  if (args.format !== 'detailed') {
    return this.truncateResponse(result, MAX_TOKENS);
  }

  return result;
}

private truncateResponse(data: any, maxTokens: number): any {
  // Remove low-signal fields
  const { id, timestamp, metadata, ...essential } = data;

  // Truncate arrays
  if (Array.isArray(essential.items)) {
    essential.items = essential.items.slice(0, 10);
    essential.truncated = true;
  }

  return essential;
}
Server Response Design:
typescript
// ❌ BAD: Verbose response
{
  "temperature": 72.5,
  "temperature_unit": "fahrenheit",
  "humidity": 65,
  "humidity_unit": "percentage",
  "wind_speed": 5,
  "wind_speed_unit": "mph",
  "wind_direction": "N",
  "pressure": 1013,
  "pressure_unit": "mb",
  // ... 20 more fields
}

// ✅ GOOD: Concise response
{
  "temp": "72°F",
  "conditions": "cloudy",
  "wind": "5mph N"
}
问题:工具输入输出中的每个令牌都会占用LLM的上下文窗口。
解决方案模式:
typescript
interface ToolResponse {
  format: 'concise' | 'detailed';  // Let LLM choose
}

async executeTool(name: string, args: { format?: string }) {
  const result = await this.server.callTool(name, args);

  // Default to concise
  if (args.format !== 'detailed') {
    return this.truncateResponse(result, MAX_TOKENS);
  }

  return result;
}

private truncateResponse(data: any, maxTokens: number): any {
  // Remove low-signal fields
  const { id, timestamp, metadata, ...essential } = data;

  // Truncate arrays
  if (Array.isArray(essential.items)) {
    essential.items = essential.items.slice(0, 10);
    essential.truncated = true;
  }

  return essential;
}
服务器响应设计:
typescript
// ❌ BAD: Verbose response
{
  "temperature": 72.5,
  "temperature_unit": "fahrenheit",
  "humidity": 65,
  "humidity_unit": "percentage",
  "wind_speed": 5,
  "wind_speed_unit": "mph",
  "wind_direction": "N",
  "pressure": 1013,
  "pressure_unit": "mb",
  // ... 20 more fields
}

// ✅ GOOD: Concise response
{
  "temp": "72°F",
  "conditions": "cloudy",
  "wind": "5mph N"
}

4.2 Connection Pooling

4.2 连接池

For Database-Backed Servers:
typescript
class ConnectionPool {
  private pool: Connection[] = [];
  private maxSize = 10;

  async acquire(): Promise<Connection> {
    if (this.pool.length > 0) {
      return this.pool.pop()!;
    }

    if (this.activeConnections < this.maxSize) {
      return await this.createConnection();
    }

    // Wait for available connection
    return await this.waitForConnection();
  }

  release(conn: Connection) {
    this.pool.push(conn);
  }
}
针对数据库后端服务器:
typescript
class ConnectionPool {
  private pool: Connection[] = [];
  private maxSize = 10;

  async acquire(): Promise<Connection> {
    if (this.pool.length > 0) {
      return this.pool.pop()!;
    }

    if (this.activeConnections < this.maxSize) {
      return await this.createConnection();
    }

    // Wait for available connection
    return await this.waitForConnection();
  }

  release(conn: Connection) {
    this.pool.push(conn);
  }
}

4.3 Request Queuing

4.3 请求队列

Handle Burst Traffic:
typescript
class RequestQueue {
  private queue: PendingRequest[] = [];
  private processing = 0;
  private maxConcurrent = 5;

  async enqueue(request: Request): Promise<Response> {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.processQueue();
    });
  }

  private async processQueue() {
    while (this.queue.length > 0 && this.processing < this.maxConcurrent) {
      const { request, resolve, reject } = this.queue.shift()!;
      this.processing++;

      try {
        const result = await this.executeRequest(request);
        resolve(result);
      } catch (error) {
        reject(error);
      } finally {
        this.processing--;
        this.processQueue();
      }
    }
  }
}

处理突发流量:
typescript
class RequestQueue {
  private queue: PendingRequest[] = [];
  private processing = 0;
  private maxConcurrent = 5;

  async enqueue(request: Request): Promise<Response> {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.processQueue();
    });
  }

  private async processQueue() {
    while (this.queue.length > 0 && this.processing < this.maxConcurrent) {
      const { request, resolve, reject } = this.queue.shift()!;
      this.processing++;

      try {
        const result = await this.executeRequest(request);
        resolve(result);
      } catch (error) {
        reject(error);
      } finally {
        this.processing--;
        this.processQueue();
      }
    }
  }
}

Phase 5: Testing & Validation

阶段5:测试与验证

5.1 Use MCP Inspector

5.1 使用MCP Inspector

bash
npx @modelcontextprotocol/inspector <server-command>
bash
npx @modelcontextprotocol/inspector <server-command>

**Validation Checklist**:
- [ ] Connection establishes successfully
- [ ] Capabilities negotiated correctly
- [ ] Tools discovered and listed
- [ ] Tool calls execute and return results
- [ ] Errors display meaningful messages
- [ ] Session IDs propagate (HTTP transport)
- [ ] OAuth flow completes (if applicable)

**验证清单**:
- [ ] 连接成功建立
- [ ] 能力协商正确
- [ ] 工具被发现并列出
- [ ] 工具调用执行并返回结果
- [ ] 错误显示有意义的信息
- [ ] 会话ID正确传播(HTTP传输)
- [ ] OAuth流程完成(如适用)

5.2 Automated Testing

5.2 自动化测试

Technical Tests (fast, comprehensive):
typescript
describe('Client', () => {
  it('negotiates capabilities', async () => {
    const client = new Client({ capabilities: { sampling: {} } });
    await client.connect(mockTransport);

    expect(client.serverCapabilities).toBeDefined();
  });

  it('handles tool calls', async () => {
    const result = await client.callTool('test_tool', { arg: 'value' });
    expect(result.content).toBeDefined();
  });

  it('retries on transient errors', async () => {
    mockTransport.failTimes(2);  // Fail twice, then succeed
    const result = await client.callTool('flaky_tool', {});
    expect(result).toBeDefined();
  });
});
Behavioral Tests (with real LLM):
typescript
describe('Client with LLM', () => {
  it('LLM can discover and use tools', async () => {
    const query = "What's the weather in Tokyo?";
    const response = await client.processQuery(query);

    expect(response).toContain('Tokyo');
    expect(response).toMatch(/\d+°[FC]/);  // Contains temperature
  });
});

技术测试(快速、全面):
typescript
describe('Client', () => {
  it('negotiates capabilities', async () => {
    const client = new Client({ capabilities: { sampling: {} } });
    await client.connect(mockTransport);

    expect(client.serverCapabilities).toBeDefined();
  });

  it('handles tool calls', async () => {
    const result = await client.callTool('test_tool', { arg: 'value' });
    expect(result.content).toBeDefined();
  });

  it('retries on transient errors', async () => {
    mockTransport.failTimes(2);  // Fail twice, then succeed
    const result = await client.callTool('flaky_tool', {});
    expect(result).toBeDefined();
  });
});
行为测试(使用真实LLM):
typescript
describe('Client with LLM', () => {
  it('LLM can discover and use tools', async () => {
    const query = "What's the weather in Tokyo?";
    const response = await client.processQuery(query);

    expect(response).toContain('Tokyo');
    expect(response).toMatch(/\d+°[FC]/);  // Contains temperature
  });
});

Reference Architecture

参考架构

Recommended Client Structure

推荐客户端结构

typescript
class MCPClient {
  private transport: Transport;
  private session: SessionManager;
  private auth: AuthProvider;
  private authz: AuthorizationManager;
  private errorHandler: ErrorHandler;
  private requestQueue: RequestQueue;

  // Core protocol methods
  async connect(transport: Transport): Promise<void> { /* ... */ }
  async listTools(): Promise<Tool[]> { /* ... */ }
  async callTool(name: string, args: any): Promise<ToolResult> { /* ... */ }
  async listResources(): Promise<Resource[]> { /* ... */ }
  async readResource(uri: string): Promise<ResourceContent> { /* ... */ }
  async listPrompts(): Promise<Prompt[]> { /* ... */ }
  async getPrompt(name: string, args: any): Promise<PromptContent> { /* ... */ }

  // Client capability implementations
  async handleSamplingRequest(request: SamplingRequest): Promise<SamplingResult> { /* ... */ }
  async handleElicitationRequest(request: ElicitationRequest): Promise<ElicitationResult> { /* ... */ }

  // Lifecycle
  async close(): Promise<void> { /* ... */ }
}

typescript
class MCPClient {
  private transport: Transport;
  private session: SessionManager;
  private auth: AuthProvider;
  private authz: AuthorizationManager;
  private errorHandler: ErrorHandler;
  private requestQueue: RequestQueue;

  // Core protocol methods
  async connect(transport: Transport): Promise<void> { /* ... */ }
  async listTools(): Promise<Tool[]> { /* ... */ }
  async callTool(name: string, args: any): Promise<ToolResult> { /* ... */ }
  async listResources(): Promise<Resource[]> { /* ... */ }
  async readResource(uri: string): Promise<ResourceContent> { /* ... */ }
  async listPrompts(): Promise<Prompt[]> { /* ... */ }
  async getPrompt(name: string, args: any): Promise<PromptContent> { /* ... */ }

  // Client capability implementations
  async handleSamplingRequest(request: SamplingRequest): Promise<SamplingResult> { /* ... */ }
  async handleElicitationRequest(request: ElicitationRequest): Promise<ElicitationResult> { /* ... */ }

  // Lifecycle
  async close(): Promise<void> { /* ... */ }
}

Common Patterns

常见模式

Pattern: Bidirectional Communication

模式:双向通信

typescript
class Client {
  // Client → Server requests
  async request(method: string, params: any): Promise<any> {
    return this.sendRequest({ method, params });
  }

  // Server → Client requests (handlers)
  private handlers = new Map<string, RequestHandler>();

  registerHandler(method: string, handler: RequestHandler) {
    this.handlers.set(method, handler);
  }

  // Message router
  private async handleIncomingMessage(message: JSONRPCMessage) {
    if ('method' in message && 'id' in message) {
      // This is a request FROM server TO client
      const handler = this.handlers.get(message.method);
      if (handler) {
        const result = await handler(message.params);
        await this.sendResponse(message.id, result);
      }
    }
  }
}

// Usage:
client.registerHandler('sampling/createMessage', async (params) => {
  // Server is asking client to get LLM completion
  return await this.llm.complete(params.messages);
});
typescript
class Client {
  // Client → Server requests
  async request(method: string, params: any): Promise<any> {
    return this.sendRequest({ method, params });
  }

  // Server → Client requests (handlers)
  private handlers = new Map<string, RequestHandler>();

  registerHandler(method: string, handler: RequestHandler) {
    this.handlers.set(method, handler);
  }

  // Message router
  private async handleIncomingMessage(message: JSONRPCMessage) {
    if ('method' in message && 'id' in message) {
      // This is a request FROM server TO client
      const handler = this.handlers.get(message.method);
      if (handler) {
        const result = await handler(message.params);
        await this.sendResponse(message.id, result);
      }
    }
  }
}

// Usage:
client.registerHandler('sampling/createMessage', async (params) => {
  // Server is asking client to get LLM completion
  return await this.llm.complete(params.messages);
});

Pattern: Resource vs Tool Usage

模式:资源与工具的使用差异

typescript
// Resources: Data client can READ
const calendarData = await client.readResource('calendar://events/today');
// Returns: { text: "Meeting at 3pm, Lunch at 12pm" }

// Tools: Actions client can EXECUTE
const result = await client.callTool('create_event', {
  title: "Team Meeting",
  time: "2024-01-15T15:00:00Z"
});
// Returns: { content: [{ type: "text", text: "Event created" }] }
typescript
// Resources: Data client can READ
const calendarData = await client.readResource('calendar://events/today');
// Returns: { text: "Meeting at 3pm, Lunch at 12pm" }

// Tools: Actions client can EXECUTE
const result = await client.callTool('create_event', {
  title: "Team Meeting",
  time: "2024-01-15T15:00:00Z"
});
// Returns: { content: [{ type: "text", text: "Event created" }] }

Pattern: Prompts as Templates

模式:模板化提示词

typescript
// Get prompt template from server
const prompt = await client.getPrompt('write_email', {
  recipient: "boss@company.com",
  topic: "project update"
});

// prompt.messages contains pre-filled conversation
// Send to LLM with additional context
const completion = await llm.complete([
  ...prompt.messages,
  { role: "user", content: "Include metrics from Q4" }
]);

typescript
// Get prompt template from server
const prompt = await client.getPrompt('write_email', {
  recipient: "boss@company.com",
  topic: "project update"
});

// prompt.messages contains pre-filled conversation
// Send to LLM with additional context
const completion = await llm.complete([
  ...prompt.messages,
  { role: "user", content: "Include metrics from Q4" }
]);

Anti-Patterns to Avoid

需避免的反模式

❌ Client Choosing Tools

❌ 客户端选择工具

typescript
// WRONG: Client decides what to do
if (query.includes('weather')) {
  return await client.callTool('check_weather', { city: extractCity(query) });
}
typescript
// WRONG: Client decides what to do
if (query.includes('weather')) {
  return await client.callTool('check_weather', { city: extractCity(query) });
}

❌ Forgetting Session IDs

❌ 忘记会话ID

typescript
// WRONG: Not propagating session
fetch(url, {
  headers: { 'Content-Type': 'application/json' }  // Missing Mcp-Session-Id
});
typescript
// WRONG: Not propagating session
fetch(url, {
  headers: { 'Content-Type': 'application/json' }  // Missing Mcp-Session-Id
});

❌ No Retry Logic

❌ 无重试逻辑

typescript
// WRONG: Fail on first error
const result = await client.callTool('flaky_api', {});  // Will fail randomly
typescript
// WRONG: Fail on first error
const result = await client.callTool('flaky_api', {});  // Will fail randomly

❌ Verbose Responses

❌ 冗长响应

typescript
// WRONG: Returning all data
return await database.query('SELECT * FROM users');  // 10,000 rows

// CORRECT: Return summary
return { count: 10000, sample: users.slice(0, 10) };

typescript
// WRONG: Returning all data
return await database.query('SELECT * FROM users');  // 10,000 rows

// CORRECT: Return summary
return { count: 10000, sample: users.slice(0, 10) };

Quick Start Templates

快速开始模板

See reference files:
  • TypeScript: typescript_mcp_client.md
  • Python: python_mcp_client.md
  • Architecture: client_architecture.md
  • Best Practices: mcp_client_best_practices.md

参考以下文件:
  • TypeScript: typescript_mcp_client.md
  • Python: python_mcp_client.md
  • Architecture: client_architecture.md
  • Best Practices: mcp_client_best_practices.md

Success Criteria

成功标准

Client is production-ready when:
  • Connects to servers via all required transports
  • Negotiates capabilities correctly
  • Discovers and executes tools, resources, prompts
  • Implements retry logic with exponential backoff
  • Handles errors gracefully with actionable messages
  • Enforces security at network, auth, authz, validation layers
  • Optimizes for token efficiency
  • Passes both technical and behavioral tests
  • Includes comprehensive logging to stderr (never stdout in stdio mode)
  • Manages sessions correctly (HTTP transport)
客户端达到生产就绪状态的标志:
  • 可通过所有所需传输方式连接到服务器
  • 正确协商能力
  • 发现并执行工具、资源、提示词
  • 实现带指数退避的重试逻辑
  • 优雅处理错误并提供可操作的信息
  • 在网络、认证、授权、验证层面实施安全措施
  • 优化令牌效率
  • 通过技术测试和行为测试
  • 包含全面的日志输出到stderr(stdio模式下绝不能输出到stdout)
  • 正确管理会话(HTTP传输)