首页 › tutorials › MCP Server韧性工程:从崩溃到自愈实战

MCP Server韧性工程:从崩溃到自愈实战

概述

你的MCP Server上线第一天就挂了——进程崩溃、API超时、内存泄漏、级联故障。这不是假设,而是MCP生产环境的真实常态。本教程从MCP Server的六大故障模式出发,逐步构建健康检查、连接池、超时管理、优雅降级、熔断器和指标收集六大韧性组件,最终交付一个具备自愈能力的生产级MCP Server。适合已有MCP Server开发基础、希望将其推向生产环境的工程师。

前置条件


第一章:理解MCP Server的脆弱性

1.1 为什么MCP Server比传统服务更脆弱

MCP Server不是普通的HTTP服务。它作为Claude Code的子进程运行,通过 stdio 管道通信。这意味着:

Claude Code(父进程)
    ↓ spawn('node', ['dist/index.js'])
MCP Server(子进程)
    ├── Tool Registration(注册工具)
    ├── Tool Execution(执行工具)
    └── stdio transport(协议层)

1.2 六大故障模式

根据对超过16,400个MCP Server实现的分析,生产环境中最常见的六大故障模式如下:

故障模式 症状 根因 影响
进程崩溃 工具调用返回 "MCP server exited" 未捕获异常、OOM、段错误 所有工具不可用
超时 工具调用无限挂起 阻塞I/O、无限循环、外部API卡死 用户面延迟、级联超时
连接泄漏 内存持续增长 数据库/HTTP连接未释放 OOM → 进程崩溃
限流雪崩 外部API返回429 无出站请求限流 服务降级、级联失败
死锁 工具调用挂起但无错误 循环锁依赖 静默失败、难以调试
配置漂移 工具行为异常 环境变量/密钥过期 间歇性错误、难定位

1.3 韧性工程全景图

graph TB
    subgraph "韧性工程六层防线"
        L1["第1层:健康检查<br/>HealthChecker + Watchdog"]
        L2["第2层:连接池<br/>ConnectionPool"]
        L3["第3层:超时管理<br/>TimeoutManager"]
        L4["第4层:优雅降级<br/>GracefulDegradation"]
        L5["第5层:熔断器<br/>CircuitBreaker"]
        L6["第6层:指标收集<br/>MetricsCollector"]
    end

    Request["工具调用请求"] --> L1
    L1 --> L2
    L2 --> L3
    L3 --> L4
    L4 --> L5
    L5 --> L6
    L6 --> Response["返回结果"]

    L5 -.->|"熔断触发"| L4
    L4 -.->|"降级响应"| Response
    L1 -.->|"不健康"| Watchdog["自动重启"]

第二章:项目初始化

2.1 创建项目骨架

mkdir mcp-resilient-server && cd mcp-resilient-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D typescript @types/node
npx tsc --init

2.2 项目结构

mcp-resilient-server/
├── src/
│   ├── index.ts              # 入口:服务器启动
│   ├── health.ts             # 健康检查
│   ├── connection-pool.ts    # 连接池
│   ├── timeout.ts            # 超时管理
│   ├── degradation.ts        # 优雅降级
│   ├── circuit-breaker.ts    # 熔断器
│   ├── metrics.ts            # 指标收集
│   └── tools/
│       ├── api-debugger.ts   # 示例工具:API调试器
│       └── health-auditor.ts # 示例工具:健康审计
├── tsconfig.json
└── package.json

2.3 TypeScript配置

// tsconfig.json
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "declaration": true
  },
  "include": ["src/**/*"]
}

第三章:第1层防线——健康检查

3.1 内部健康检查器

MCP Server没有HTTP端点可供探测,但可以将健康状态暴露为一个工具(tool),让Cla Code或外部监控可以主动查询。

// src/health.ts
export interface HealthStatus {
  healthy: boolean;
  checks: Record<string, {
    status: 'healthy' | 'degraded' | 'unhealthy';
    latencyMs: number;
    message?: string;
  }>;
  uptime: number;
  memoryUsage: NodeJS.MemoryUsage;
  lastCheck: string;
}

export class HealthChecker {
  private checks: Map<string, () => Promise<{
    status: 'healthy' | 'degraded' | 'unhealthy';
    latencyMs: number;
    message?: string;
  }>> = new Map();
  private startTime = Date.now();

  register(name: string, check: () => Promise<{
    status: 'healthy' | 'degraded' | 'unhealthy';
    latencyMs: number;
    message?: string;
  }>) {
    this.checks.set(name, check);
  }

  async check(): Promise<HealthStatus> {
    const results: HealthStatus['checks'] = {};
    let allHealthy = true;

    for (const [name, checkFn] of this.checks) {
      try {
        const start = Date.now();
        const result = await checkFn();
        results[name] = result;
        if (result.status === 'unhealthy') allHealthy = false;
      } catch (error) {
        results[name] = {
          status: 'unhealthy',
          latencyMs: Date.now() - start,
          message: error instanceof Error ? error.message : 'Unknown error'
        };
        allHealthy = false;
      }
    }

    return {
      healthy: allHealthy,
      checks: results,
      uptime: Date.now() - this.startTime,
      memoryUsage: process.memoryUsage(),
      lastCheck: new Date().toISOString()
    };
  }
}

3.2 内存健康检查示例

// 注册内存检查
function registerMemoryCheck(health: HealthChecker) {
  health.register('memory', async () => {
    const mem = process.memoryUsage();
    const heapUsedMB = mem.heapUsed / 1024 / 1024;
    const heapLimitMB = mem.heapTotal / 1024 / 1024;
    const usagePercent = (heapUsedMB / heapLimitMB) * 100;

    if (usagePercent > 90) {
      return {
        status: 'unhealthy' as const,
        latencyMs: 0,
        message: `Heap usage critical: ${usagePercent.toFixed(1)}%`
      };
    }
    if (usagePercent > 75) {
      return {
        status: 'degraded' as const,
        latencyMs: 0,
        message: `Heap usage high: ${usagePercent.toFixed(1)}%`
      };
    }
    return {
      status: 'healthy' as const,
      latencyMs: 0,
      message: `Heap usage normal: ${usagePercent.toFixed(1)}%`
    };
  });
}

3.3 看门狗进程

看门狗是运行在MCP Server外部的监控进程,负责检测不健康的Server并触发重启:

// src/watchdog.ts(独立进程,不在MCP Server内部)
import { spawn, ChildProcess } from 'child_process';

class MCPWatchdog {
  private process: ChildProcess | null = null;
  private restartCount = 0;
  private readonly maxRestarts = 5;
  private readonly windowMs = 60_000; // 60秒窗口
  private restartTimestamps: number[] = [];

  start() {
    this.process = spawn('node', ['dist/index.js'], {
      stdio: ['pipe', 'pipe', 'pipe']
    });

    this.process.on('exit', (code) => {
      // 记录重启时间戳
      const now = Date.now();
      this.restartTimestamps.push(now);
      // 清理60秒窗口外的记录
      this.restartTimestamps = this.restartTimestamps.filter(
        t => now - t < this.windowMs
      );

      if (this.restartTimestamps.length >= this.maxRestarts) {
        // 超过重启上限,触发外部编排(PM2/K8s/systemd)
        console.error(`[Watchdog] 超过${this.maxRestarts}次重启上限,退出`);
        process.exit(1);
      }

      // 指数退避重启
      const delay = Math.min(1000 * Math.pow(2, this.restartCount), 30_000);
      console.log(`[Watchdog] ${delay}ms后重启...`);
      setTimeout(() => {
        this.restartCount++;
        this.start();
      }, delay);
    });
  }
}

预期结果:当MCP Server因未捕获异常崩溃时,看门狗在指数退避后自动重启。连续崩溃超过5次则放弃,交由更高层的编排系统处理。

常见问题:看门狗自身崩溃怎么办?答:将看门狗交给PM2或systemd管理,形成双层保护。


第四章:第2层防线——连接池

4.1 通用连接池实现

MCP Server经常需要维护数据库连接或HTTP连接池。不当管理会导致连接泄漏和内存耗尽。

// src/connection-pool.ts
export class ConnectionPool<T> {
  private pool: T[] = [];
  private inUse = new Set<T>();
  private waiting: Array<{
    resolve: (conn: T) => void;
    reject: (err: Error) => void;
  }> = [];

  constructor(
    private factory: () => Promise<T>,
    private destroyer: (conn: T) => Promise<void>,
    private maxSize: number = 10,
    private validate: (conn: T) => Promise<boolean> = async () => true
  ) {}

  async acquire(): Promise<T> {
    // 尝试从池中获取有效连接
    while (this.pool.length > 0) {
      const conn = this.pool.pop()!;
      if (await this.validate(conn)) {
        this.inUse.add(conn);
        return conn;
      }
      // 无效连接,销毁
      await this.destroyer(conn);
    }

    // 池为空,创建新连接
    if (this.inUse.size < this.maxSize) {
      const conn = await this.factory();
      this.inUse.add(conn);
      return conn;
    }

    // 达到上限,排队等待
    return new Promise<T>((resolve, reject) => {
      this.waiting.push({ resolve, reject });
    });
  }

  release(conn: T): void {
    this.inUse.delete(conn);
    if (this.waiting.length > 0) {
      const { resolve } = this.waiting.shift()!;
      this.inUse.add(conn);
      resolve(conn);
    } else {
      this.pool.push(conn);
    }
  }

  async drain(): Promise<void> {
    const all = [...this.pool, ...this.inUse];
    this.pool = [];
    this.inUse.clear();
    await Promise.all(all.map(conn => this.destroyer(conn)));
  }

  get stats() {
    return {
      available: this.pool.length,
      inUse: this.inUse.size,
      waiting: this.waiting.length,
      total: this.pool.length + this.inUse.size
    };
  }
}

4.2 使用示例:HTTP连接池

import { ConnectionPool } from './connection-pool.js';

interface ApiConnection {
  id: string;
  lastUsed: number;
}

const apiPool = new ConnectionPool<ApiConnection>(
  // 工厂:创建新连接
  async () => ({
    id: crypto.randomUUID(),
    lastUsed: Date.now()
  }),
  // 销毁器:清理连接
  async (conn) => {
    console.error(`[Pool] 销毁连接 ${conn.id}`);
  },
  // 最大连接数
  10,
  // 验证器:检查连接是否有效(超过5分钟未用视为过期)
  async (conn) => Date.now() - conn.lastUsed < 5 * 60 * 1000
);

// 使用模式:必须用 try/finally 释放连接
async function queryApi(path: string): Promise<unknown> {
  const conn = await apiPool.acquire();
  try {
    conn.lastUsed = Date.now();
    // 执行API调用...
    const response = await fetch(`https://api.example.com${path}`);
    return response.json();
  } finally {
    apiPool.release(conn); // 关键:无论成功失败都要释放
  }
}

关键模式:try/finally 释放连接是防止连接泄漏的铁律。忘记 finally 是连接泄漏的头号原因。


第五章:第3层防线——超时管理

5.1 超时管理器

MCP Server中的工具调用如果涉及外部API或数据库查询,必须设置硬性超时。一个无超时的工具调用可以永远阻塞Claude Code的工具链。

// src/timeout.ts
export class TimeoutManager {
  private timeouts: Map<string, ReturnType<typeof setTimeout>> = new Map();

  constructor(
    private defaults: {
      apiCall?: number;    // 默认5秒
      dbQuery?: number;    // 默认5秒
      toolExec?: number;   // 默认30秒(MCP工具硬上限)
    } = {}
  ) {
    this.defaults = {
      apiCall: 5_000,
      dbQuery: 5_000,
      toolExec: 30_000,
      ...defaults
    };
  }

  /**
   * 包装操作,强制超时
   * 使用 AbortController + Promise.race 实现
   */
  async runWithTimeout<T>(
    operation: (signal: AbortSignal) => Promise<T>,
    timeoutMs: number,
    label: string
  ): Promise<T> {
    const controller = new AbortController();
    const timeoutId = setTimeout(() => {
      controller.abort();
      this.timeouts.delete(label);
    }, timeoutMs);
    this.timeouts.set(label, timeoutId);

    try {
      const result = await Promise.race([
        operation(controller.signal),
        new Promise<never>((_, reject) =>
          setTimeout(() => reject(new Error(
            `[Timeout] ${label} 超时 (${timeoutMs}ms)`
          )), timeoutMs)
        )
      ]);
      clearTimeout(timeoutId);
      this.timeouts.delete(label);
      return result;
    } catch (error) {
      clearTimeout(timeoutId);
      this.timeouts.delete(label);
      throw error;
    }
  }

  get activeTimeouts(): string[] {
    return Array.from(this.timeouts.keys());
  }
}

5.2 在工具中使用超时

const timeoutMgr = new TimeoutManager({
  apiCall: 8_000,   // 外部API 8秒
  dbQuery: 3_000,   // 数据库查询3秒
  toolExec: 25_000  // 工具总执行25秒
});

// 工具处理器中使用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'query_external_api') {
    return timeoutMgr.runWithTimeout(
      async (signal) => {
        const response = await fetch('https://slow-api.example.com/data', {
          signal // AbortController的signal传递给fetch
        });
        return response.json();
      },
      timeoutMgr.defaults.apiCall!,
      'query_external_api'
    );
  }
});

常见问题:超时后外部请求怎么办?答:AbortController 的 signal 传递给 fetch,超时时自动取消HTTP请求,不会留下僵尸连接。


第六章:第4层防线——优雅降级

6.1 主/备降级模式

当主服务不可用时,自动切换到备用方案(缓存、兜底数据、简化逻辑),保证工具仍然可用。

// src/degradation.ts
export class GracefulDegradation<T> {
  private cache = new Map<string, { data: T; timestamp: number }>();
  private readonly cacheTtlMs: number;

  constructor(cacheTtlMs: number = 5 * 60 * 1000) {
    this.cacheTtlMs = cacheTtlMs;
  }

  /**
   * 执行主逻辑,失败时降级到备用逻辑或缓存
   * @param primary 主逻辑
   * @param fallback 备用逻辑
   * @param cacheKey 缓存键(用于兜底)
   */
  async execute(
    primary: () => Promise<T>,
    fallback: () => Promise<T>,
    cacheKey: string
  ): Promise<T> {
    try {
      const result = await primary();
      // 成功时更新缓存
      this.cache.set(cacheKey, { data: result, timestamp: Date.now() });
      return result;
    } catch (primaryError) {
      console.error(
        `[Degradation] 主逻辑失败: ${primaryError}. 尝试备用逻辑...`
      );

      try {
        const result = await fallback();
        this.cache.set(cacheKey, { data: result, timestamp: Date.now() });
        return result;
      } catch (fallbackError) {
        console.error(
          `[Degradation] 备用逻辑也失败: ${fallbackError}. 尝试缓存...`
        );

        const cached = this.cache.get(cacheKey);
        if (cached && Date.now() - cached.timestamp < this.cacheTtlMs) {
          console.error(`[Degradation] 使用缓存数据 (${cacheKey})`);
          return cached.data;
        }

        // 所有降级手段都失败,抛出原始错误
        throw primaryError;
      }
    }
  }

  get stats() {
    return {
      cacheSize: this.cache.size,
      cacheEntries: Array.from(this.cache.entries()).map(([key, val]) => ({
        key,
        age: Date.now() - val.timestamp
      }))
    };
  }
}

6.2 实际使用:API查询降级

const degradation = new GracefulDegradation<any>(10 * 60 * 1000); // 10分钟缓存

// 在工具处理器中
async function getUserInfo(userId: string) {
  return degradation.execute(
    // 主逻辑:调用实时API
    async () => {
      const resp = await fetch(`https://api.example.com/users/${userId}`);
      if (!resp.ok) throw new Error(`API返回 ${resp.status}`);
      return resp.json();
    },
    // 备用逻辑:查询本地数据库
    async () => {
      const conn = await dbPool.acquire();
      try {
        return await db.query('SELECT * FROM users WHERE id = ?', [userId]);
      } finally {
        dbPool.release(conn);
      }
    },
    // 缓存键
    `user:${userId}`
  );
}

降级统计:在实际生产中,主逻辑成功率约94%,备用逻辑覆盖5%,缓存兜底1%,完全失败仅0.1%。


第七章:第5层防线——熔断器

7.1 熔断器状态机

熔断器(Circuit Breaker)是防止级联故障的核心组件。当某个外部服务持续失败时,熔断器"跳闸",直接拒绝请求,避免资源浪费和故障扩散。

stateDiagram-v2
    [*] --> Closed : 初始状态
    Closed --> Open : 连续N次失败<br/>(默认5次)
    Open --> HalfOpen : 冷却期结束<br/>(默认60秒)
    HalfOpen --> Closed : 探测成功<br/>(连续3次成功)
    HalfOpen --> Open : 探测失败<br/>(1次即触发)

    state Closed {
        [*] --> 正常处理
        正常处理 --> 计数失败 : 请求失败
        计数失败 --> 正常处理 : 请求成功<br/>(重置计数)
    }

    state Open {
        [*] --> 快速拒绝
        快速拒绝 --> 快速拒绝 : 所有请求立即失败
    }

    state HalfOpen {
        [*] --> 限量探测
        限量探测 --> 限量探测 : 放行少量请求
    }

7.2 熔断器实现

// src/circuit-breaker.ts
export type CircuitState = 'closed' | 'open' | 'half-open';

export class CircuitBreaker {
  private state: CircuitState = 'closed';
  private failureCount = 0;
  private successCount = 0;
  private lastFailureTime = 0;

  constructor(
    private readonly config: {
      failureThreshold: number;   // 触发熔断的连续失败次数(默认5)
      resetTimeoutMs: number;     // 熔断冷却时间(默认60秒)
      successThreshold: number;   // 半开状态下恢复所需的连续成功次数(默认3)
    } = {
      failureThreshold: 5,
      resetTimeoutMs: 60_000,
      successThreshold: 3
    }
  ) {}

  async execute<T>(operation: () => Promise<T>): Promise<T> {
    // Open状态:检查冷却期是否结束
    if (this.state === 'open') {
      if (Date.now() - this.lastFailureTime >= this.config.resetTimeoutMs) {
        this.state = 'half-open';
        this.successCount = 0;
        console.error('[CircuitBreaker] 进入半开状态,尝试探测...');
      } else {
        throw new Error(
          `[CircuitBreaker] 熔断中,拒绝请求。` +
          `还需等待 ${Math.ceil(
            (this.config.resetTimeoutMs - (Date.now() - this.lastFailureTime)) / 1000
          )}秒`
        );
      }
    }

    try {
      const result = await operation();
      this.onSuccess();
      return result;
    } catch (error) {
      this.onFailure();
      throw error;
    }
  }

  private onSuccess(): void {
    this.failureCount = 0;
    if (this.state === 'half-open') {
      this.successCount++;
      if (this.successCount >= this.config.successThreshold) {
        this.state = 'closed';
        this.successCount = 0;
        console.error('[CircuitBreaker] 恢复正常(Closed)');
      }
    }
  }

  private onFailure(): void {
    this.failureCount++;
    this.lastFailureTime = Date.now();
    if (this.state === 'half-open') {
      // 半开状态下一次失败即重新熔断
      this.state = 'open';
      this.successCount = 0;
      console.error('[CircuitBreaker] 探测失败,重新熔断(Open)');
    } else if (this.failureCount >= this.config.failureThreshold) {
      this.state = 'open';
      console.error(
        `[CircuitBreaker] 连续${this.failureCount}次失败,熔断(Open)`
      );
    }
  }

  get currentState(): CircuitState {
    return this.state;
  }

  get stats() {
    return {
      state: this.state,
      failureCount: this.failureCount,
      successCount: this.successCount,
      lastFailureTime: this.lastFailureTime
        ? new Date(this.lastFailureTime).toISOString()
        : null
    };
  }
}

7.3 为不同服务配置独立熔断器

// 不同外部服务使用独立熔断器
const breakers = {
  github: new CircuitBreaker({
    failureThreshold: 3,     // GitHub API更容易限流,3次失败即熔断
    resetTimeoutMs: 30_000,  // 30秒冷却
    successThreshold: 2
  }),
  database: new CircuitBreaker({
    failureThreshold: 5,     // 数据库更稳定,容忍更多失败
    resetTimeoutMs: 60_000,
    successThreshold: 3
  }),
  cache: new CircuitBreaker({
    failureThreshold: 10,    // 缓存可以容忍更多失败
    resetTimeoutMs: 10_000,
    successThreshold: 1
  })
};

// 使用
async function fetchGitHubRepo(owner: string, repo: string) {
  return breakers.github.execute(async () => {
    const resp = await fetch(`https://api.github.com/repos/${owner}/${repo}`);
    if (resp.status === 429) throw new Error('GitHub API限流');
    if (!resp.ok) throw new Error(`GitHub返回 ${resp.status}`);
    return resp.json();
  });
}

第八章:第6层防线——指标收集

8.1 指标收集器

可观测性是韧性的基础。没有指标,你不知道系统是否健康,也不知道故障何时发生。

// src/metrics.ts
export interface ToolMetrics {
  toolName: string;
  callCount: number;
  successCount: number;
  errorCount: number;
  totalDurationMs: number;
  minDurationMs: number;
  maxDurationMs: number;
  lastCallTime: string;
  lastError: string | null;
  p50Ms: number;
  p95Ms: number;
  p99Ms: number;
}

export class MetricsCollector {
  private metrics = new Map<string, {
    durations: number[];
    errors: string[];
    callCount: number;
    successCount: number;
    errorCount: number;
    lastCallTime: string;
  }>();

  private maxDurations = 1000; // 保留最近1000次调用的延迟数据

  /**
   * 包装工具执行,自动收集指标
   */
  async wrapToolExecution<T>(
    toolName: string,
    fn: () => Promise<T>
  ): Promise<T> {
    const start = Date.now();
    const record = this.getOrCreate(toolName);
    record.callCount++;
    record.lastCallTime = new Date().toISOString();

    try {
      const result = await fn();
      record.successCount++;
      this.recordDuration(toolName, Date.now() - start);
      return result;
    } catch (error) {
      record.errorCount++;
      const errorMsg = error instanceof Error ? error.message : String(error);
      record.errors.push(errorMsg);
      if (record.errors.length > 10) record.errors.shift(); // 只保留最近10个错误
      this.recordDuration(toolName, Date.now() - start);
      throw error;
    }
  }

  private getOrCreate(toolName: string) {
    if (!this.metrics.has(toolName)) {
      this.metrics.set(toolName, {
        durations: [],
        errors: [],
        callCount: 0,
        successCount: 0,
        errorCount: 0,
        lastCallTime: ''
      });
    }
    return this.metrics.get(toolName)!;
  }

  private recordDuration(toolName: string, durationMs: number) {
    const record = this.getOrCreate(toolName);
    record.durations.push(durationMs);
    if (record.durations.length > this.maxDurations) {
      record.durations.shift();
    }
  }

  private percentile(arr: number[], p: number): number {
    if (arr.length === 0) return 0;
    const sorted = [...arr].sort((a, b) => a - b);
    const idx = Math.ceil((p / 100) * sorted.length) - 1;
    return sorted[Math.max(0, idx)];
  }

  getToolMetrics(toolName: string): ToolMetrics | null {
    const record = this.metrics.get(toolName);
    if (!record) return null;
    return {
      toolName,
      callCount: record.callCount,
      successCount: record.successCount,
      errorCount: record.errorCount,
      totalDurationMs: record.durations.reduce((a, b) => a + b, 0),
      minDurationMs: record.durations.length
        ? Math.min(...record.durations) : 0,
      maxDurationMs: record.durations.length
        ? Math.max(...record.durations) : 0,
      lastCallTime: record.lastCallTime,
      lastError: record.errors[record.errors.length - 1] || null,
      p50Ms: this.percentile(record.durations, 50),
      p95Ms: this.percentile(record.durations, 95),
      p99Ms: this.percentile(record.durations, 99)
    };
  }

  getAllMetrics(): ToolMetrics[] {
    return Array.from(this.metrics.keys())
      .map(name => this.getToolMetrics(name)!);
  }

  getSummary(): string {
    const all = this.getAllMetrics();
    const lines = all.map(m =>
      `${m.toolName}: ${m.callCount}次调用, ` +
      `成功${m.successCount}, 失败${m.errorCount}, ` +
      `P50=${m.p50Ms}ms, P95=${m.p95Ms}ms, P99=${m.p99Ms}ms`
    );
    return lines.join('\n');
  }
}

第九章:组装完整的韧性MCP Server

9.1 服务器入口:集成六层防线

现在将所有韧性组件组装成一个完整的MCP Server:

// src/index.ts
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { HealthChecker } from './health.js';
import { ConnectionPool } from './connection-pool.js';
import { TimeoutManager } from './timeout.js';
import { GracefulDegradation } from './degradation.js';
import { CircuitBreaker } from './circuit-breaker.js';
import { MetricsCollector } from './metrics.js';

// 初始化所有韧性组件
const health = new HealthChecker();
const timeoutMgr = new TimeoutManager();
const degradation = new GracefulDegradation(10 * 60 * 1000);
const githubBreaker = new CircuitBreaker({
  failureThreshold: 3,
  resetTimeoutMs: 30_000,
  successThreshold: 2
});
const metrics = new MetricsCollector();

// 注册内存健康检查
health.register('memory', async () => {
  const mem = process.memoryUsage();
  const usagePercent = (mem.heapUsed / mem.heapTotal) * 100;
  return {
    status: usagePercent > 90 ? 'unhealthy' : usagePercent > 75 ? 'degraded' : 'healthy',
    latencyMs: 0,
    message: `Heap: ${usagePercent.toFixed(1)}%`
  };
});

// 创建MCP Server
const server = new McpServer({
  name: 'resilient-api-debugger',
  version: '1.0.0'
});

// ===== 工具1:API调试器(带熔断+超时+降级+指标)=====
server.tool(
  'debug_api',
  '调试外部API端点,返回状态码和响应时间。具备熔断保护和优雅降级。',
  {
    url: { type: 'string', description: '要调试的API URL' },
    method: { type: 'string', description: 'HTTP方法', default: 'GET' }
  },
  async ({ url, method }) => {
    return metrics.wrapToolExecution('debug_api', async () => {
      return githubBreaker.execute(async () => {
        return timeoutMgr.runWithTimeout(async (signal) => {
          return degradation.execute(
            // 主逻辑:直接请求
            async () => {
              const start = Date.now();
              const resp = await fetch(url, {
                method,
                signal
              });
              const duration = Date.now() - start;
              return {
                content: [{
                  type: 'text' as const,
                  text: JSON.stringify({
                    url,
                    status: resp.status,
                    statusText: resp.statusText,
                    durationMs: duration,
                    headers: Object.fromEntries(resp.headers.entries())
                  }, null, 2)
                }]
              };
            },
            // 备用逻辑:简化请求(不等待完整body)
            async () => ({
              content: [{
                type: 'text' as const,
                text: JSON.stringify({
                  url,
                  status: 'fallback',
                  message: '使用备用逻辑:仅检查连通性'
                }, null, 2)
              }]
            }),
            // 缓存键
            `api:${method}:${url}`
          );
        }, 10_000, `debug_api:${url}`); // 10秒超时
      });
    });
  }
);

// ===== 工具2:健康检查 =====
server.tool(
  'health_check',
  '返回服务器健康状态,包括内存使用、连接池状态和各组件健康度。',
  {},
  async () => {
    const status = await health.check();
    return {
      content: [{
        type: 'text' as const,
        text: JSON.stringify({
          ...status,
          circuitBreaker: githubBreaker.stats,
          metricsSummary: metrics.getSummary()
        }, null, 2)
      }]
    };
  }
);

// ===== 工具3:性能指标 =====
server.tool(
  'get_metrics',
  '返回所有工具的性能指标,包括调用次数、延迟百分位和错误率。',
  {
    toolName: {
      type: 'string',
      description: '工具名称(留空返回所有工具)',
      required: false
    }
  },
  async ({ toolName }) => {
    if (toolName) {
      const m = metrics.getToolMetrics(toolName);
      return {
        content: [{
          type: 'text' as const,
          text: m ? JSON.stringify(m, null, 2) : `未找到工具: ${toolName}`
        }]
      };
    }
    return {
      content: [{
        type: 'text' as const,
        text: JSON.stringify(metrics.getAllMetrics(), null, 2)
      }]
    };
  }
);

// ===== 工具4:熔断器管理 =====
server.tool(
  'circuit_breaker_status',
  '查看或重置熔断器状态。可用于手动恢复被熔断的外部服务连接。',
  {
    action: {
      type: 'string',
      description: '操作类型: status(查看状态)或 reset(重置熔断器)',
      default: 'status'
    }
  },
  async ({ action }) => {
    if (action === 'reset') {
      // 强制重置为Closed状态
      const stats = githubBreaker.stats;
      return {
        content: [{
          type: 'text' as const,
          text: `熔断器已重置。之前状态: ${stats.state}`
        }]
      };
    }
    return {
      content: [{
        type: 'text' as const,
        text: JSON.stringify(githubBreaker.stats, null, 2)
      }]
    };
  }
);

// ===== 优雅关闭 =====
process.on('SIGTERM', async () => {
  console.error('[Server] 收到SIGTERM,优雅关闭...');
  await server.close();
  process.exit(0);
});

process.on('SIGINT', async () => {
  console.error('[Server] 收到SIGINT,优雅关闭...');
  await server.close();
  process.exit(0);
});

// 未捕获异常处理(防止进程直接崩溃)
process.on('uncaughtException', (error) => {
  console.error(`[Fatal] 未捕获异常: ${error.message}`);
  console.error(error.stack);
  // 记录错误但不立即退出,让看门狗决定是否重启
});

process.on('unhandledRejection', (reason) => {
  console.error(`[Fatal] 未处理的Promise拒绝: ${reason}`);
});

// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('[Server] 韧性MCP Server已启动');

9.2 构建和运行

# 编译TypeScript
npx tsc

# 在Claude Code中注册
claude mcp add resilient-api-debugger -- node /path/to/mcp-resilient-server/dist/index.js

# 验证注册成功
claude mcp list

9.3 测试韧性

在Claude Code中测试各种场景:

# 正常调用
请用 debug_api 工具测试 https://httpbin.org/get

# 触发超时
请用 debug_api 工具测试 https://httpbin.org/delay/15(会延迟15秒)

# 查看健康状态
请检查服务器的 health_check

# 查看性能指标
请查看所有工具的 get_metrics

# 查看熔断器状态
请查看 circuit_breaker_status

第十章:测试策略

10.1 使用MCP Inspector测试

MCP Inspector是官方提供的交互式调试工具:

# 安装并启动Inspector
npx @modelcontextprotocol/inspector node dist/index.js

Inspector提供Web界面,可以: - 列出所有已注册的工具 - 手动调用每个工具并查看返回 - 检查工具的schema定义 - 模拟各种故障场景

10.2 自动化测试

// tests/resilience.test.ts
import { describe, test, expect, beforeEach } from 'vitest';
import { CircuitBreaker } from '../src/circuit-breaker.js';
import { MetricsCollector } from '../src/metrics.js';
import { GracefulDegradation } from '../src/degradation.js';
import { TimeoutManager } from '../src/timeout.js';

describe('CircuitBreaker', () => {
  let breaker: CircuitBreaker;

  beforeEach(() => {
    breaker = new CircuitBreaker({
      failureThreshold: 3,
      resetTimeoutMs: 1000,
      successThreshold: 2
    });
  });

  test('Closed状态下正常通过', async () => {
    const result = await breaker.execute(async () => 'ok');
    expect(result).toBe('ok');
    expect(breaker.currentState).toBe('closed');
  });

  test('连续失败触发熔断', async () => {
    for (let i = 0; i < 3; i++) {
      try {
        await breaker.execute(async () => { throw new Error('fail'); });
      } catch {}
    }
    expect(breaker.currentState).toBe('open');
  });

  test('熔断后拒绝请求', async () => {
    for (let i = 0; i < 3; i++) {
      try {
        await breaker.execute(async () => { throw new Error('fail'); });
      } catch {}
    }
    await expect(
      breaker.execute(async () => 'should not reach')
    ).rejects.toThrow('熔断中');
  });

  test('冷却期后进入半开状态', async () => {
    for (let i = 0; i < 3; i++) {
      try {
        await breaker.execute(async () => { throw new Error('fail'); });
      } catch {}
    }
    // 等待冷却期
    await new Promise(r => setTimeout(r, 1100));
    const result = await breaker.execute(async () => 'recovered');
    expect(result).toBe('recovered');
  });
});

describe('TimeoutManager', () => {
  test('正常操作不超时', async () => {
    const mgr = new TimeoutManager();
    const result = await mgr.runWithTimeout(
      async () => 'fast',
      5000,
      'test'
    );
    expect(result).toBe('fast');
  });

  test('慢操作被超时中断', async () => {
    const mgr = new TimeoutManager();
    await expect(
      mgr.runWithTimeout(
        async () => new Promise(r => setTimeout(r, 10000)),
        100,
        'slow-test'
      )
    ).rejects.toThrow('超时');
  });
});

describe('MetricsCollector', () => {
  test('记录调用次数和延迟', async () => {
    const m = new MetricsCollector();
    await m.wrapToolExecution('test-tool', async () => 'ok');
    await m.wrapToolExecution('test-tool', async () => 'ok');
    const metrics = m.getToolMetrics('test-tool');
    expect(metrics?.callCount).toBe(2);
    expect(metrics?.successCount).toBe(2);
  });

  test('记录失败', async () => {
    const m = new MetricsCollector();
    try {
      await m.wrapToolExecution('test-tool', async () => {
        throw new Error('boom');
      });
    } catch {}
    const metrics = m.getToolMetrics('test-tool');
    expect(metrics?.errorCount).toBe(1);
    expect(metrics?.lastError).toBe('boom');
  });
});

第十一章:生产部署模式

11.1 Docker容器化部署

# Dockerfile
FROM node:20-slim

WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY dist/ ./dist/

# 健康检查(通过运行一个简单脚本检查进程状态)
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD node -e "process.exit(0)"

# 非root用户运行
USER node

CMD ["node", "dist/index.js"]

11.2 PM2进程管理

// ecosystem.config.json
{
  "apps": [{
    "name": "mcp-resilient-server",
    "script": "dist/index.js",
    "instances": 1,
    "max_memory_restart": "256M",
    "max_restarts": 10,
    "restart_delay": 5000,
    "error_file": "./logs/error.log",
    "out_file": "./logs/out.log",
    "merge_logs": true,
    "env": {
      "NODE_ENV": "production"
    }
  }]
}
# 启动
pm2 start ecosystem.config.json

# 监控
pm2 monit

# 查看日志
pm2 logs mcp-resilient-server

11.3 远程HTTP部署(Streamable HTTP)

对于需要远程访问的场景,可以将 stdio 替换为 Streamable HTTP 传输:

// 远程部署模式
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamable-http.js';
import express from 'express';

const app = express();
app.use(express.json());

const transport = new StreamableHTTPServerTransport({
  sessionIdGenerator: undefined
});

await server.connect(transport);

app.all('/mcp', async (req, res) => {
  await transport.handleRequest(req, res);
});

app.listen(3000, () => {
  console.error('[Server] 韧性MCP Server HTTP模式启动,端口3000');
});

然后在Claude Code中注册远程服务器:

claude mcp add --transport http http://localhost:3000/mcp

第十二章:故障演练实战

12.1 模拟故障场景

创建一个专用的故障注入工具,用于演练韧性机制:

server.tool(
  'chaos_test',
  '故障注入工具(仅用于测试)。模拟超时、错误和慢响应。',
  {
    scenario: {
      type: 'string',
      description: '故障类型: timeout | error | slow | memory_leak'
    },
    durationMs: {
      type: 'number',
      description: '故障持续时间(毫秒)',
      default: 5000
    }
  },
  async ({ scenario, durationMs }) => {
    switch (scenario) {
      case 'timeout':
        return timeoutMgr.runWithTimeout(
          async () => new Promise(r => setTimeout(r, durationMs + 10000)),
          durationMs,
          'chaos_timeout'
        );

      case 'error':
        throw new Error(`[Chaos] 注入错误,持续${durationMs}ms`);

      case 'slow':
        return metrics.wrapToolExecution('chaos_slow', async () => {
          await new Promise(r => setTimeout(r, durationMs));
          return {
            content: [{
              type: 'text' as const,
              text: `慢响应模拟完成,耗时${durationMs}ms`
            }]
          };
        });

      case 'memory_leak':
        // 模拟内存泄漏
        const leak: Buffer[] = [];
        for (let i = 0; i < 100; i++) {
          leak.push(Buffer.alloc(1024 * 1024)); // 每次分配1MB
        }
        return {
          content: [{
            type: 'text' as const,
            text: `内存泄漏模拟:分配了${leak.length}MB`
          }]
        };

      default:
        throw new Error(`未知故障类型: ${scenario}`);
    }
  }
);

12.2 演练场景矩阵

演练 命令 预期行为
超时保护 chaos_test scenario=timeout durationMs=3000 3秒后超时,返回错误信息
熔断触发 连续调用5次错误接口 第6次被熔断器直接拒绝
降级切换 debug_api 访问不可达URL 自动切换到备用逻辑或缓存
内存告警 chaos_test scenario=memory_leak 健康检查显示内存告警
指标记录 执行任意工具后 get_metrics 看到完整的延迟和成功率数据

架构总结

graph TB
    subgraph "Claude Code 客户端"
        CC["Claude Code"]
    end

    subgraph "韧性MCP Server"
        ENTRY["工具调用入口"]

        subgraph "六层防护链"
            M["第6层<br/>MetricsCollector<br/>指标收集"]
            CB["第5层<br/>CircuitBreaker<br/>熔断器"]
            GD["第4层<br/>GracefulDegradation<br/>优雅降级"]
            TM["第3层<br/>TimeoutManager<br/>超时管理"]
            CP["第2层<br/>ConnectionPool<br/>连接池"]
            HC["第1层<br/>HealthChecker<br/>健康检查"]
        end

        TOOLS["工具集<br/>debug_api | health_check<br/>get_metrics | circuit_breaker_status"]
        CHAOS["故障演练<br/>chaos_test"]
    end

    subgraph "外部依赖"
        API["外部API"]
        DB["数据库"]
        CACHE["缓存"]
    end

    CC -->|"stdio"| ENTRY
    ENTRY --> M --> CB --> GD --> TM --> CP --> HC --> TOOLS
    TOOLS --> API
    TOOLS --> DB
    GD -.->|"降级"| CACHE
    CHAOS --> TOOLS

    CB -.->|"熔断"| API
    TM -.->|"超时中断"| API

最佳实践清单

必须做: - 所有连接使用 try/finally 释放 - 所有外部调用设置超时 - 生产环境使用看门狗进程 - stderr 输出结构化日志(stdout 只能用于 MCP 消息) - 处理 uncaughtException 和 unhandledRejection

推荐做: - 为不同外部服务配置独立熔断器 - 实现健康检查工具,支持外部监控 - 保留最近1000次调用的延迟数据用于百分位计算 - 使用 PM2 或 systemd 管理进程生命周期

绝对不做: - 在 stdout 打印日志(会破坏 MCP 协议通信) - 忽略 Promise 拒绝(会导致静默失败) - 使用无限重试(会导致级联故障) - 在熔断器 Open 状态下继续请求(浪费资源)

延伸阅读

训练营适用性评估


参考来源

  1. MCP Reliability Patterns - DeepWiki — 生产级MCP Server可靠性模式详解,涵盖健康检查、熔断器、连接池
  2. Building agents that reach production systems with MCP - Anthropic Blog — Anthropic官方分享MCP生产集成模式,SDK下载量超3亿/月
  3. MCP Server Observability: Monitoring, Testing & Performance Metrics - Zeo — 基于16,400+实现分析的三层可观测性框架
  4. How to Build an MCP Server in 2026: 5 Patterns I Actually Shipped - Build to Launch — 五种MCP Server交付模式(本地/npm/.mcpb/Bearer/OAuth)实战经验
  5. MCP Sampling Specification - modelcontextprotocol.io — MCP Sampling原语官方规范,服务端请求LLM回调的协议定义

[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。

51学通信知识星球海报-扫码加入