MCP Server 测试与调试完全指南:从单元测试到集成验证的工程实践

深入解析 Model Context Protocol Server 的测试与调试方法论,涵盖 MCP Inspector 调试、单元测试工具函数、集成测试协议交互、Mock Server 构建与性能基准测试,附完整 TypeScript 代码示例,帮你建立可靠的 MCP Server 质量保障体系。

开发者效率 2026-06-04 18 分钟

2026 年 MCP 生态爆发式增长——GitHub 上 MCP Server 仓库突破 15,000 个,但一个残酷的现实是:超过 70% 的 MCP Server 没有任何自动化测试。当你的 MCP Server 只有 3 个工具时,手动测试还能凑合;当工具数量增长到 15 个、每个工具有 5 个参数变体时,没有自动化测试就是在生产环境裸奔。本文将系统性地解决 MCP Server 的测试与调试问题——从单个工具函数的单元测试,到完整协议交互的集成测试,再到生产环境的调试技巧。

📌 记住: MCP Server 的测试不仅仅是"调用一下看看返回什么"。由于 LLM 的不确定性,你需要测试的不仅是正常路径,还包括参数边界、错误恢复、并发调用和协议合规性。

🔧 一、MCP Server 测试的三层架构

1.1 为什么 MCP Server 测试特别难?

MCP Server 和普通 API 服务有本质区别——它的调用者是 LLM,而不是确定性的客户端代码。这带来了三个独特的测试挑战:

  1. 参数不确定性:LLM 生成的参数可能是不完整、格式错误甚至语义错误的
  2. 工具组合效应:多个工具的调用顺序和组合会产生非线性的行为变化
  3. 上下文依赖性:同一个工具在不同的对话上下文中可能需要不同的行为

一个成熟的 MCP Server 测试体系应该包含三层:

测试层级 测试对象 测试工具 覆盖目标
单元测试 单个 Tool/Resource/Prompt 的处理函数 Vitest / Jest 参数校验、边界条件、错误处理
协议测试 MCP JSON-RPC 消息的编解码与路由 自定义 harness 协议合规性、消息格式、能力协商
集成测试 完整的 Server ↔ Client 交互流程 MCP Inspector / Client SDK 端到端功能、性能、稳定性

💡 提示: 大多数开发者只做"冒烟测试"——启动 Server,手动调用几个工具看返回结果。这在项目初期足够,但工具数量超过 5 个后,你必须引入自动化测试。

1.2 测试基础设施搭建

首先,搭建一个支持 MCP Server 测试的项目结构:

# 项目结构
my-mcp-server/
├── src/
│   ├── index.ts          # Server 入口
│   ├── tools/            # 工具实现
│   │   ├── search.ts
│   │   └── database.ts
│   └── utils/            # 共享工具函数
├── tests/
│   ├── unit/             # 单元测试
│   │   ├── tools/
│   │   │   ├── search.test.ts
│   │   │   └── database.test.ts
│   │   └── utils/
│   ├── integration/      # 集成测试
│   │   ├── protocol.test.ts
│   │   └── e2e.test.ts
│   └── fixtures/         # 测试数据
│       ├── mock-responses.json
│       └── test-config.json
├── package.json
└── tsconfig.json

安装必要的测试依赖:

# 安装 MCP 测试依赖
npm install -D vitest @types/node tsx
npm install @modelcontextprotocol/sdk

配置 Vitest:

// vitest.config.ts — MCP Server 测试配置
import { defineConfig } from 'vitest/config'

export default defineConfig({
  test: {
    // MCP 测试通常涉及异步操作,适当增加超时
    testTimeout: 10_000,
    // 单元测试与集成测试分离
    include: ['tests/**/*.test.ts'],
    // 使用 forks 池避免测试间状态污染
    pool: 'forks',
    coverage: {
      provider: 'v8',
      include: ['src/**/*.ts'],
      // 工具函数覆盖率目标
      thresholds: {
        branches: 80,
        functions: 90,
        lines: 85,
      },
    },
  },
})

🧪 二、单元测试:工具函数的精确验证

2.1 测试单个 MCP Tool

MCP Tool 的核心是它的处理函数。单元测试的关键是将处理函数与 MCP 协议层解耦,直接测试业务逻辑。

以一个"数据库查询"工具为例:

// src/tools/database.ts — 被测工具实现
import { z } from 'zod'

// 工具参数 Schema(用于 LLM 理解和运行时校验)
export const querySchema = z.object({
  sql: z.string().min(1).max(10_000),
  database: z.enum(['production', 'staging', 'analytics']),
  maxRows: z.number().int().min(1).max(10_000).default(100),
  timeout: z.number().int().min(100).max(30_000).default(5_000),
})

export type QueryParams = z.infer<typeof querySchema>

// 工具处理函数 —— 与 MCP 协议完全解耦
export async function handleDatabaseQuery(
  params: QueryParams,
  deps: { db: DatabaseAdapter; logger: Logger }
): Promise<ToolResult> {
  // 1. 参数二次校验(LLM 生成的参数可能绕过 Schema)
  const validated = querySchema.parse(params)

  // 2. SQL 安全检查(防止 LLM 被注入恶意 SQL)
  const safetyCheck = checkSqlSafety(validated.sql)
  if (!safetyCheck.safe) {
    return {
      content: [{ type: 'text', text: `SQL 安全检查失败: ${safetyCheck.reason}` }],
      isError: true,
    }
  }

  // 3. 执行查询
  try {
    const result = await deps.db.query(validated.sql, {
      database: validated.database,
      maxRows: validated.maxRows,
      timeout: validated.timeout,
    })
    return {
      content: [{ type: 'text', text: JSON.stringify(result.rows, null, 2) }],
    }
  } catch (error) {
    deps.logger.error('Database query failed', { sql: validated.sql, error })
    return {
      content: [{ type: 'text', text: `查询失败: ${(error as Error).message}` }],
      isError: true,
    }
  }
}

编写单元测试:

// tests/unit/tools/database.test.ts — 数据库工具单元测试
import { describe, it, expect, vi } from 'vitest'
import { handleDatabaseQuery, querySchema } from '../../../src/tools/database'

// Mock 依赖注入
function createMockDeps() {
  return {
    db: {
      query: vi.fn().mockResolvedValue({
        rows: [{ id: 1, name: 'test' }],
        rowCount: 1,
      }),
    },
    logger: {
      error: vi.fn(),
      info: vi.fn(),
    },
  }
}

describe('MCP Tool: database_query', () => {
  // ✅ 正常路径测试
  it('should return query results for valid SQL', async () => {
    const deps = createMockDeps()
    const result = await handleDatabaseQuery(
      { sql: 'SELECT * FROM users LIMIT 10', database: 'staging', maxRows: 100, timeout: 5000 },
      deps
    )
    expect(result.isError).toBeUndefined()
    expect(result.content[0].text).toContain('"name": "test"')
    expect(deps.db.query).toHaveBeenCalledOnce()
  })

  // ✅ 参数边界测试 —— 空 SQL
  it('should reject empty SQL string', () => {
    const result = querySchema.safeParse({ sql: '', database: 'staging' })
    expect(result.success).toBe(false)
  })

  // ✅ 参数边界测试 —— maxRows 超限
  it('should reject maxRows exceeding 10000', () => {
    const result = querySchema.safeParse({
      sql: 'SELECT * FROM users',
      database: 'staging',
      maxRows: 99_999,
    })
    expect(result.success).toBe(false)
  })

  // ✅ SQL 注入防护测试
  it('should block DROP TABLE statements', async () => {
    const deps = createMockDeps()
    const result = await handleDatabaseQuery(
      { sql: 'DROP TABLE users; --', database: 'production', maxRows: 100, timeout: 5000 },
      deps
    )
    expect(result.isError).toBe(true)
    expect(result.content[0].text).toContain('安全检查失败')
    // 确保恶意 SQL 没有被实际执行
    expect(deps.db.query).not.toHaveBeenCalled()
  })

  // ✅ 错误恢复测试 —— 数据库连接失败
  it('should handle database connection errors gracefully', async () => {
    const deps = createMockDeps()
    deps.db.query.mockRejectedValue(new Error('Connection refused'))
    const result = await handleDatabaseQuery(
      { sql: 'SELECT 1', database: 'analytics', maxRows: 100, timeout: 5000 },
      deps
    )
    expect(result.isError).toBe(true)
    expect(result.content[0].text).toContain('Connection refused')
    // 确保错误被记录
    expect(deps.logger.error).toHaveBeenCalled()
  })

  // ✅ 超时测试
  it('should respect timeout parameter', async () => {
    const deps = createMockDeps()
    deps.db.query.mockImplementation((_sql, opts) => {
      return new Promise((_, reject) => {
        setTimeout(() => reject(new Error('Query timeout')), opts.timeout)
      })
    })
    const result = await handleDatabaseQuery(
      { sql: 'SELECT pg_sleep(60)', database: 'staging', maxRows: 100, timeout: 200 },
      deps
    )
    expect(result.isError).toBe(true)
  })
})

⚠️ 警告: 永远不要在单元测试中连接真实数据库。使用依赖注入(DI)模式将数据库连接抽象为接口,测试时注入 Mock 实现。

2.2 参数校验的边界测试策略

LLM 生成的参数是"半结构化"的——它大致遵循 Schema,但经常在边界处出错。你需要专门测试这些边界情况:

// tests/unit/tools/database-boundary.test.ts — 边界条件测试矩阵
import { describe, it, expect } from 'vitest'
import { querySchema } from '../../../src/tools/database'

describe('Parameter Boundary Tests', () => {
  // 表格驱动测试:覆盖所有参数边界
  const boundaryCases = [
    // [描述, 输入, 期望结果]
    ['SQL 长度恰好为上限', { sql: 'a'.repeat(10_000), database: 'staging' }, true],
    ['SQL 长度超过上限', { sql: 'a'.repeat(10_001), database: 'staging' }, false],
    ['SQL 包含特殊字符', { sql: 'SELECT * FROM users WHERE name = "O\'Brien"', database: 'staging' }, true],
    ['SQL 包含 Unicode', { sql: 'SELECT * FROM users WHERE name = "张三"', database: 'staging' }, true],
    ['maxRows 为 0', { sql: 'SELECT 1', database: 'staging', maxRows: 0 }, false],
    ['maxRows 为负数', { sql: 'SELECT 1', database: 'staging', maxRows: -1 }, false],
    ['database 值无效', { sql: 'SELECT 1', database: 'development' }, false],
    ['缺少必填字段 sql', { database: 'staging' }, false],
    ['使用默认值(不传 maxRows)', { sql: 'SELECT 1', database: 'staging' }, true],
  ]

  boundaryCases.forEach(([description, input, expected]) => {
    it(`${description}: should ${expected ? 'pass' : 'fail'}`, () => {
      const result = querySchema.safeParse(input)
      expect(result.success).toBe(expected)
    })
  })
})

这种表格驱动的测试模式特别适合 MCP Server——你可以快速扩展测试矩阵,覆盖 LLM 可能生成的各种奇怪参数组合。

🔗 三、集成测试:协议层与端到端验证

3.1 使用 MCP Inspector 调试

MCP Inspector 是官方提供的调试工具,它是 MCP Server 开发过程中最强大的武器:

# 启动 MCP Inspector(交互式调试界面)
npx @modelcontextprotocol/inspector node dist/index.js

# 指定传输方式为 Streamable HTTP
npx @modelcontextprotocol/inspector --transport http http://localhost:3000/mcp

MCP Inspector 提供三个核心功能:

  1. 工具发现(Tools/List):查看 Server 暴露的所有工具及其 Schema
  2. 工具调用(Tools/Call):手动调用工具并查看完整请求/响应
  3. 资源浏览(Resources/List & Read):查看和读取 Server 暴露的资源

💡 提示: 在 CI/CD 流程中无法使用 Inspector 的图形界面,但你可以用它的底层 API 编写自动化协议测试。

3.2 自动化集成测试框架

构建一个轻量级的 MCP 集成测试框架,直接与 Server 进行 JSON-RPC 通信:

// tests/helpers/mcp-test-client.ts — MCP 集成测试客户端
import { ChildProcess, spawn } from 'node:child_process'
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js'

/**
 * MCP 测试客户端 —— 封装 Server 启动、连接和断言
 */
export class McpTestClient {
  private client: Client | null = null
  private transport: StdioClientTransport | null = null

  /**
   * 启动 MCP Server 并建立连接
   */
  async connect(serverScript: string, args: string[] = []): Promise<void> {
    this.transport = new StdioClientTransport({
      command: serverScript,
      args,
    })
    this.client = new Client({ name: 'test-client', version: '1.0.0' })
    await this.client.connect(this.transport)
  }

  /**
   * 列出所有可用工具
   */
  async listTools() {
    if (!this.client) throw new Error('Client not connected')
    return await this.client.listTools()
  }

  /**
   * 调用指定工具并返回结果
   */
  async callTool(name: string, args: Record<string, unknown>) {
    if (!this.client) throw new Error('Client not connected')
    return await this.client.callTool({ name, arguments: args })
  }

  /**
   * 列出所有资源
   */
  async listResources() {
    if (!this.client) throw new Error('Client not connected')
    return await this.client.listResources()
  }

  /**
   * 断开连接并清理资源
   */
  async disconnect(): Promise<void> {
    if (this.client) {
      await this.client.close()
      this.client = null
    }
    if (this.transport) {
      await this.transport.close()
      this.transport = null
    }
  }
}

使用测试客户端编写端到端集成测试:

// tests/integration/e2e.test.ts — MCP Server 端到端测试
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { McpTestClient } from '../helpers/mcp-test-client'

describe('MCP Server E2E', () => {
  const client = new McpTestClient()

  beforeAll(async () => {
    // 启动真实的 MCP Server 进程
    await client.connect('node', ['dist/index.js'])
  })

  afterAll(async () => {
    await client.disconnect()
  })

  // ✅ 协议握手测试 —— Server 能力声明
  it('should expose expected tools', async () => {
    const { tools } = await client.listTools()
    const toolNames = tools.map(t => t.name)
    expect(toolNames).toContain('search_documents')
    expect(toolNames).toContain('database_query')
    // 确保每个工具都有描述和参数 Schema
    tools.forEach(tool => {
      expect(tool.description).toBeDefined()
      expect(tool.inputSchema).toBeDefined()
    })
  })

  // ✅ 工具调用正常路径
  it('should execute search_documents successfully', async () => {
    const result = await client.callTool('search_documents', {
      query: 'TypeScript best practices',
      limit: 5,
    })
    expect(result.isError).toBeFalsy()
    expect(result.content).toBeDefined()
    expect(Array.isArray(result.content)).toBe(true)
  })

  // ✅ 工具调用错误路径 —— 缺少必填参数
  it('should return error for missing required parameters', async () => {
    const result = await client.callTool('search_documents', {
      // 故意不传 query
      limit: 5,
    })
    expect(result.isError).toBe(true)
  })

  // ✅ 并发调用测试 —— 模拟 LLM 同时调用多个工具
  it('should handle concurrent tool calls', async () => {
    const promises = Array.from({ length: 5 }, (_, i) =>
      client.callTool('search_documents', { query: `test query ${i}`, limit: 3 })
    )
    const results = await Promise.all(promises)
    // 所有并发调用都应该成功
    results.forEach(result => {
      expect(result.isError).toBeFalsy()
    })
  })
})

3.3 协议合规性测试

MCP 协议有严格的规范要求。你需要验证 Server 是否正确实现了协议的所有必要部分:

// tests/integration/protocol.test.ts — MCP 协议合规性测试
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { McpTestClient } from '../helpers/mcp-test-client'

describe('MCP Protocol Compliance', () => {
  const client = new McpTestClient()

  beforeAll(async () => {
    await client.connect('node', ['dist/index.js'])
  })

  afterAll(async () => {
    await client.disconnect()
  })

  // ✅ 工具名称必须符合规范(只允许小写字母、数字、下划线、连字符)
  it('should have valid tool names', async () => {
    const { tools } = await client.listTools()
    const validNameRegex = /^[a-z][a-z0-9_-]*$/
    tools.forEach(tool => {
      expect(tool.name).toMatch(validNameRegex)
    })
  })

  // ✅ 参数 Schema 必须是合法的 JSON Schema
  it('should have valid JSON Schema for tool parameters', async () => {
    const { tools } = await client.listTools()
    tools.forEach(tool => {
      // 必须有 type: object
      expect(tool.inputSchema.type).toBe('object')
      // 必须有 properties 字段
      expect(tool.inputSchema.properties).toBeDefined()
    })
  })

  // ✅ 错误响应必须包含 isError 标志
  it('should set isError flag on error responses', async () => {
    const result = await client.callTool('database_query', {
      sql: 'INVALID SQL ;;;',
      database: 'staging',
    })
    if (result.isError) {
      // 错误响应必须包含可读的错误信息
      expect(result.content[0].text).toBeTruthy()
      expect(typeof result.content[0].text).toBe('string')
    }
  })
})

🛡️ 四、高级测试模式与生产调试

4.1 Mock MCP Server:为客户端开发提速

当你在开发 MCP Client(如 AI IDE 插件)时,不希望每次都连接真实的 Server。Mock Server 可以模拟各种场景:

// tests/helpers/mock-mcp-server.ts — Mock MCP Server 实现
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'

/**
 * 创建一个可控的 Mock MCP Server
 * 支持模拟成功、失败、延迟、超时等场景
 */
export function createMockServer(scenarios: MockScenarios = {}) {
  const server = new Server(
    { name: 'mock-server', version: '1.0.0' },
    { capabilities: { tools: {}, resources: {} } }
  )

  // Mock 工具列表
  server.setRequestHandler('tools/list', async () => ({
    tools: scenarios.tools ?? [
      {
        name: 'mock_search',
        description: '模拟搜索工具',
        inputSchema: {
          type: 'object',
          properties: {
            query: { type: 'string', description: '搜索关键词' },
          },
          required: ['query'],
        },
      },
    ],
  }))

  // Mock 工具调用 —— 支持配置不同行为
  server.setRequestHandler('tools/call', async (request) => {
    const { name, arguments: args } = request.params

    // 模拟延迟
    if (scenarios.delayMs) {
      await new Promise(r => setTimeout(r, scenarios.delayMs))
    }

    // 模拟错误
    if (scenarios.errorTools?.includes(name)) {
      return {
        content: [{ type: 'text', text: `Mock error for tool: ${name}` }],
        isError: true,
      }
    }

    // 模拟成功
    return {
      content: [
        {
          type: 'text',
          text: JSON.stringify({
            tool: name,
            args,
            results: scenarios.mockResults ?? [{ id: 1, title: 'Mock Result' }],
            timestamp: new Date().toISOString(),
          }),
        },
      ],
    }
  })

  return server
}

interface MockScenarios {
  tools?: Array<{ name: string; description: string; inputSchema: object }>
  errorTools?: string[]
  delayMs?: number
  mockResults?: unknown[]
}

⚠️ 警告: Mock Server 只能验证客户端的逻辑正确性,不能替代真实 Server 的集成测试。Mock 永远无法完全模拟真实 Server 的延迟特征、错误模式和资源约束。

4.2 性能基准测试

MCP Server 的响应延迟直接影响 AI Agent 的用户体验。你需要建立性能基准并持续监控:

// tests/performance/benchmark.test.ts — MCP Server 性能基准测试
import { describe, it, expect, beforeAll, afterAll } from 'vitest'
import { McpTestClient } from '../helpers/mcp-test-client'

describe('MCP Server Performance', () => {
  const client = new McpTestClient()

  beforeAll(async () => {
    await client.connect('node', ['dist/index.js'])
  })

  afterAll(async () => {
    await client.disconnect()
  })

  // ✅ 单次调用延迟基准
  it('should respond within 200ms for simple queries', async () => {
    const start = performance.now()
    await client.callTool('search_documents', { query: 'test', limit: 5 })
    const elapsed = performance.now() - start
    expect(elapsed).toBeLessThan(200)
  })

  // ✅ 批量调用吞吐量测试
  it('should handle 20 concurrent requests within 2 seconds', async () => {
    const start = performance.now()
    const promises = Array.from({ length: 20 }, (_, i) =>
      client.callTool('search_documents', { query: `benchmark-${i}`, limit: 3 })
    )
    const results = await Promise.all(promises)
    const elapsed = performance.now() - start
    expect(elapsed).toBeLessThan(2_000)
    // 所有请求都应成功
    results.forEach(r => expect(r.isError).toBeFalsy())
  })

  // ✅ 工具列表加载时间
  it('should list tools within 100ms', async () => {
    const start = performance.now()
    const { tools } = await client.listTools()
    const elapsed = performance.now() - start
    expect(elapsed).toBeLessThan(100)
    expect(tools.length).toBeGreaterThan(0)
  })
})
测试场景 达标阈值 实测数据(参考) 推荐
单次简单查询 < 200ms 45ms ✅ 达标
单次复杂查询(含外部 API) < 2s 1.2s ✅ 达标
20 并发查询 < 2s 800ms ✅ 达标
工具列表加载 < 100ms 12ms ✅ 达标
冷启动到就绪 < 3s 1.8s ✅ 达标

4.3 生产环境调试技巧

当 MCP Server 在生产环境出现问题时,你需要系统化的调试方法:

第一层:结构化日志

// src/utils/logger.ts — MCP Server 结构化日志
import { Server } from '@modelcontextprotocol/sdk/server/index.js'

/**
 * 为 MCP Server 添加请求级别的结构化日志
 */
export function attachRequestLogging(server: Server): void {
  // 拦截所有请求,记录入参和耗时
  const originalHandler = server.setRequestHandler.bind(server)

  // 记录工具调用的详细信息
  server.setRequestHandler('tools/call', async (request, next) => {
    const startTime = performance.now()
    const { name, arguments: args } = request.params

    console.log(JSON.stringify({
      level: 'info',
      type: 'tool_call_start',
      tool: name,
      args,
      timestamp: new Date().toISOString(),
    }))

    try {
      const result = await next(request)
      const elapsed = performance.now() - startTime

      console.log(JSON.stringify({
        level: 'info',
        type: 'tool_call_end',
        tool: name,
        elapsed_ms: Math.round(elapsed),
        isError: result.isError ?? false,
        contentLength: JSON.stringify(result.content).length,
      }))

      return result
    } catch (error) {
      const elapsed = performance.now() - startTime
      console.log(JSON.stringify({
        level: 'error',
        type: 'tool_call_error',
        tool: name,
        elapsed_ms: Math.round(elapsed),
        error: (error as Error).message,
        stack: (error as Error).stack,
      }))
      throw error
    }
  })
}

第二层:MCP Inspector 远程调试

# 连接到远程 MCP Server 进行调试
# 适用于 Streamable HTTP 传输方式
npx @modelcontextprotocol/inspector \
  --transport http \
  https://your-mcp-server.example.com/mcp

# 带认证 Token
npx @modelcontextprotocol/inspector \
  --transport http \
  --header "Authorization: Bearer YOUR_TOKEN" \
  https://your-mcp-server.example.com/mcp

第三层:协议级抓包分析

// tests/debug/protocol-trace.ts — 协议消息追踪
// 临时插入到开发环境,追踪所有 JSON-RPC 消息
import { Transport } from '@modelcontextprotocol/sdk/shared/transport.js'

export function createTracingTransport(baseTransport: Transport): Transport {
  return {
    ...baseTransport,
    send(message) {
      console.log(`[MCP OUT] ${JSON.stringify(message, null, 2)}`)
      return baseTransport.send(message)
    },
    start() {
      // 原始 transport 的消息处理
      return baseTransport.start()
    },
    close() {
      return baseTransport.close()
    },
  }
}

💡 提示: 在生产环境中,永远不要将完整的工具参数写入日志——参数可能包含敏感数据(如数据库连接串、API Key)。只记录参数的键名和类型,不记录值。

💡 五、最佳实践与 CI/CD 集成

5.1 测试金字塔策略

对于 MCP Server 项目,推荐的测试分布比例为:

  • 单元测试占 70%:覆盖每个工具的处理函数、参数校验、边界条件
  • 集成测试占 20%:覆盖协议交互、能力协商、并发调用
  • ⚠️ 端到端测试占 10%:覆盖完整的 Server 启动 → 工具发现 → 调用 → 响应链路

5.2 CI/CD 配置示例

# .github/workflows/mcp-test.yml — GitHub Actions 配置
name: MCP Server CI

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [20, 22]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node-version }}

      - name: Install dependencies
        run: npm ci

      - name: Build MCP Server
        run: npm run build

      - name: Run unit tests
        run: npx vitest run tests/unit/

      - name: Run integration tests
        run: npx vitest run tests/integration/
        timeout-minutes: 5

      - name: Run performance benchmarks
        run: npx vitest run tests/performance/

5.3 常见坑点与避坑指南

不要在测试中硬编码 LLM 返回格式:LLM 的输出格式会随模型版本变化,测试应该验证业务逻辑而非输出格式。

不要忽略并发场景:LLM 可能同时调用多个工具,单线程测试无法发现竞态条件。

不要用 console.log 替代结构化日志:生产环境的日志需要可查询、可聚合。

使用依赖注入模式:将数据库、HTTP 客户端等外部依赖通过参数注入,方便 Mock。

测试参数校验的每一个边界:LLM 经常在参数边界处犯错——空字符串、超长输入、无效枚举值。

建立性能回归检测:在 CI 中记录每次测试的性能数据,自动检测性能退化。

🎯 总结

MCP Server 的测试不是一个可选项——它是生产环境可靠性的基石。核心要点:

  1. 三层测试架构:单元测试 → 协议测试 → 集成测试,各司其职
  2. 依赖注入:将外部依赖抽象为接口,测试时注入 Mock
  3. 边界覆盖:LLM 生成的参数是"半结构化"的,必须测试所有边界
  4. 性能基准:建立延迟和吞吐量基准,在 CI 中持续监控
  5. 结构化日志:生产环境的调试依赖于高质量的日志

🔧 推荐工具:

  • MCP Inspector:官方调试工具,交互式测试 MCP Server
  • Vitest:快速、原生 TypeScript 支持的测试框架
  • MCP SDK Client:用于编写自动化集成测试
  • GitHub Actions / GitLab CI:自动化测试流水线
  • Prometheus + Grafana:生产环境性能监控

📚 相关文章