1. Vì sao cần trình kiểm tra MCP
Hãy tưởng tượng bạn đang debug frontend nhưng bị cấm mở DevTools. Cuộc sống không có MCP inspector trông gần như vậy. Giao thức MCP diễn ra “dưới nắp capo” của ChatGPT và Apps SDK; nếu bạn chỉ nhìn câu trả lời trong chat và nghĩ: “Sao nó không thấy công cụ của tôi?”, thì thực ra bạn đang bắn trong bóng tối.
Các inspector như MCP Inspector (chính thức) hoặc MCP Jam — là những MCP client dành cho lập trình viên. Chúng có thể:
- kết nối tới MCP server của bạn giống hệt cách ChatGPT làm;
- thực hiện handshake / capabilities;
- yêu cầu danh sách tools/resources/prompts;
- gọi thủ công bất kỳ tool nào với đối số tùy ý;
- hiển thị thông điệp JSON thô (requests / replies / errors).
Thực chất nó là “Postman cho MCP, nhưng thông minh hơn”. Khác với REST client thông thường, inspector hiểu đặc thù MCP: nó hiểu tools/list, tools/call, biết hiển thị schema tham số, đôi khi còn hỗ trợ cả OAuth flow cho các server bảo vệ.
Nếu không có inspector, quy trình gỡ lỗi sẽ như sau: bạn mở ChatGPT, thử gọi App, thấy “Error talking to app” hoặc thấy tool không hề được gọi, rồi bắt đầu đoán: có phải model không muốn gọi tool, MCP của bạn không khởi chạy, hay là lỗi JSON? Với inspector bạn có thể kiểm tra từng lớp một cách riêng rẽ: trước tiên là MCP server nói chuyện trực tiếp với inspector, sau đó mới tới cặp ChatGPT ↔ MCP.
2. Mini review các inspector: MCP Inspector, Jam và các công cụ khác
Trong thực tế, bạn sẽ hay dùng hai loại inspector cho MCP.
Thứ nhất là MCP Inspector chính thức từ repository Model Context Protocol. Đây là web app (thường là SPA trên React), có thể chạy cục bộ hoặc qua npx/Docker và có thể kết nối đến MCP server của bạn qua HTTP/SSE.
Thứ hai là các inspector kiểu MCP Jam, thường bổ sung tính tiện lợi cho OAuth. Chúng có thể tự đọc .well-known/oauth-protected-resource, lấy ra authorization_endpoint và token_endpoint, thực hiện PKCE flow và sau khi đã được ủy quyền thì gọi tới MCP.
MCP Jam được xây trên nền MCP Inspector. Nếu MCP Inspector cung cấp bộ công cụ tối thiểu cho việc gỡ lỗi, thì MCP Jam cung cấp đầy đủ những gì lập trình viên cần trong công việc hằng ngày với MCP. Cá nhân tôi khuyên dùng MCP Jam ngay để sau này đỡ phải đổi thói quen.
Trong phạm vi khóa học của chúng ta, khác biệt là:
- Inspector cơ bản cần mọi lúc, kể cả cho MCP server đơn giản nhất chưa bảo vệ;
- MCP Jam (hoặc tương tự) hữu dụng khi bạn học tới các module về xác thực và phân quyền.
Nhưng ý tưởng chung là một: đó là MCP client bình thường, chỉ khác là biết hiển thị đẹp những gì ChatGPT làm “âm thầm”.
3. Kịch bản điển hình khi làm việc với MCP inspector
Hãy đi theo một kịch bản tiêu biểu: bạn vừa viết một tool mới trong MCP server của mình và muốn chắc chắn nó thực sự chạy được.
Trong bài trước bạn đã khởi chạy MCP server tối giản. Giờ ta bổ sung cách kiểm tra có hệ thống: chạy đủ vòng “server → inspector → logic JSON” theo từng bước.
Bước 1 — khởi chạy MCP server
Bạn đã làm điều này ở bài trước: giả sử bạn có script npm run mcp-dev:
# ví dụ chạy MCP server
npm run mcp-dev
# bên dưới sẽ là: ts-node src/mcp-server.ts
Quan trọng là server lắng nghe đúng loại transport bạn chọn: trong khóa này thường là HTTP endpoint /mcp trên một cổng nào đó, ví dụ http://localhost:4001/mcp.
Bước 2 — khởi chạy MCP Jam
Cửa sổ terminal thứ hai:
# một cách chạy MCP Jam
npx @mcpjam/inspector@latest
# nếu cần có thể thêm --port 4002, v.v.
Sau đó inspector mở trong trình duyệt, thường ở http://localhost:6274 hoặc cổng tương tự.
Ở màn hình bắt đầu MCP Jam sẽ yêu cầu bạn nhập URL của MCP server. Bạn nhập:
http://localhost:4001/mcp
hoặc URL đã được tunnel nếu bạn chạy qua ngrok.
Bước 3 — handshake / capabilities
Ngay khi MCP Jam kết nối, nó tự động làm những gì ChatGPT làm:
- Gửi request khởi tạo (initialize) với thông tin client.
- Nhận phản hồi với version của giao thức và capabilities của server.
- Dựa trên capabilities để hiểu server có hỗ trợ tools, resources, prompts và các tính năng khác không.
Trong UI thường hiển thị kiểu như:
Connected
Protocol: mcp/2025-06-18
Capabilities:
- tools: list, call
- resources: list, read
- prompts: list, get
Nếu ngay bước này inspector không thể kết nối (connection refused, CORS, 500, v.v.), bạn sẽ thấy lỗi và hiểu ngay: vấn đề chắc chắn không nằm ở model hay ChatGPT, mà ở phía server hoặc mạng của bạn.
Bước 4 — discovery: xem tools/resources/prompts
Sau handshake thành công, inspector thường tự gọi các method như tools/list, resources/list, prompts/list để đổ dữ liệu cho sidebar. Bạn sẽ thấy:
- danh sách công cụ với mô tả và JSON Schema của tham số đầu vào;
- danh sách tài nguyên, nhóm theo collection/đường dẫn;
- danh sách prompts cùng mô tả ngắn.
Nếu bạn vừa thêm một tool mới mà không thấy trong danh sách, nghĩa là nó đăng ký sai trên server hoặc server chưa khởi chạy với code mới. Thấy điều này ở đây dễ hơn nhiều so với ngồi đoán vì sao ChatGPT “không muốn” gọi công cụ của bạn.
4. Gọi tools thủ công qua MCP Jam
Tính năng hữu dụng nhất của MCP Jam là gọi công cụ thủ công. Đây là UI riêng của bạn cho tools/call.
Chọn công cụ và điền tham số
Giả sử ở module trước bạn đã viết tool suggest_gifts:
// đâu đó trong src/mcp/tools/suggestGifts.ts
export const suggestGiftsTool = {
name: "suggest_gifts",
description: "Gợi ý ý tưởng quà tặng theo độ tuổi, ngân sách và sở thích",
inputSchema: {
type: "object",
properties: {
age: { type: "number" },
budget: { type: "number" },
interests: {
type: "array",
items: { type: "string" }
}
},
required: ["age", "budget"]
},
// handler được định nghĩa riêng
};
Trong MCP Jam bạn nhấp vào suggest_gifts. Bên phải mở form được sinh từ inputSchema. Ở đó bạn điền:
{
"age": 30,
"budget": 100,
"interests": ["trò chơi", "sách"]
}
và bấm “Call” hoặc nút tương tự.
Inspector sẽ gửi MCP request tools/call, và bạn thấy ngay:
- dữ liệu JSON thô của request (chính xác cái gì được gửi lên server);
- dữ liệu JSON thô của response (result hoặc error);
- có thể cả bản xem trước kết quả ở định dạng thuận mắt.
Đọc JSON log trong inspector
Thường inspector hiển thị kiểu như:
// Request
{
"id": "1",
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "suggest_gifts",
"arguments": {
"age": 30,
"budget": 100,
"interests": ["trò chơi", "sách"]
}
}
}
// Reply
{
"id": "1",
"jsonrpc": "2.0",
"result": {
"content": [
{
"type": "text",
"text": "1) Board game ... 2) Phiếu quà tặng tại hiệu sách ..."
}
]
}
}
Nếu handler của bạn ném exception, bạn sẽ thấy error theo kiểu JSON-RPC:
{
"id": "1",
"jsonrpc": "2.0",
"error": {
"code": -32603,
"message": "Internal error",
"data": "TypeError: Cannot read properties of undefined ..."
}
}
Rất quan trọng: chính tại đây bạn thấy tầng giao thức. Nếu response không đúng format mà Apps SDK/ChatGPT mong đợi, bạn sẽ phát hiện được trước khi quy lỗi cho “bug của GPT”.
5. Gỡ lỗi resources và prompts
Tools không phải là tất cả những gì MCP có. Bạn đã biết còn có resources và prompts.
Qua inspector bạn có thể:
- mở danh sách resource (resources/list) và xem metadata;
- đọc một resource cụ thể (resources/read) và kiểm tra dữ liệu trả về có đúng hay không;
- thực hiện tìm kiếm trên resources (nếu bạn có triển khai);
- xem các prompt dựng sẵn và nội dung của chúng.
Ví dụ, nếu bạn có resource gift_catalog:
// pseudocode đăng ký resource
registerResource({
uri: "resource://giftgenius/catalog",
name: "Danh mục quà tặng",
mimeType: "application/json",
handler: async () => {
return JSON.stringify(giftCatalogData);
}
});
Trong inspector bạn sẽ thấy resource này, nhấp vào và có thể xem ngay JSON. Nếu JSON không hợp lệ hoặc MIME type kỳ lạ — bạn sẽ bắt được trước khi ChatGPT bắt đầu trục trặc khi đọc hoặc nhúng vào widget.
6. Log của MCP server: log cái gì, ở đâu và như thế nào
MCP Jam rất hữu ích, nhưng vẫn chưa đủ: bạn cần log của chính MCP server. Không có chúng, production sẽ thành trò may rủi.
Log những gì
Những thứ tối thiểu hữu ích:
- mỗi thông điệp MCP vào (request/notification) kèm:
- thời gian;
- method (tools/call, tools/list, v.v.);
- tên công cụ (nếu có);
- tham số đã rút gọn (không chứa dữ liệu nhạy cảm);
- mỗi response đi ra:
- trạng thái (thành công / lỗi);
- thời gian thực thi;
- phiên bản rút gọn của kết quả hoặc ít nhất là kiểu dữ liệu;
- lỗi kỹ thuật:
- JSON parsing;
- exception bất ngờ trong handlers.
Đồng thời rất quan trọng là không log đầy đủ PII và secrets: token, mật khẩu, toàn bộ nội dung request bí mật. Khuyến nghị logging production thường nói rõ: log dữ liệu đã rút gọn PII.
Ghi log đi đâu: stdout / stderr
MCP có một yêu cầu quan trọng: thông điệp JSON phải đi đúng “kênh”, và mọi log gỡ lỗi đi kênh khác. Ví dụ nếu bạn dùng transport trên stdout/stderr, thì:
- thông điệp JSON-RPC phải đi vào stdout;
- mọi console.log, console.error, v.v. phải chuyển sang stderr.
Nếu bạn trộn JSON và log dạng text trong cùng một stream, client (MCP Jam hoặc ChatGPT) sẽ không parse được, bởi vì giữa JSON lại chèn vào một dòng như Server started at http://localhost:4001. Đây là một lỗi thường gặp trong MCP server.
Trong kịch bản HTTP, vấn đề đơn giản hơn, nhưng nguyên tắc giống nhau: nội dung HTTP response phải là JSON thuần, còn log — in ra console/file, không được nằm trong body của response.
Logger đơn giản cho MCP server viết bằng TypeScript
Hãy thêm vào MCP server giả định của chúng ta một logger nhỏ:
// src/logger.ts
export function logRequest(method: string, details: unknown) {
console.error(
JSON.stringify({
level: "info",
type: "request",
method,
details,
ts: new Date().toISOString(),
})
);
}
export function logError(method: string, error: unknown) {
console.error(
JSON.stringify({
level: "error",
type: "error",
method,
error: String(error),
ts: new Date().toISOString(),
})
);
}
Và trong handler của tools:
// src/mcp-server.ts (trích đoạn)
server.setRequestHandler("tools/call", async (req) => {
logRequest("tools/call", {
name: req.params?.name,
// ở đây không nên nhét toàn bộ payload, chỉ nên để các trường an toàn
});
try {
const result = await handleToolCall(req);
return result;
} catch (e) {
logError("tools/call", e);
throw e;
}
});
Như vậy bạn sẽ thấy trong console các JSON log có cấu trúc, sau đó dễ dàng đối chiếu chúng với nhau theo ts hoặc một requestId bổ sung.
7. Kết hợp: MCP Jam + log
Chiến lược gỡ lỗi MCP đúng đắn gần như luôn như sau:
- Bạn tái hiện vấn đề trong inspector: thấy tools/list trả về danh sách rỗng, tools/call bị lỗi, JSON response lạ, v.v.
- Đồng thời xem log của MCP server: nó ghi gì khi khởi chạy, lỗi nào xuất hiện với mỗi thông điệp, có stack trace không.
- Đối chiếu id, method, ts trong log với những gì inspector thấy.
Ví dụ bạn thấy trong inspector:
{
"error": {
"code": -32603,
"message": "Internal error"
}
}
Và song song trong log:
{
"level": "error",
"type": "error",
"method": "tools/call",
"error": "TypeError: Cannot read properties of undefined (reading 'age')",
"ts": "2025-11-21T10:15:12.345Z"
}
Thế là rõ bệnh: đâu đó trong handler bạn mong đợi có age, nhưng schema/đối số lại khác.
8. Mini checklist “MCP server đã sẵn sàng tích hợp với App chưa”
Trước khi nối MCP server vào ChatGPT App thật, nên đi qua một checklist nhỏ bằng inspector.
Thứ nhất, handshake và capabilities phải chạy qua được không lỗi. MCP Jam phải hiển thị rằng server hỗ trợ những thực thể bạn cần: ít nhất là tools và, nếu dùng, resources / prompts.
Thứ hai, danh sách tools/resources/prompts trong inspector phải trùng với bộ công cụ, tài nguyên và prompts mà bạn cho là mình đã triển khai. Các lỗi chính tả trong name, quên đăng ký, v.v. sẽ bị bắt ngay tại đây.
Thứ ba, gọi công cụ với đối số hợp lệ phải ổn định trả về result đúng. Tốt nhất hãy thử vài case điển hình (những request bạn thật sự kỳ vọng trong production).
Thứ tư, gọi với đối số không hợp lệ phải trả về error rõ ràng theo chuẩn JSON-RPC, chứ không đổ 500. Ví dụ thiếu tham số bắt buộc thì nên trả về lỗi có cấu trúc, để ChatGPT sau đó có thể biến thành thông điệp dễ hiểu cho người dùng.
Thứ năm, log của server khi đó không nên làm console ngập tràn stack trace mỗi tí lại in ra. Lỗi cần được cấu trúc, và dữ liệu nhạy cảm — được lọc cẩn thận.
Nếu mọi điều trên đều ổn trong inspector, bạn có thể yên tâm hơn nhiều khi nối MCP server vào Apps SDK và nghịch widget trong Dev Mode.
9. Các bug điển hình của MCP server và cách bắt chúng qua inspector
Giờ hãy điểm qua phần “ngon” nhất — thường hỏng ở đâu và nhìn thấy thế nào.
Cấu hình và kết nối
Đôi khi tưởng là “server không chạy”, nhưng hóa ra nó chẳng lắng nghe đúng cổng hoặc endpoint. Inspector khi đó sẽ báo đúng connection refused hoặc không thể kết nối. Nguyên nhân phổ biến: URL sai (ví dụ /mcp thay vì /api/mcp), cổng bị tiến trình khác chiếm, tunnel chưa mở hoặc CORS chặn request.
JSON không hợp lệ / trộn log với giao thức
Một câu chuyện đau đầu — khi bạn in console.log("Server started") vào stdout, trong khi trên đó phải truyền thông điệp JSON-RPC. Client kỳ vọng JSON thuần, nhưng nhận text + JSON, thử parse và sập vì lỗi định dạng.
Giải pháp đơn giản: tách bạch nghiêm ngặt thứ gì đi vào luồng giao thức (stdout hoặc phần body của HTTP response), và thứ gì đi vào log (stderr hoặc file log riêng).
Schema không khớp với hiện thực của công cụ
Lỗi phổ biến khác: trong inputSchema bạn khai báo một kiểu, còn code lại kỳ vọng kiểu khác. Ví dụ, schema nói: age — số, interests — mảng string không bắt buộc, nhưng code lại làm arguments.interests.toLowerCase(). Model (và inspector) gửi interests là null hoặc không gửi trường này — và thế là sập.
Inspector cho phép bạn nhìn rõ ràng JSON thực sự được gửi trong tools/call là gì, và đối chiếu với code của bạn.
Tên tools/resources không đúng
Nếu trong capabilities / tools/list bạn export tool là suggest_gifts_v2, còn trong Apps manifest hoặc widget lại kỳ vọng suggest_gifts, thì “tool không tìm thấy” sẽ đeo bám bạn tới cuối dự án. Trong inspector, qua danh sách tools và trường name bạn sẽ thấy ngay, khỏi phải đoán GPT nghĩ gì.
Tools chậm hoặc treo
Nếu gọi tool trong inspector mất 30 giây rồi timeout, — đừng hy vọng ChatGPT phản ứng tốt hơn. MCP inspector sẽ giúp chỉ ra bạn chậm ở bước nào: gọi mạng, DB, hay API bên ngoài. Trong log, nên có thời điểm bắt đầu và kết thúc xử lý mỗi request để thấy ngay outlier.
10. Lỗi điển hình khi inspect và gỡ lỗi MCP
Lỗi №1: cố debug MCP chỉ qua ChatGPT.
Nhiều lập trình viên nối MCP vào App trước, thấy “có gì đó không chạy”, rồi bắt đầu đổi prompt, mô tả công cụ, thậm chí đổi version model. Trong khi MCP server còn chưa khởi chạy hoặc tools/list rỗng. Luôn bắt đầu với inspector: nếu ở đó đã tệ, thì model không liên quan.
Lỗi №2: trộn JSON-RPC và log vào một luồng.
Khi MCP client kỳ vọng JSON thuần, còn bạn in vào stdout các dòng debug, kết quả dễ đoán — parse hỏng, Inspector hiển thị lỗi kỳ quặc. Log phải đi riêng (stderr, file, hệ thống logging ngoài), còn thông điệp giao thức — đi đúng kênh của nó.
Lỗi №3: không xem capabilities và danh sách tools.
Thường tool “biến mất” chỉ vì bạn quên đăng ký hoặc quên bật capability tương ứng. Nếu không xem capabilities và tools/list trong inspector, bạn có thể đinh ninh rằng lỗi do model chứ không phải code đăng ký của mình.
Lỗi №4: bỏ qua lỗi schema và sự không khớp JSON.
Khi inputSchema và JSON thực tế không khớp, model và inspector tất yếu sẽ hành xử kỳ lạ. Nếu bạn không xem JSON thô trong inspector và không validate schema, lỗi sẽ xuất hiện ở những nơi khó lường nhất.
Lỗi №5: log tất cả mọi thứ, kể cả PII và token.
Trong lúc hăng say gỡ lỗi rất dễ in vào log toàn bộ request body, gồm cả dữ liệu cá nhân hoặc secret. Ở production điều này thành quả bom nổ chậm: rò rỉ, vấn đề tuân thủ, v.v. Chỉ log những gì thực sự cần cho chẩn đoán, và ở dạng đã rút gọn/ẩn danh.
Lỗi №6: không tái hiện vấn đề bằng case tối thiểu.
Đôi khi bug xuất hiện trong một cuộc hội thoại phức tạp qua ChatGPT, và lập trình viên cố gắng gỡ lỗi y như vậy. Hiệu quả hơn nhiều là tái hiện cùng kịch bản đó trong inspector bằng một hai MCP request, loại bỏ ảnh hưởng của prompt, lịch sử hội thoại và “tâm trạng” của model.
GO TO FULL VERSION