1. Vì sao cần một bài riêng về debug cục bộ
Trong các mô‑đun trước chúng ta đã xem qua cấu trúc của Apps SDK và MCP. Giờ hãy nói về lý do cần một bài riêng cho debug cục bộ.
Nhiều người đi theo lộ trình: “Mình mở ChatGPT, gõ ‘hãy dùng App của tôi’, rồi xem nó nói gì. Không chạy thì sửa code đại.” Điều đó giống như sửa backend chỉ bằng cách nhìn trang HTML trên trình duyệt mà không bao giờ mở log của server.
Với ChatGPT Apps đặc biệt dễ rơi vào “ma thuật”: có GPT, nó tự quyết định có gọi tool hay không, có logic lỗi riêng. Nếu bạn không thấy chuyện gì diễn ra bên trong, việc debug sẽ biến thành “tụng phép” vô định.
Mục tiêu của chúng ta: biến việc đó thành một quy trình kỹ thuật bình thường:
- bạn biết xem log của Next/MCP ở đâu;
- bạn biết tự tay gọi máy chủ MCP qua inspector;
- bạn hiểu Dev Mode kiểm tra điều gì và cách đảm bảo ChatGPT thực sự có thể truy cập máy chủ của bạn.
Và quan trọng nhất: bạn ngừng gỡ lỗi theo kiểu “đoán mò GPT” và bắt đầu kiểm tra các lớp thấp của stack trước — máy chủ và giao thức, rồi sau đó mới đến UI và hành vi của mô hình.
2. Mô hình tư duy: ba lớp debug
Để không chìm trong hỗn loạn, ta thống nhất nghĩ về debug theo ba lớp. Đây là “bánh nhiều tầng” nho nhỏ của chúng ta:
| Lớp | Trong đó có gì | Triệu chứng thường gặp | Dùng gì để gỡ lỗi |
|---|---|---|---|
| UI (widget) | các component React, state, window.openai | Widget trống/xám, render lỗi, nút không hoạt động | DevTools của trình duyệt |
| Backend / máy chủ MCP | tools, truy cập DB/API | 500, “tool bị lỗi”, dữ liệu kỳ lạ | log máy chủ, MCP Inspector |
| Giao thức MCP | JSON‑RPC, tools/list, tools/call, schema | GPT báo “không gọi được tool”, invalid params | inspector + log request |
Ở lớp thứ hai, ta quan tâm máy chủ MCP tự làm gì (tools, DB, API), còn ở lớp thứ ba — là “dây nối” và định dạng thông điệp MCP (JSON‑RPC, schema, v.v.).
Bộ ba này là nền tảng cho kế hoạch bài giảng và khóa học về debug.
Để trực quan, có thể nhìn vào dòng chảy của một request:
sequenceDiagram
participant User as Nguoi dung
participant ChatGPT as ChatGPT (Dev Mode)
participant Tunnel as Tunnel (ngrok/CF)
participant Next as Next.js + MCP
User->>ChatGPT: "Chon qua toi da $50"
ChatGPT->>Next: tools/call search_gifts (qua Tunnel)
Next->>Next: Goi MCP tool, truy cap DB/API
Next-->>ChatGPT: JSON-RPC result + ToolOutput
ChatGPT-->>User: Phan hoi + render widget
Có thể hỏng ở bất cứ điểm nào: tunnel, endpoint, logic MCP, JSON schema, React widget. Nhiệm vụ của bạn khi debug — xác định lỗi ở đúng lớp nào, thay vì vội vàng viết lại mọi thứ.
3. Log của Next.js và MCP: nền tảng của mọi thứ
Bắt đầu từ thứ nhàm chán nhất nhưng hữu ích nhất — log.
Log nằm ở đâu khi phát triển cục bộ
Trong template chuẩn của Apps SDK trên Next.js, máy chủ MCP thường được bọc trong API route (/api/mcp hoặc tương tự). Bạn chạy npm run dev, và trong một terminal bạn có:
- dev server của Next.js;
- trình xử lý cho endpoint MCP, nhận các request JSON‑RPC tools/list, tools/call, v.v.;
- toàn bộ console output từ console.log/console.error.
Nếu bạn tách MCP thành một tiến trình riêng, sẽ có terminal thứ hai, nhưng ý tưởng vẫn vậy: mọi thứ thú vị đều thấy trong console.
Cần phân biệt:
- lỗi build/khởi động — next dev không lên, TypeScript crash, import sai, v.v.;
- lỗi lúc chạy — mọi thứ đã chạy, nhưng một request tới /api/mcp lại làm công cụ bị lỗi.
Next.js ở chế độ dev hiển thị lỗi runtime bằng overlay đẹp mắt, đồng thời ghi stack trace vào console.
Cần log gì trong máy chủ MCP
Dù MCP dùng giao thức JSON‑RPC, để debug bạn không cần in toàn bộ JSON. Hữu ích hơn là log ngắn gọn, có cấu trúc.
Thực hành tốt cho log của MCP — log tối thiểu các trường: timestamp, request_id/traceId, tên tool, tham số (đã ẩn danh), trạng thái (ok/error) và thời gian thực thi.
Một logger.ts đơn giản cho GiftGenius có thể như sau:
// src/lib/logger.ts
export function logToolEvent(
phase: "start" | "end" | "error",
data: Record<string, unknown>
) {
const ts = new Date().toISOString();
console.log(JSON.stringify({ ts, phase, ...data }));
}
Và trong handler của công cụ:
// src/mcp/tools/searchGifts.ts
import { logToolEvent } from "@/lib/logger";
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... tìm kiếm quà tặng thực tế ...
const results = []; // kết quả giả lập
logToolEvent("end", { tool: "search_gifts", traceId, count: results.length });
return results;
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
throw err;
}
}
Có hai điểm quan trọng.
Thứ nhất, đừng lưu email đầy đủ, số điện thoại, số thẻ, token trong log. Việc này không chỉ xấu mà còn mâu thuẫn với các thực hành bảo mật cơ bản của MCP.
Thứ hai, traceId — là “bạn thân” của bạn. Khi xem log Next.js và MCP cùng lúc, bạn dễ dàng liên kết sự kiện qua nó: request tools/call cụ thể, render React tương ứng và network log của widget.
Cách xác định qua log, lỗi ở đâu
Bạn có terminal, trong đó các dòng JSON từ logToolEvent đang trôi qua. Kịch bản điển hình:
- đến phase: "start" với tool: "search_gifts";
- không có phase: "end", nhưng có phase: "error" và stack trace;
- từ đó thấy được công cụ đã chạy đến logic của bạn, nhưng vỡ bên trong — ví dụ gọi API ngoài, parse, thao tác DB.
Còn nếu bạn không thấy log cho tên tool này — nghĩa là request thậm chí chưa tới công cụ. Khi đó bạn đi lên stack: tunnel, endpoint /mcp, JSON request tools/call.
4. MCP Inspector: gỡ lỗi MCP trước khi tới ChatGPT
Nếu log là “đôi mắt” của bạn, thì MCP Inspector (hoặc MCPJam Inspector) là “kính hiển vi”.
Về MCP Inspector và lý do cần nó
Trong mô‑đun về MCP ta đã kết nối Inspector để kiểm tra “Hello, MCP” server. Ở đây ta dùng nó như công cụ gỡ lỗi chính: trước hết xác minh MCP tự chạy ổn, rồi mới tới Dev Mode và UI.
Inspector là một ứng dụng riêng (thường là web UI kèm CLI), đóng vai trò client của MCP. Nó kết nối tới máy chủ của bạn qua HTTP/SSE hoặc stdin/stdout, thực hiện tools/list, tools/call và hiển thị các thông điệp JSON thô, handshake, danh sách công cụ, resources, v.v.
Ý tưởng chính: loại ChatGPT ra khỏi phương trình. Nếu công cụ của bạn không chạy, trước tiên bạn muốn biết máy chủ có sống không, giao thức và schema có chuẩn không, rồi mới “đổ lỗi” cho GPT.
Quy trình nhỏ với Inspector
Kịch bản gỡ lỗi cục bộ điển hình:
- Chạy npm run dev để Next.js + MCP endpoint được bật.
- Chạy MCP Inspector, ví dụ:
npx @modelcontextprotocol/inspector
(lệnh cụ thể tùy công cụ bạn dùng).
- Trong Inspector, chỉ định URL endpoint MCP của bạn, ví dụ http://localhost:3000/api/mcp (hoặc dùng tunnel HTTPS nếu muốn kiểm tra cả tunnel).
- Kiểm tra handshake: server phải phản hồi capabilities hỗ trợ, danh sách tools, resources, v.v.
- Tự tay gọi công cụ quan tâm: chọn search_gifts, nhập tham số {"q": "cho ban nu duoi 30"}, nhấn “Call tool” và xem:
- có nhận được phản hồi không;
- có lỗi JSON‑RPC hoặc MCP không;
- máy chủ ghi gì trong log cho lần gọi đó.
Nếu ngay trong Inspector đã lỗi, không cần mở ChatGPT: hãy sửa máy chủ MCP.
Nếu trong Inspector mọi thứ ổn mà ChatGPT vẫn phàn nàn — nghĩa là vấn đề ở phía trên: URL Dev Mode, ủy quyền, hành vi mô hình.
Ví dụ “cố tình làm tool hỏng”
Lấy search_gifts của chúng ta và phá nó có chủ đích:
export async function searchGiftsTool(args: { q: string }) {
if (args.q === "loi di") {
throw new Error("Loi minh hoa cho muc dich debug");
}
// ... logic bình thường ...
return [];
}
Tiếp theo:
- Trong Inspector gọi search_gifts với tham số {"q": "loi di"}.
- Trong log thấy phase: "error" và stack trace.
- Xác nhận máy chủ MCP trả lỗi một cách “thành thật”.
Sau đó, khi bạn nối tất cả vào ChatGPT Dev Mode và yêu cầu mô hình “hãy chọn quà có chứa từ ‘loi di’”, nó sẽ cố gọi công cụ và hiển thị cho người dùng thông báo kiểu “I encountered an error running the tool”. Rõ ràng: lỗi xuất hiện không phải do mô hình, mà do exception có chủ đích của bạn.
Thủ pháp này rèn tư duy tốt: bạn tách bạch lỗi nghiệp vụ (chính ta ném Error) với lỗi giao thức (JSON hỏng, sai tên công cụ, v.v.).
5. Debug widget: DevTools, state và “debug banner”
Khi máy chủ MCP đã tạm ổn, chuyển sang frontend — widget của Apps SDK.
Xem lỗi widget ở đâu và như thế nào
Widget của bạn được render bên trong ChatGPT trong sandbox iframe. Tin vui: iframe này vẫn dùng DevTools trình duyệt như thường.
Quy trình nhỏ:
- Mở ChatGPT trong trình duyệt (Chrome/Edge/Firefox).
- Mở DevTools (thường là F12 hoặc Ctrl+Shift+I).
- Tab Console — chọn context của frame nơi widget của bạn chạy (thường là domain web-sandbox.oaiusercontent.com).
- Refresh chat/gửi tin nhắn để GPT hiển thị App của bạn.
Nếu widget:
- không xuất hiện;
- hiện xám/trống;
- hiện lỗi đỏ trong console
— gần như chắc chắn là vấn đề ở code React: truy cập thuộc tính không tồn tại, import sai, hook lỗi, v.v.
Tab Network cũng hữu ích. Ở đó bạn sẽ thấy:
- việc tải JS bundle của ứng dụng bạn (nếu 404/500 — vấn đề ở dev server/tunnel);
- các request mà widget gọi ra ngoài qua window.fetch, và phản hồi 4xx/5xx.
Debug banner đơn giản
Một thủ pháp rất tiện — thêm vào component gốc của widget một “debug banner” nhỏ, ở Dev Mode sẽ hiển thị môi trường và phiên bản build.
Ví dụ:
// src/components/DebugBanner.tsx
export function DebugBanner() {
if (process.env.NODE_ENV !== "development") return null;
return (
<div style={{ padding: 4, background: "#222", color: "#0f0", fontSize: 10 }}>
ENV: dev | build: local | {new Date().toLocaleTimeString()}
</div>
);
}
Và trong component gốc của widget:
// src/app/widget/page.tsx
import { DebugBanner } from "@/components/DebugBanner";
export default function GiftGeniusWidget() {
return (
<div>
<DebugBanner />
{/* phần UI còn lại cho tìm kiếm quà tặng */}
</div>
);
}
Nếu bạn mở ChatGPT, chạy App mà không thấy banner — nghĩa là JS của bạn chưa tới được trình duyệt: hoặc lỗi build, hoặc vấn đề với endpoint, hoặc widget chưa được đăng ký trong máy chủ MCP.
State cục bộ và xử lý lỗi
Widget của bạn nên hiển thị được các trạng thái: loading, success, error. Nếu chưa — đây là lúc thêm vào.
Mẫu nhỏ:
const [status, setStatus] = useState<"idle"|"loading"|"error"|"success">("idle");
async function handleSearch(query: string) {
try {
setStatus("loading");
// gọi MCP tool qua window.openai.callTool hoặc hook của Apps SDK
setStatus("success");
} catch (e) {
console.error("Search failed", e);
setStatus("error");
}
}
Trong JSX:
{status === "error" && (
<div style={{ color: "red" }}>Đã có lỗi xảy ra, vui lòng thử lại.</div>
)}
Đối với debug, điều cốt yếu là:
- bạn không nuốt exception (nếu không console sẽ trống, còn UI “đơ”);
- bạn hiển thị lỗi rõ ràng trên UI, nếu không người dùng sẽ tưởng App đã “chết”.
6. Dev Mode trong quy trình debug: nó làm gì và đừng đổ lỗi oan
Giờ thêm ChatGPT Dev Mode vào bức tranh. Trước đó ta chỉ xét code của bạn. Nhưng đôi khi mọi thứ chạy tốt cục bộ, Inspector ổn, mà ChatGPT vẫn trả “Error talking to [AppName]” hoặc không hề gợi ý App của bạn.
Dev Mode làm gì
Dev Mode là chế độ của ChatGPT nơi bạn có thể:
- tạo và chỉnh sửa App của mình;
- chỉ định endpoint của máy chủ MCP (thường là https://your-domain/mcp hoặc /api/mcp);
- cập nhật nhanh manifest và metadata mà không cần publish lên Store.
Về góc nhìn debug, Dev Mode chỉ là một lớp cấu hình nữa:
- nếu ở đó URL sai;
- nếu bạn quên /mcp ở cuối;
- nếu tunnel cấp domain mới mà bạn không cập nhật cấu hình
— ChatGPT đơn giản là không thể chạm tới máy chủ của bạn.
Kịch bản hỏng Dev Mode điển hình
Kinh điển:
- Bạn bật tunnel https://abcd.ngrok.io, điền nó trong Dev Mode, mọi thứ chạy.
- Hôm sau khởi động lại ngrok, nhận https://efgh.ngrok.io.
- Trong Dev Mode vẫn để https://abcd.ngrok.io/mcp.
- ChatGPT ghi “Error talking to GiftGenius”.
MCP Inspector khi trỏ vào http://localhost:3000/api/mcp lại cho thấy mọi thứ ổn. Nghĩa là MCP vẫn sống, nhưng ChatGPT đang nhìn nhầm chỗ.
Cách xử lý: vào cài đặt Dev Mode, cập nhật URL, đừng quên /mcp ở cuối.
Dev Mode vs Store
Trong bài này ta chỉ nói về Dev Mode — sân chơi của bạn. Ở đây việc đổi URL thường xuyên, đổi tunnel, sửa schema công cụ là bình thường.
Khi sau này lên Store, endpoint sẽ “đóng đinh” chặt chẽ hơn, và những trò như vậy không còn ổn. Nhưng còn vài mô‑đun nữa mới tới Store, nên giờ cứ thoải mái phá và sửa trong Dev Mode.
7. Thuật toán gỡ lỗi mini: làm gì khi “không gì chạy cả”
Giờ gom lại thành thuật toán thực tế. Về bản chất, vẫn là ba lớp debug ở đầu bài, chỉ viết lại thành các bước.
Giả sử bạn mở ChatGPT, chọn GiftGenius, yêu cầu “Chọn quà đến 30$ cho người bạn geek”, và:
- GPT không nói gì về App;
- hoặc báo “Error talking to GiftGenius”;
- hoặc widget mở lên trống/xám.
Làm sao để không tuyệt vọng?
Bước 1 (lớp MCP/máy chủ). Kiểm tra MCP qua Inspector và log
Tạm thời bỏ qua GPT và UI. Ta chỉ quan tâm máy chủ.
- Đảm bảo đã chạy npm run dev, và endpoint (/api/mcp) phản hồi.
- Kết nối MCP Inspector tới http://localhost:3000/api/mcp hoặc tunnel của bạn.
- Kiểm tra handshake — danh sách tools phải hiển thị.
- Tự tay gọi công cụ mà GPT đáng ra sẽ gọi (ví dụ search_gifts) với tham số tương tự.
Nếu ở đây đã lỗi — hãy sửa MCP: schema, business logic, các call mạng. Dùng log và traceId để hiểu chính xác chỗ hỏng.
Bước 2 (lớp giao thức/Dev Mode). Kiểm tra Dev Mode và URL
Nếu trong Inspector mọi thứ tuyệt vời, nhưng ChatGPT vẫn không thấy App của bạn hoặc báo vấn đề kết nối:
- Mở cài đặt Dev Mode cho App của bạn.
- Xem URL cấu hình cho MCP.
- Đối chiếu với địa chỉ mà máy chủ/tunnel của bạn thực sự đang lắng nghe (đừng quên /mcp ở cuối nếu server của bạn yêu cầu).
Thường vấn đề nằm chính ở đây.
Bước 3 (lớp UI). Kiểm tra widget qua DevTools
Nếu ChatGPT gọi công cụ thành công (thấy qua log MCP), nhưng widget cư xử kỳ lạ:
- Mở DevTools trên trang ChatGPT.
- Tab Console — chọn context iframe của widget.
- Xem lỗi JS.
- Tab Network — đảm bảo rằng:
- JS bundle của widget tải không bị 404/500;
- các request bổ sung (qua fetch/window.openai.fetch) trả về dữ liệu hợp lý.
Song song hãy nhìn DebugBanner: nếu nó không xuất hiện, nghĩa là chưa tới được cây React.
Bước 4. Dùng Dev Mode để tái hiện bug report
Khi nhận bug report từ đồng nghiệp/người dùng, hãy cố lưu lại đúng prompt gây lỗi. Trong Dev Mode bạn có thể tái hiện rất nhanh:
- Chạy npm run dev, bật tunnel.
- Trong Dev Mode chọn App.
- Dán prompt gây vấn đề.
- Đồng thời:
- xem các request JSON tới MCP trong log;
- trong Inspector, nếu cần, lặp lại tools/call với cùng tham số.
Như vậy bạn biến “thỉnh thoảng không chạy” thành kịch bản có thể tái hiện.
8. Vài điểm code nhỏ để debug tiện hơn
Để củng cố, thêm vài đoạn hữu ích vào ứng dụng GiftGenius của chúng ta.
Cấu hình môi trường và mức log
Trong cấu hình server, nên khai báo rõ endpoint MCP và mức log:
// src/config.ts
export const config = {
mcpEndpoint:
process.env.NODE_ENV === "development"
? "http://localhost:3000/api/mcp" // tunnel sẽ phủ lên cái này
: "https://api.giftgenius.com/api/mcp",
logLevel: process.env.NODE_ENV === "development" ? "DEBUG" : "ERROR",
};
Và trong logToolEvent có thể xét logLevel, để ở prod không spam thừa.
Log lỗi có cấu trúc của MCP
Khi xử lý công cụ, cố gắng bắt các lỗi dự kiến và trả thông điệp dễ hiểu, thay vì ném tất cả bằng throw:
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... code bình thường ...
return { content: [{ type: "text", text: "Tìm thấy 3 món quà" }] };
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
return {
content: [{ type: "text", text: "Lỗi tìm kiếm quà. Vui lòng thử lại sau." }],
isError: true,
};
}
}
Như vậy ChatGPT sẽ thấy kết quả được đánh dấu isError, có thể diễn giải vấn đề đúng cho người dùng, còn bạn — xem được chuyện gì đã xảy ra trong log.
9. Lỗi thường gặp khi debug cục bộ ChatGPT App
Lỗi №1: gỡ lỗi “qua GPT” thay vì qua máy chủ và inspector.
Rất dễ bị cám dỗ chỉ nhìn câu trả lời của mô hình và đoán xem bug ở đâu. Nhưng mô hình là lớp cao nhất. Nếu máy chủ MCP không tự chạy ổn (bằng tay, qua Inspector) — đừng mong phép màu từ GPT. Hãy làm MCP ổn định trước, rồi mới nối ChatGPT.
Lỗi №2: không xem log hoặc log mọi thứ bừa bãi.
Thiếu log khiến bạn “mù”: không biết công cụ nào được gọi, tham số nào, kết thúc ra sao. Log quá nhiều thì biến console thành “ma trận” các dòng rời rạc. Tốt nhất là log gọn, có cấu trúc với tool, args (đã ẩn danh), traceId, status và thời gian chạy.
Lỗi №3: lưu dữ liệu nhạy cảm trong log.
Log token, email đầy đủ, số thẻ — là thực hành tệ cả về bảo mật lẫn chính sách OpenAI. Log chỉ nên có thông tin thực sự giúp debug; dữ liệu cá nhân — hãy che đi hoặc đừng ghi.
Lỗi №4: đổ mọi tội cho Dev Mode.
Dev Mode hay bị “đổ vạ”: “OpenAI chắc hỏng gì đó”. Thực tế rất thường là bạn quên cập nhật URL sau khi restart tunnel hoặc chỉ sai đường dẫn (/ thay vì /mcp). Trước khi liên hệ hỗ trợ, hãy vào cài đặt Dev Mode và đối chiếu endpoint với địa chỉ server thực.
Lỗi №5: bỏ qua DevTools và lỗi trong widget.
Widget trống hoặc xám hầu như luôn là lỗi JavaScript phía client. Nếu bạn chỉ nhìn log của MCP mà không mở DevTools trong ChatGPT, bạn chỉ thấy nửa bức tranh. Hình thành thói quen nhấn F12 và xem Console/Network sẽ tiết kiệm hàng giờ.
Lỗi №6: “chữa” bug bằng delay “ma thuật”.
Đôi khi muốn thêm setTimeout hoặc delay kiểu Thread.sleep “cho đủ thời gian tải”. Trong thế giới MCP/Next/React đó hầu như luôn là cách chữa sai: vấn đề thường nằm ở schema, endpoint sai, hoặc lỗi code — chứ không phải “server chưa kịp”. Tốt hơn là hiểu chỗ đứt (Inspector → Dev Mode → widget) thay vì che nó bằng delay.
Lỗi №7: deploy lên Vercel khi cục bộ chưa chạy ổn.
Muốn “lên prod nhanh” là dễ hiểu, nhưng mang một MCP hỏng lên Vercel là cách hoàn hảo để có hai tầng vấn đề: cục bộ và production. Trong mô‑đun này ta cố ý yêu cầu: trước hết MCP Jam/Inspector → mọi thứ OK, Dev Mode → kịch bản cơ bản chạy, rồi hãy deploy.
GO TO FULL VERSION