{"product_id":"testing-va-debug-mcp-server-dảm-bảo-chất-lượng-cho-production","title":"Testing và Debug MCP Server — Đảm bảo chất lượng cho production","description":"\n\u003cp\u003eXây dựng MCP server là bước đầu tiên. Đảm bảo nó hoạt động đúng, ổn định và sẵn sàng cho production là một thách thức hoàn toàn khác. MCP server chạy như một process riêng biệt, giao tiếp qua JSON-RPC, và được gọi bởi AI model — tạo ra nhiều điểm có thể xảy ra lỗi mà testing truyền thống không bao phủ. Bài viết này hướng dẫn bạn xây dựng test suite hoàn chỉnh cho MCP server, từ unit test đến CI\/CD.\u003c\/p\u003e\n\n\u003ch2\u003eTại sao testing MCP server đặc biệt quan trọng?\u003c\/h2\u003e\n\u003cp\u003eMCP server khác biệt so với API thông thường ở nhiều điểm:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eCaller là AI model:\u003c\/strong\u003e Claude có thể gọi tool với input không lường trước, kết hợp các tool theo cách bạn chưa nghĩ đến\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eSchema là contract:\u003c\/strong\u003e Nếu schema sai, Claude sẽ gửi input sai format và tool sẽ fail silently hoặc trả kết quả sai\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eError propagation phức tạp:\u003c\/strong\u003e Lỗi từ MCP server được truyền qua JSON-RPC rồi mới đến Claude, dễ bị mất context\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eSide effects nguy hiểm:\u003c\/strong\u003e MCP tool có thể thực hiện các thao tác không thể hoàn tác (gửi email, xóa dữ liệu, deploy code)\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eConcurrency:\u003c\/strong\u003e Nhiều tool call có thể xảy ra đồng thời\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eUnit Testing MCP Tools\u003c\/h2\u003e\n\u003cp\u003eUnit test cho MCP tools tập trung vào việc kiểm tra logic xử lý của từng tool handler. Không cần khởi động MCP server hay kết nối Claude — chỉ test hàm xử lý thuần túy.\u003c\/p\u003e\n\n\u003ch3\u003eThiết lập test environment\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Cai dat dependencies\n\/\/ npm install --save-dev vitest @types\/node\n\n\/\/ vitest.config.ts\nimport { defineConfig } from \"vitest\/config\";\n\nexport default defineConfig({\n  test: {\n    globals: true,\n    environment: \"node\",\n    coverage: {\n      provider: \"v8\",\n      reporter: [\"text\", \"json\", \"html\"],\n      thresholds: {\n        lines: 80,\n        functions: 80,\n        branches: 75,\n      },\n    },\n  },\n});\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eTách logic khỏi MCP framework\u003c\/h3\u003e\n\u003cp\u003eNguyên tắc quan trọng nhất: tách business logic ra khỏi MCP server definition. Điều này giúp test logic mà không cần MCP framework.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ src\/handlers\/search-products.ts\n\/\/ Logic thuan tuy, khong phu thuoc MCP\nexport interface SearchParams {\n  query: string;\n  category?: string;\n  maxPrice?: number;\n  limit?: number;\n}\n\nexport interface Product {\n  id: string;\n  name: string;\n  price: number;\n  category: string;\n}\n\nexport async function searchProducts(\n  params: SearchParams,\n  db: DatabaseClient\n): Promise\u0026lt;Product[]\u0026gt; {\n  const { query, category, maxPrice, limit = 20 } = params;\n\n  if (!query || query.trim().length === 0) {\n    throw new Error(\"Query khong duoc de trong\");\n  }\n\n  if (limit \u0026lt; 1 || limit \u0026gt; 100) {\n    throw new Error(\"Limit phai tu 1 den 100\");\n  }\n\n  let sql = \"SELECT * FROM products WHERE name ILIKE $1\";\n  const sqlParams: any[] = [\"%\" + query + \"%\"];\n\n  if (category) {\n    sql += \" AND category = $\" + (sqlParams.length + 1);\n    sqlParams.push(category);\n  }\n\n  if (maxPrice !== undefined) {\n    sql += \" AND price \u0026lt;= $\" + (sqlParams.length + 1);\n    sqlParams.push(maxPrice);\n  }\n\n  sql += \" LIMIT $\" + (sqlParams.length + 1);\n  sqlParams.push(limit);\n\n  const result = await db.query(sql, sqlParams);\n  return result.rows;\n}\n\n\n\/\/ src\/index.ts - MCP server chi la wrapper mong\nimport { McpServer } from \"@modelcontextprotocol\/sdk\/server\/mcp.js\";\nimport { z } from \"zod\";\nimport { searchProducts } from \".\/handlers\/search-products.js\";\n\nserver.tool(\n  \"search_products\",\n  \"Tim kiem san pham\",\n  {\n    query: z.string(),\n    category: z.string().optional(),\n    maxPrice: z.number().optional(),\n    limit: z.number().optional(),\n  },\n  async (params) =\u0026gt; {\n    const results = await searchProducts(params, db);\n    return {\n      content: [{ type: \"text\", text: JSON.stringify(results) }],\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eViết unit test\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ tests\/handlers\/search-products.test.ts\nimport { describe, it, expect, vi, beforeEach } from \"vitest\";\nimport { searchProducts, SearchParams } from\n  \"..\/..\/src\/handlers\/search-products.js\";\n\n\/\/ Mock database client\nconst mockDb = {\n  query: vi.fn(),\n};\n\ndescribe(\"searchProducts\", () =\u0026gt; {\n  beforeEach(() =\u0026gt; {\n    vi.clearAllMocks();\n  });\n\n  it(\"tra ve san pham phu hop voi tu khoa\", async () =\u0026gt; {\n    const mockProducts = [\n      { id: \"1\", name: \"iPhone 15\", price: 25000000,\n        category: \"dien-thoai\" },\n      { id: \"2\", name: \"iPhone 14\", price: 20000000,\n        category: \"dien-thoai\" },\n    ];\n    mockDb.query.mockResolvedValue({ rows: mockProducts });\n\n    const result = await searchProducts(\n      { query: \"iPhone\" }, mockDb as any\n    );\n\n    expect(result).toEqual(mockProducts);\n    expect(mockDb.query).toHaveBeenCalledWith(\n      expect.stringContaining(\"ILIKE\"),\n      expect.arrayContaining([\"%iPhone%\"])\n    );\n  });\n\n  it(\"throw error khi query rong\", async () =\u0026gt; {\n    await expect(\n      searchProducts({ query: \"\" }, mockDb as any)\n    ).rejects.toThrow(\"Query khong duoc de trong\");\n  });\n\n  it(\"throw error khi limit vuot gioi han\", async () =\u0026gt; {\n    await expect(\n      searchProducts({ query: \"test\", limit: 200 }, mockDb as any)\n    ).rejects.toThrow(\"Limit phai tu 1 den 100\");\n  });\n\n  it(\"ap dung filter category khi co\", async () =\u0026gt; {\n    mockDb.query.mockResolvedValue({ rows: [] });\n\n    await searchProducts(\n      { query: \"phone\", category: \"dien-thoai\" },\n      mockDb as any\n    );\n\n    expect(mockDb.query).toHaveBeenCalledWith(\n      expect.stringContaining(\"category = $2\"),\n      expect.arrayContaining([\"%phone%\", \"dien-thoai\"])\n    );\n  });\n\n  it(\"ap dung filter maxPrice khi co\", async () =\u0026gt; {\n    mockDb.query.mockResolvedValue({ rows: [] });\n\n    await searchProducts(\n      { query: \"laptop\", maxPrice: 30000000 },\n      mockDb as any\n    );\n\n    expect(mockDb.query).toHaveBeenCalledWith(\n      expect.stringContaining(\"price \u0026lt;=\"),\n      expect.arrayContaining([30000000])\n    );\n  });\n\n  it(\"mac dinh limit la 20\", async () =\u0026gt; {\n    mockDb.query.mockResolvedValue({ rows: [] });\n\n    await searchProducts({ query: \"test\" }, mockDb as any);\n\n    expect(mockDb.query).toHaveBeenCalledWith(\n      expect.any(String),\n      expect.arrayContaining([20])\n    );\n  });\n});\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eIntegration Testing với MCP Protocol\u003c\/h2\u003e\n\u003cp\u003eIntegration test kiểm tra toàn bộ luồng từ MCP request đến response, bao gồm serialization, schema validation và error handling.\u003c\/p\u003e\n\n\u003ch3\u003eTest MCP server end-to-end\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ tests\/integration\/server.test.ts\nimport { describe, it, expect, beforeAll, afterAll } from \"vitest\";\nimport { Client } from \"@modelcontextprotocol\/sdk\/client\/index.js\";\nimport { StdioClientTransport } from\n  \"@modelcontextprotocol\/sdk\/client\/stdio.js\";\nimport { spawn } from \"child_process\";\n\ndescribe(\"MCP Server Integration\", () =\u0026gt; {\n  let client: Client;\n  let transport: StdioClientTransport;\n\n  beforeAll(async () =\u0026gt; {\n    transport = new StdioClientTransport({\n      command: \"node\",\n      args: [\"dist\/index.js\"],\n      env: {\n        ...process.env,\n        DATABASE_URL: \"postgresql:\/\/localhost\/test_db\",\n      },\n    });\n\n    client = new Client(\n      { name: \"test-client\", version: \"1.0.0\" },\n      { capabilities: {} }\n    );\n\n    await client.connect(transport);\n  });\n\n  afterAll(async () =\u0026gt; {\n    await client.close();\n  });\n\n  it(\"liet ke tat ca tools\", async () =\u0026gt; {\n    const result = await client.listTools();\n    expect(result.tools).toBeInstanceOf(Array);\n    expect(result.tools.length).toBeGreaterThan(0);\n\n    const toolNames = result.tools.map(t =\u0026gt; t.name);\n    expect(toolNames).toContain(\"search_products\");\n    expect(toolNames).toContain(\"get_product_detail\");\n  });\n\n  it(\"tool co schema hop le\", async () =\u0026gt; {\n    const result = await client.listTools();\n    const searchTool = result.tools.find(\n      t =\u0026gt; t.name === \"search_products\"\n    );\n\n    expect(searchTool).toBeDefined();\n    expect(searchTool!.inputSchema.properties).toHaveProperty(\"query\");\n    expect(searchTool!.inputSchema.required).toContain(\"query\");\n  });\n\n  it(\"goi tool thanh cong voi input hop le\", async () =\u0026gt; {\n    const result = await client.callTool(\"search_products\", {\n      query: \"test\",\n      limit: 5,\n    });\n\n    expect(result.content).toBeInstanceOf(Array);\n    expect(result.content[0].type).toBe(\"text\");\n\n    const data = JSON.parse(result.content[0].text);\n    expect(data).toBeInstanceOf(Array);\n  });\n\n  it(\"tra ve loi khi input khong hop le\", async () =\u0026gt; {\n    const result = await client.callTool(\"search_products\", {\n      query: \"\",\n    });\n\n    expect(result.isError).toBe(true);\n  });\n});\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eMCP Inspector — Công cụ debug chính thức\u003c\/h2\u003e\n\u003cp\u003eMCP Inspector là công cụ chính thức từ Anthropic để debug MCP server. Nó cung cấp giao diện web cho phép bạn tương tác trực tiếp với MCP server, xem request\/response, và test tool calls.\u003c\/p\u003e\n\n\u003ch3\u003eCài đặt và sử dụng\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e# Chay MCP Inspector\nnpx @modelcontextprotocol\/inspector\n\n# Hoac chi dinh server cu the\nnpx @modelcontextprotocol\/inspector node dist\/index.js\u003c\/code\u003e\u003c\/pre\u003e\n\u003cp\u003eMCP Inspector cung cấp:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTool Explorer:\u003c\/strong\u003e Liệt kê tất cả tools với schema, cho phép test từng tool bằng form input\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eResource Browser:\u003c\/strong\u003e Duyệt các resources mà server cung cấp\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003ePrompt Templates:\u003c\/strong\u003e Xem và test các prompt templates\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eRequest\/Response Log:\u003c\/strong\u003e Xem toàn bộ giao tiếp JSON-RPC giữa client và server\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eError Details:\u003c\/strong\u003e Hiển thị chi tiết lỗi với stack trace\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch3\u003eDebug workflow với Inspector\u003c\/h3\u003e\n\u003cp\u003eQuy trình debug hiệu quả với MCP Inspector:\u003c\/p\u003e\n\u003col\u003e\n  \u003cli\u003eKhởi động Inspector kết nối tới MCP server\u003c\/li\u003e\n  \u003cli\u003eKiểm tra danh sách tools — xác nhận tool xuất hiện đúng tên và schema\u003c\/li\u003e\n  \u003cli\u003eTest tool với input đơn giản — xác nhận response format đúng\u003c\/li\u003e\n  \u003cli\u003eTest với edge cases — input rỗng, giá trị biên, ký tự đặc biệt\u003c\/li\u003e\n  \u003cli\u003eKiểm tra error handling — gửi input sai format, thiếu required fields\u003c\/li\u003e\n  \u003cli\u003eXem log JSON-RPC — kiểm tra request\/response payload\u003c\/li\u003e\n\u003c\/ol\u003e\n\n\u003ch2\u003eCác lỗi thường gặp và cách xử lý\u003c\/h2\u003e\n\n\u003ch3\u003eLỗi 1: Connection errors\u003c\/h3\u003e\n\u003cp\u003eMCP server không khởi động hoặc không kết nối được.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Nguyen nhan thuong gap:\n\/\/ 1. Server crash khi khoi dong do missing dependencies\n\/\/ 2. Port bi chiem (HTTP transport)\n\/\/ 3. Path toi server binary sai\n\n\/\/ Cach debug:\n\/\/ Chay server truc tiep de xem error output\nnode dist\/index.js\n\n\/\/ Kiem tra stderr\n\/\/ MCP server ghi log ra stderr, stdout danh cho JSON-RPC\n\n\/\/ Fix: Them error handling cho startup\nprocess.on(\"uncaughtException\", (err) =\u0026gt; {\n  console.error(\"[MCP Server] Uncaught exception:\", err);\n  process.exit(1);\n});\n\nprocess.on(\"unhandledRejection\", (reason) =\u0026gt; {\n  console.error(\"[MCP Server] Unhandled rejection:\", reason);\n  process.exit(1);\n});\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eLỗi 2: Schema mismatch\u003c\/h3\u003e\n\u003cp\u003eClaude gửi input không khớp với schema mong đợi.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Van de: Schema dinh nghia \"price\" la number\n\/\/ nhung Claude gui string \"25000\"\n\n\/\/ Fix: Su dung Zod coerce de tu dong convert\nserver.tool(\n  \"filter_products\",\n  \"Loc san pham theo gia\",\n  {\n    minPrice: z.coerce.number().optional(),\n    maxPrice: z.coerce.number().optional(),\n    category: z.string().optional(),\n  },\n  async (params) =\u0026gt; {\n    \/\/ params.minPrice va maxPrice luon la number\n    \/\/ du Claude gui string hay number\n    return await filterProducts(params);\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eLỗi 3: Timeout\u003c\/h3\u003e\n\u003cp\u003eTool xử lý quá lâu, dẫn đến timeout.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Van de: Tool goi external API mat 30 giay,\n\/\/ vuot qua timeout cua client\n\n\/\/ Fix 1: Dat timeout cho external calls\nasync function callExternalAPI(url: string, timeoutMs = 10000) {\n  const controller = new AbortController();\n  const timeout = setTimeout(\n    () =\u0026gt; controller.abort(), timeoutMs\n  );\n\n  try {\n    const response = await fetch(url, {\n      signal: controller.signal\n    });\n    return await response.json();\n  } catch (err) {\n    if (err instanceof Error \u0026amp;\u0026amp; err.name === \"AbortError\") {\n      throw new Error(\n        \"Request timeout sau \" + timeoutMs + \"ms\"\n      );\n    }\n    throw err;\n  } finally {\n    clearTimeout(timeout);\n  }\n}\n\n\/\/ Fix 2: Tra ve progress updates cho long-running tasks\nserver.tool(\n  \"generate_report\",\n  \"Tao bao cao - co the mat vai phut\",\n  { reportType: z.string() },\n  async ({ reportType }) =\u0026gt; {\n    \/\/ Chia nho thanh cac buoc va cap nhat tien do\n    const steps = [\"Thu thap du lieu\", \"Phan tich\", \"Tao bao cao\"];\n    const results: string[] = [];\n\n    for (const step of steps) {\n      results.push(await executeStep(step, reportType));\n    }\n\n    return {\n      content: [{\n        type: \"text\",\n        text: JSON.stringify({\n          status: \"completed\",\n          steps: steps.length,\n          result: results.join(\"\n\"),\n        }),\n      }],\n    };\n  }\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eLỗi 4: Memory leak\u003c\/h3\u003e\n\u003cp\u003eMCP server tiêu tốn ngày càng nhiều memory theo thời gian.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ Nguyen nhan thuong gap:\n\/\/ 1. Khong dong database connections\n\/\/ 2. Cache khong co TTL\n\/\/ 3. Event listeners khong duoc remove\n\n\/\/ Fix: Implement resource cleanup\nclass ResourceManager {\n  private resources: Map\u0026lt;string, any\u0026gt; = new Map();\n  private cleanupInterval: NodeJS.Timeout;\n\n  constructor() {\n    \/\/ Cleanup moi 5 phut\n    this.cleanupInterval = setInterval(\n      () =\u0026gt; this.cleanup(), 5 * 60 * 1000\n    );\n  }\n\n  register(id: string, resource: any, ttlMs: number) {\n    this.resources.set(id, {\n      resource,\n      expiresAt: Date.now() + ttlMs,\n    });\n  }\n\n  private cleanup() {\n    const now = Date.now();\n    for (const [id, entry] of this.resources) {\n      if (entry.expiresAt \u0026lt; now) {\n        if (typeof entry.resource.close === \"function\") {\n          entry.resource.close();\n        }\n        this.resources.delete(id);\n      }\n    }\n  }\n\n  dispose() {\n    clearInterval(this.cleanupInterval);\n    for (const [, entry] of this.resources) {\n      if (typeof entry.resource.close === \"function\") {\n        entry.resource.close();\n      }\n    }\n    this.resources.clear();\n  }\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eLogging Best Practices\u003c\/h2\u003e\n\u003cp\u003eLogging cho MCP server cần đặc biệt cẩn thận vì stdout được dùng cho JSON-RPC communication. Tất cả log phải ghi vào stderr hoặc file.\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003e\/\/ src\/logger.ts\nimport { createWriteStream } from \"fs\";\nimport { join } from \"path\";\n\ntype LogLevel = \"debug\" | \"info\" | \"warn\" | \"error\";\n\nclass McpLogger {\n  private logFile;\n  private level: LogLevel;\n\n  private levels: Record\u0026lt;LogLevel, number\u0026gt; = {\n    debug: 0, info: 1, warn: 2, error: 3,\n  };\n\n  constructor(logDir: string, level: LogLevel = \"info\") {\n    this.level = level;\n    const filename = \"mcp-\" + new Date().toISOString().split(\"T\")[0] + \".log\";\n    this.logFile = createWriteStream(\n      join(logDir, filename), { flags: \"a\" }\n    );\n  }\n\n  private log(level: LogLevel, message: string, data?: any) {\n    if (this.levels[level] \u0026lt; this.levels[this.level]) return;\n\n    const entry = {\n      timestamp: new Date().toISOString(),\n      level,\n      message,\n      ...(data \u0026amp;\u0026amp; { data }),\n    };\n\n    \/\/ Ghi ra stderr (khong anh huong JSON-RPC tren stdout)\n    console.error(\"[\" + level.toUpperCase() + \"] \" + message);\n    \/\/ Ghi ra file\n    this.logFile.write(JSON.stringify(entry) + \"\n\");\n  }\n\n  debug(msg: string, data?: any) { this.log(\"debug\", msg, data); }\n  info(msg: string, data?: any) { this.log(\"info\", msg, data); }\n  warn(msg: string, data?: any) { this.log(\"warn\", msg, data); }\n  error(msg: string, data?: any) { this.log(\"error\", msg, data); }\n}\n\nexport const logger = new McpLogger(\n  process.env.LOG_DIR || \"\/tmp\/mcp-logs\",\n  (process.env.LOG_LEVEL as LogLevel) || \"info\"\n);\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch3\u003eNhững gì cần log\u003c\/h3\u003e\n\u003cul\u003e\n  \u003cli\u003e\n\u003cstrong\u003eTool invocations:\u003c\/strong\u003e Tên tool, input parameters (sanitized), thời gian xử lý\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eErrors:\u003c\/strong\u003e Error message, stack trace, input gây lỗi\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eExternal calls:\u003c\/strong\u003e URL, status code, response time\u003c\/li\u003e\n  \u003cli\u003e\n\u003cstrong\u003eResource usage:\u003c\/strong\u003e Số connections active, memory usage, cache hit rate\u003c\/li\u003e\n\u003c\/ul\u003e\n\u003cp\u003eLưu ý: Không log dữ liệu nhạy cảm (passwords, API keys, personal data). Sanitize input trước khi ghi log.\u003c\/p\u003e\n\n\u003ch2\u003ePerformance Benchmarking\u003c\/h2\u003e\n\u003cp\u003eĐo lường hiệu suất MCP server giúp phát hiện bottleneck và đảm bảo response time đáp ứng yêu cầu.\u003c\/p\u003e\n\n\u003cpre\u003e\u003ccode\u003e\/\/ tests\/benchmark\/performance.test.ts\nimport { describe, it, expect } from \"vitest\";\nimport { Client } from \"@modelcontextprotocol\/sdk\/client\/index.js\";\n\ndescribe(\"Performance Benchmarks\", () =\u0026gt; {\n  let client: Client;\n  \/\/ ... setup\n\n  it(\"search_products tra ve trong 200ms\", async () =\u0026gt; {\n    const start = performance.now();\n    await client.callTool(\"search_products\", {\n      query: \"test\", limit: 10,\n    });\n    const duration = performance.now() - start;\n    expect(duration).toBeLessThan(200);\n  });\n\n  it(\"xu ly 50 concurrent requests\", async () =\u0026gt; {\n    const requests = Array.from({ length: 50 }, (_, i) =\u0026gt;\n      client.callTool(\"search_products\", {\n        query: \"test-\" + i, limit: 5,\n      })\n    );\n\n    const start = performance.now();\n    const results = await Promise.all(requests);\n    const duration = performance.now() - start;\n\n    \/\/ Tat ca requests thanh cong\n    expect(results.every(r =\u0026gt; !r.isError)).toBe(true);\n    \/\/ Tong thoi gian duoi 5 giay\n    expect(duration).toBeLessThan(5000);\n  });\n\n  it(\"memory khong tang qua 50MB sau 1000 requests\",\n    async () =\u0026gt; {\n    const before = process.memoryUsage().heapUsed;\n\n    for (let i = 0; i \u0026lt; 1000; i++) {\n      await client.callTool(\"search_products\", {\n        query: \"stress-\" + i, limit: 1,\n      });\n    }\n\n    const after = process.memoryUsage().heapUsed;\n    const increase = (after - before) \/ 1024 \/ 1024; \/\/ MB\n    expect(increase).toBeLessThan(50);\n  });\n});\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eCI\/CD cho MCP Server\u003c\/h2\u003e\n\u003cp\u003eThiết lập CI\/CD pipeline đảm bảo MCP server luôn ở trạng thái deployable.\u003c\/p\u003e\n\n\u003ch3\u003eGitHub Actions workflow\u003c\/h3\u003e\n\u003cpre\u003e\u003ccode\u003e# .github\/workflows\/mcp-server-ci.yml\nname: MCP Server CI\/CD\n\non:\n  push:\n    branches: [main]\n    paths: [\"mcp-server\/**\"]\n  pull_request:\n    branches: [main]\n    paths: [\"mcp-server\/**\"]\n\njobs:\n  test:\n    runs-on: ubuntu-latest\n    services:\n      postgres:\n        image: postgres:16\n        env:\n          POSTGRES_DB: test_db\n          POSTGRES_PASSWORD: test\n        ports: [\"5432:5432\"]\n        options: \u0026gt;-\n          --health-cmd pg_isready\n          --health-interval 10s\n          --health-timeout 5s\n          --health-retries 5\n\n    steps:\n      - uses: actions\/checkout@v4\n      - uses: actions\/setup-node@v4\n        with:\n          node-version: \"20\"\n          cache: \"npm\"\n          cache-dependency-path: mcp-server\/package-lock.json\n\n      - name: Install dependencies\n        run: npm ci\n        working-directory: mcp-server\n\n      - name: Type check\n        run: npx tsc --noEmit\n        working-directory: mcp-server\n\n      - name: Unit tests\n        run: npx vitest run --coverage\n        working-directory: mcp-server\n\n      - name: Build\n        run: npm run build\n        working-directory: mcp-server\n\n      - name: Integration tests\n        run: npx vitest run tests\/integration\n        working-directory: mcp-server\n        env:\n          DATABASE_URL: postgresql:\/\/postgres:test@localhost\/test_db\n\n      - name: Performance tests\n        run: npx vitest run tests\/benchmark\n        working-directory: mcp-server\n        env:\n          DATABASE_URL: postgresql:\/\/postgres:test@localhost\/test_db\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eTest Fixtures cho MCP\u003c\/h2\u003e\n\u003cp\u003eFixtures giúp tạo dữ liệu test nhất quán và dễ maintain.\u003c\/p\u003e\n\u003cpre\u003e\u003ccode\u003e\/\/ tests\/fixtures\/products.ts\nexport const sampleProducts = [\n  {\n    id: \"prod-001\",\n    name: \"MacBook Pro 14 inch\",\n    price: 52000000,\n    category: \"laptop\",\n    stock: 15,\n  },\n  {\n    id: \"prod-002\",\n    name: \"iPhone 15 Pro Max\",\n    price: 34000000,\n    category: \"dien-thoai\",\n    stock: 50,\n  },\n  {\n    id: \"prod-003\",\n    name: \"AirPods Pro 2\",\n    price: 6500000,\n    category: \"phu-kien\",\n    stock: 100,\n  },\n];\n\n\/\/ tests\/fixtures\/setup-db.ts\nimport { Pool } from \"pg\";\nimport { sampleProducts } from \".\/products.js\";\n\nexport async function setupTestDb(pool: Pool) {\n  await pool.query(\"DROP TABLE IF EXISTS products CASCADE\");\n  await pool.query(`\n    CREATE TABLE products (\n      id VARCHAR(20) PRIMARY KEY,\n      name VARCHAR(255) NOT NULL,\n      price INTEGER NOT NULL,\n      category VARCHAR(50),\n      stock INTEGER DEFAULT 0\n    )\n  `);\n\n  for (const product of sampleProducts) {\n    await pool.query(\n      \"INSERT INTO products VALUES ($1, $2, $3, $4, $5)\",\n      [product.id, product.name, product.price,\n       product.category, product.stock]\n    );\n  }\n}\n\nexport async function teardownTestDb(pool: Pool) {\n  await pool.query(\"DROP TABLE IF EXISTS products CASCADE\");\n  await pool.end();\n}\u003c\/code\u003e\u003c\/pre\u003e\n\n\u003ch2\u003eChecklist trước khi deploy production\u003c\/h2\u003e\n\u003cp\u003eTrước khi đưa MCP server lên production, hãy đảm bảo đã hoàn thành các mục sau:\u003c\/p\u003e\n\u003cul\u003e\n  \u003cli\u003eUnit test coverage trên 80% cho tất cả tool handlers\u003c\/li\u003e\n  \u003cli\u003eIntegration test verify schema, response format, error handling\u003c\/li\u003e\n  \u003cli\u003ePerformance test đảm bảo response time dưới ngưỡng chấp nhận\u003c\/li\u003e\n  \u003cli\u003eLogging hoạt động đúng, ghi ra stderr hoặc file (không phải stdout)\u003c\/li\u003e\n  \u003cli\u003eError handling cho tất cả edge cases (input rỗng, null, quá dài)\u003c\/li\u003e\n  \u003cli\u003eRate limiting nếu tool gọi external API\u003c\/li\u003e\n  \u003cli\u003eResource cleanup (database connections, file handles, browser instances)\u003c\/li\u003e\n  \u003cli\u003eCI\/CD pipeline chạy tất cả tests trước khi deploy\u003c\/li\u003e\n  \u003cli\u003eMonitoring và alerting cho uptime và error rate\u003c\/li\u003e\n  \u003cli\u003eDocumentation cho mỗi tool (description rõ ràng giúp Claude sử dụng đúng)\u003c\/li\u003e\n\u003c\/ul\u003e\n\n\u003ch2\u003eBước tiếp theo\u003c\/h2\u003e\n\u003cp\u003eTesting và debugging MCP server là quy trình liên tục, không phải chỉ làm một lần. Khi thêm tool mới, luôn viết test trước (TDD). Khi phát hiện bug, viết regression test trước khi fix. Sử dụng MCP Inspector thường xuyên để verify server behavior. Khám phá thêm các hướng dẫn phát triển MCP tại \u003ca href=\"\/en\/collections\/nang-cao\"\u003eThư viện Nâng cao Claude\u003c\/a\u003e.\u003c\/p\u003e\n","brand":"Minh Tuấn","offers":[{"title":"Default Title","offer_id":47730161352916,"sku":null,"price":0.0,"currency_code":"VND","in_stock":true}],"thumbnail_url":"\/\/cdn.shopify.com\/s\/files\/1\/0821\/0264\/9044\/files\/testing-va-debug-mcp-server-d_m-b_o-ch_t-l_ng-cho-production.jpg?v=1774716164","url":"https:\/\/claude.vn\/en\/products\/testing-va-debug-mcp-server-d%e1%ba%a3m-b%e1%ba%a3o-ch%e1%ba%a5t-l%c6%b0%e1%bb%a3ng-cho-production","provider":"CLAUDE.VN","version":"1.0","type":"link"}