ข้ามไปยังเนื้อหา

Testing and Debugging

การ debug ผ่านหน้าต่างแชตทั้งช้าและมี noise เยอะ: คุณบอกไม่ได้ว่า model เลือกผิด, client encode ผิด หรือ server ของคุณตอบผิด ทางแก้คือคุยกับ server โดยตรง ด้วย client ที่คุณควบคุมได้ แล้วดู JSON-RPC บน wire

flowchart LR
  i["Inspector: web, cli, tui"] --> s["your server"]
  t["in-process test client"] --> s
  h["real host, e.g. Claude Code"] --> s
  s --> w["wire: JSON-RPC you can read"]
สามวิธีในการขับ server

Inspector คือ developer tool อ้างอิงของ MCP หนึ่ง package สาม client หนึ่ง core ที่ใช้ร่วมกัน ทำให้การเชื่อมต่อทำงานเหมือนกันทุกโหมด ต้องใช้ Node 22.19.0 ขึ้นไป และรันผ่าน npx ได้เลยโดยไม่ต้องติดตั้ง:

Terminal window
# Web UI (default): launches a browser tab with a one-time session token
npx @modelcontextprotocol/inspector node dist/index.js
# Or start empty and add servers from the UI
npx @modelcontextprotocol/inspector
# A published npm server
npx -y @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem ~/Desktop
# A PyPI server
npx @modelcontextprotocol/inspector uvx mcp-server-git --repository ~/code/repo
# A remote Streamable HTTP server
npx @modelcontextprotocol/inspector --server-url https://api.example.com/mcp --transport http

web client มีแท็บสำหรับ tools, resources และ prompts มี OAuth flow สำหรับ server ที่ป้องกันไว้ และมี monitoring sidebar ที่แสดงทุก message แบบสด ๆ มัน negotiate protocol era ให้อัตโนมัติ คุณจึงชี้ไปที่ server ยุค 2025-11-25 หรือ 2026-07-28 ก็ได้

--cli เปลี่ยน core เดียวกันให้เป็น client แบบ script ได้ พร้อม output ที่เครื่องอ่านได้:

Terminal window
# List tools and exit
npx @modelcontextprotocol/inspector --cli node dist/index.js --method tools/list
# Call a tool and pipe into jq
npx @modelcontextprotocol/inspector --cli https://api.example.com/mcp --transport http \
--method tools/call --tool-name get_weather --tool-arg city=Boston --format json | jq .result

นี่คือ smoke test ที่เร็วที่สุดที่ใส่ใน pipeline ได้: มี exit code, ได้ JSON ออกมา, ไม่ต้องมี browser

Terminal window
npx @modelcontextprotocol/inspector --tui node dist/index.js

feature เดียวกันแต่อยู่ใน terminal สำหรับ SSH session และเครื่องที่ไม่มี browser

flag เลือกโหมดต้องมาก่อน: --web (default), --cli หรือ --tui ทุกอย่างหลัง token แรกที่ไม่ใช่ flag โหมดจะถูกส่งต่อให้ client แล้วต่อไปยัง server ของคุณ นั่นคือเหตุผลที่ server เองก็รับ argument --cli ตรง ๆ ได้

Inspector มีไว้สำรวจ แต่สำหรับ regression test คุณต้องการ client ที่อยู่ใน process เดียวกับ server ไม่มี port ไม่มี subprocess และรันภายใต้ test runner ปกติของคุณ

import assert from 'node:assert/strict';
import { test } from 'node:test';
import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
import { createMcpHandler, McpServer } from '@modelcontextprotocol/server';
import * as z from 'zod/v4';
function createServer() {
const server = new McpServer({ name: 'pricing', version: '1.0.0' });
server.registerTool(
'apply-discount',
{
description: 'Apply a percentage discount to a price',
inputSchema: z.object({ price: z.number(), percent: z.number().min(0).max(100) }),
outputSchema: z.object({ total: z.number() }),
},
async ({ price, percent }) => {
if (price < 0) {
return { content: [{ type: 'text', text: 'price must be >= 0' }], isError: true };
}
const total = price * (1 - percent / 100);
return { content: [{ type: 'text', text: `$${total}` }], structuredContent: { total } };
},
);
return server;
}
test('discount math and error path', async () => {
const handler = createMcpHandler(createServer);
// Route the client's fetch straight into the handler — no network.
const transport = new StreamableHTTPClientTransport(new URL('http://test.local/mcp'), {
fetch: (url, init) => handler.fetch(new Request(url, init)),
});
const client = new Client({ name: 'test', version: '0.0.0' });
await client.connect(transport);
const ok = await client.callTool({ name: 'apply-discount', arguments: { price: 100, percent: 25 } });
assert.equal(ok.isError, undefined);
assert.deepEqual(ok.structuredContent, { total: 75 });
const bad = await client.callTool({ name: 'apply-discount', arguments: { price: -1, percent: 0 } });
assert.equal(bad.isError, true);
});

ทั้งสองแบบวิ่งผ่าน protocol path จริง: request ถูก serialize, _meta ถูกเติม, handler validate schema และ result กลับมาในแบบเดียวกับที่ model จะเห็น ให้ assert ที่ isError, ที่ structuredContent และที่ text — เพราะ text คือสิ่งที่ model อ่าน

บน stdio stdout คือ wire console.log หรือ print() หลุดมาแค่บรรทัดเดียวก็ทำ framing เสีย client จะเห็น parse error ไม่ใช่ข้อความของคุณ กฎคือ:

  • log ไปที่ stderr เท่านั้น spec อนุญาต UTF-8 อะไรก็ได้บน stderr client อาจ capture, forward หรือเมินก็ได้
  • ใน Node console.error ไปที่ stderr ใน Python ใช้ logging กับ StreamHandler(sys.stderr) หรือ print(..., file=sys.stderr)
  • library ภายนอกที่ print ลง stdout ต้องถูกปิดเสียงหรือ redirect ก่อน server เริ่มทำงาน

บน Streamable HTTP ไม่มีอันตรายแบบนี้ แต่ feature logging ของ protocol เองถูก deprecate แล้ว (ดูบทสุดท้าย) ถ้า client อยากได้ log message ต่อ request มันจะ opt in ด้วย io.modelcontextprotocol/logLevel ใน _meta และ server ต้องไม่ ส่ง notifications/message สำหรับ request ที่ไม่ได้ใส่ field นี้ ส่วนอย่างอื่นให้ใช้ logger ของ platform คุณ

key ที่สงวนไว้ใน _meta อย่าง traceparent, tracestate และ baggage พา W3C Trace Context ผ่าน MCP ได้ ทำให้ tool call เข้าร่วม distributed trace เดียวกับ HTTP request ที่ trigger agent:

{
"jsonrpc": "2.0",
"id": 7,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "location": "Lisbon" },
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientCapabilities": {},
"traceparent": "00-0af7651916cd43dd8448eb211c80319c-00f067aa0ba902b7-01"
}
}
}

อ่าน traceparent ใน handler เริ่ม child span แล้ว downstream call ของ tool คุณจะไปโผล่ใต้ trace ของ agent ตรงตาม OpenTelemetry semantic convention สำหรับ MCP

เวลา server “ไม่ทำอะไรเลย” หรือ client “ต่อไม่ได้” แทบทุกครั้งจะเป็นหนึ่งในนี้:

อาการสาเหตุที่น่าจะเป็นตรวจอย่างไร
client รายงาน JSON parse error บน stdioมีอะไรเขียนลง stdoutgrep หา console.log / print( ที่ไม่มี file=sys.stderr
HTTP 400, error -32020 HeaderMismatchMcp-Method / Mcp-Name / MCP-Protocol-Version หายไปหรือไม่ตรงกับ bodyเทียบ header กับ params.name และ _meta
HTTP 400, error -32022 UnsupportedProtocolVersionclient ส่ง version ที่ server ไม่รองรับอ่าน error.data.supported แล้ว retry ด้วยหนึ่งในนั้น
HTTP 400, error -32602 และ “missing _meta”request ไม่มี io.modelcontextprotocol/protocolVersion หรือ clientCapabilitiesใช้ SDK client หรือเติม field เอง
HTTP 403มี header Origin แต่ไม่ได้รับอนุญาตตั้ง allowed host / origin ของ server
HTTP 406 หรือ response ค้างAccept ของ client ไม่ได้ระบุทั้ง application/json และ text/event-streamแก้ header ของ client
tool “ใช้ได้ใน Inspector แต่พังจาก host”inputSchema เข้มเกินไป หรือ model ส่ง string มาแทน numberผ่อนหรือ coerce; เติม description ให้ property
structuredContent ถูกปฏิเสธresult ไม่ตรงกับ outputSchemavalidate ก่อน return; เก็บ text fallback ไว้
legacy client ได้ 405 บน GETserver รองรับแค่ยุคใหม่ แต่ client คาดหวัง GET stream แบบเก่าserve แบบ dual-era หรือบอกให้ client อัปเกรด

อ่าน wire ก่อนเสมอ monitor sidebar ของ Inspector หรือ --format json ของ CLI จะบอกคุณชัด ๆ ว่าเป็นข้อไหน

MCP Inspector ต้องใช้ Node เวอร์ชันต่ำสุดเท่าไร?
จะรัน TypeScript server แบบ in-process สำหรับ test โดยไม่เปิด port ได้อย่างไร?
stdio server print "starting…" ด้วย console.log จะเกิดอะไรขึ้น?
remote server ตอบ HTTP 400 พร้อม JSON-RPC error code -32020 หมายความว่าอะไร?