1. 為什麼需要一堂關於本機除錯的課
在先前的模組中,我們已經拆解過 Apps SDK 與 MCP 的堆疊。現在來談談,為什麼還需要專門一堂關於本機除錯的課。
很多人的路徑是這樣: 「嗯,我就打開 ChatGPT,寫『使用我的 App』,然後看看它說什麼。如果不行——就隨便改改程式碼。」 這大概就像只看瀏覽器裡的 HTML 頁面來修後端,卻從不打開伺服器日誌。
對於 ChatGPT Apps 特別容易滑向「魔法」:有 GPT,它自己決定要不要呼叫 tool,還有自己的錯誤邏輯。如果你看不到底層在發生什麼,除錯就會變成跳大神。
我們的目標:把這一切變成正規的工程流程:
- 你知道在哪裡看 Next/MCP 的日誌;
- 你能用手動方式透過檢查器呼叫 MCP 伺服器;
- 你明白 Dev Mode 會檢查什麼,以及如何確認 ChatGPT 根本能連到你的伺服器。
最重要的是:你不再用「猜猜看 GPT」來除錯,而是先檢查堆疊的低層——伺服器與通訊協定,再來才是 UI 與模型行為。
2. 心智模型:三個除錯層級
為了不在混亂中沉沒,我們把除錯想成三個層級。可以把它當作我們的小「千層蛋糕」:
| 層級 | 裡面是什麼 | 常見症狀 | 用什麼除錯 |
|---|---|---|---|
| UI(Widget) | React 元件、狀態、window.openai | Widget 空白/灰屏、渲染異常、按鈕無作用 | 瀏覽器 DevTools |
| 後端 / MCP 伺服器 | tools、存取資料庫/API | 500 錯誤、「tool 掛了」、資料怪異 | 伺服器日誌、MCP Inspector |
| MCP 通訊協定 | JSON‑RPC、tools/list、tools/call、Schema | GPT 顯示「無法呼叫工具」、invalid params | 檢查器 + 請求日誌 |
在第二層,我們關心 MCP 伺服器本身(tools、資料庫、API);第三層則是「線路」與 MCP 訊息格式(JSON‑RPC、Schema 等)。
這三層是本課與除錯課程的基礎。
更直觀地,可以看請求流程:
sequenceDiagram
participant User as 使用者
participant ChatGPT as ChatGPT (Dev Mode)
participant Tunnel as 隧道 (ngrok/CF)
participant Next as Next.js + MCP
User->>ChatGPT: "幫我挑一個 50 美元以內的禮物"
ChatGPT->>Next: tools/call search_gifts(透過隧道)
Next->>Next: 呼叫 MCP tool,連到資料庫/API
Next-->>ChatGPT: JSON‑RPC 結果 + ToolOutput
ChatGPT-->>User: 回覆 + 渲染 Widget
任何一點都可能出錯:隧道、endpoint、MCP 邏輯、JSON Schema、React Widget。你的除錯任務是找出究竟是哪一層出了問題,而不是一股腦兒把所有東西重寫。
3. Next.js 與 MCP 的日誌:一切的基礎
從最無聊也最有用的東西開始——日誌。
本機開發時,日誌在哪裡
在 Next.js 的 Apps SDK 標準樣板中,MCP 伺服器通常包在 API route(例如 /api/mcp)裡。你執行 npm run dev 後,單一終端機裡會同時跑:
- Next.js 的開發伺服器;
- MCP endpoint 的處理器,接收 tools/list、tools/call 等 JSON‑RPC 請求;
- console.log/console.error 打印的一切精彩內容。
如果你把 MCP 拆成獨立行程,就會有第二個終端機,但概念一樣:有趣的東西都在主控台看得到。
請分清楚:
- 建置/啟動錯誤——next dev 起不來、TypeScript 掛掉、匯入錯誤等;
- 執行時錯誤——都啟動了,但對 /api/mcp 的特定請求導致某個 tool 掛掉。
Next.js 在開發模式會用漂亮的浮層顯示 runtime 錯誤,並在主控台列出 stack trace。
在 MCP 伺服器中要記錄什麼
雖然 MCP 使用 JSON‑RPC 協定,但除錯不需要把整個 JSON 都印出來。更實用的是結構化但精簡的日誌。
對 MCP 日誌的好做法——至少記錄: timestamp、 request_id/traceId、 工具名稱、 (去識別化的)參數、 狀態(ok/error), 以及執行時間。
GiftGenius 的最簡 logger.ts 可以長這樣:
// 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 }));
}
而在工具的處理器中:
// 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 {
// ... 真正的禮物搜尋邏輯 ...
const results = []; // 佔位
logToolEvent("end", { tool: "search_gifts", traceId, count: results.length });
return results;
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
throw err;
}
}
有兩個重要細節。
第一,不要在日誌中保留完整的 e‑mail、電話、卡號、token。這不只不妥,也違反 MCP 的基本安全實務。
第二,traceId 是你最好的朋友。當你同時看 Next.js 與 MCP 的日誌時,可以用它把事件串起來:特定的 tools/call 請求、對應的 React 渲染,以及 Widget 的網路日誌。
如何從日誌判斷是哪裡出錯
終端機裡滾著 logToolEvent 打出的 JSON 行。典型情境:
- 出現 phase: "start" 搭配 tool: "search_gifts";
- 沒有 phase: "end",但有 phase: "error" 與 stack trace;
- 由此可見,請求已經進入你的工具邏輯,但內部有東西壞了——例如外部 API 請求、剖析、或資料庫操作。
如果你根本看不到對應 tool 名稱的任何日誌——代表請求甚至沒到工具。那就往上查:隧道、/mcp endpoint、tools/call 的 JSON 請求。
4. MCP Inspector:在接入 MCP 給 ChatGPT 前先把它除錯好
如果日誌是你的眼睛,MCP Inspector(或 MCPJam Inspector)就是顯微鏡。
關於 MCP Inspector 與它的用途
在 MCP 模組中,我們已經用 Inspector 來驗證「Hello, MCP」伺服器。這裡把它當主要除錯工具:先確認 MCP 自己活得好,再碰 Dev Mode 與 UI。
Inspector 是一個獨立應用(多半是網頁 UI 加 CLI),扮演 MCP 的客戶端。它透過 HTTP/SSE 或 stdin/stdout 連到你的伺服器,執行 tools/list、tools/call,並顯示原始 JSON 訊息、握手、工具清單、資源等。
核心想法:把 ChatGPT 移出方程式。如果工具不工作,你該先搞清楚伺服器是否存活、協定與 Schema 是否正確,再來怪 GPT。
使用 Inspector 的迷你流程
本機除錯的典型流程如下:
- 啟動 npm run dev,讓 Next.js + MCP endpoint 起來。
- 啟動 MCP Inspector,例如:
npx @modelcontextprotocol/inspector
(實際指令視你使用的工具而定)。
- 在 Inspector 指定你的 MCP endpoint URL,例如 http://localhost:3000/api/mcp(若也想同時檢查隧道,則填 HTTPS 隧道)。
- 查看握手是否通過:伺服器應回應支援的 capabilities、tools 清單、資源等。
- 手動呼叫目標工具:選 search_gifts,填入參數 {"q": "給 30 歲以下女性"},按下「Call tool」,並查看:
- 是否有回應;
- 是否回了 JSON‑RPC 或 MCP 錯誤;
- 伺服器針對這次呼叫,日誌印了什麼。
如果在 Inspector 已經會掛,根本不用打開 ChatGPT:先修好 MCP 伺服器。
如果在 Inspector 一切正常,但 ChatGPT 還是抱怨——問題就在更上層:Dev Mode URL、授權、或模型行為。
「刻意把 tool 弄壞」的示例
拿我們的 search_gifts,手動把它弄壞:
export async function searchGiftsTool(args: { q: string }) {
if (args.q === "壞掉") {
throw new Error("教學用錯誤,用於示範除錯");
}
// ... 正常邏輯 ...
return [];
}
接著:
- 在 Inspector 用參數 {"q": "壞掉"} 呼叫 search_gifts。
- 在日誌看到 phase: "error" 與 stack trace。
- 確認 MCP 伺服器如實回傳了錯誤。
之後把這一切接到 ChatGPT Dev Mode,並請模型「挑一個包含『壞掉』這個詞的禮物」,它會嘗試呼叫工具,並向使用者顯示類似「I encountered an error running the tool」的訊息。可見:錯誤不是來自模型,而是你刻意丟出的例外。
這招能很好地訓練思維:你能清楚劃分 業務錯誤(我們自己丟了 Error)與協定錯誤(JSON 壞了、工具名稱不對等)。
5. Widget 除錯:DevTools、狀態與「debug 橫幅」
當 MCP 伺服器大致搞定後,我們轉到前端——Apps SDK 的 Widget。
在哪裡、如何查看 Widget 的錯誤
你的 Widget 在 ChatGPT 內的 iframe 沙箱裡渲染。好消息是:該 iframe 一樣有瀏覽器 DevTools。
迷你步驟:
- 在瀏覽器(Chrome/Edge/Firefox)打開 ChatGPT。
- 打開 DevTools(通常是 F12 或 Ctrl+Shift+I)。
- 在 Console 分頁選擇你的 Widget 所在的 frame 內容(常見網域是 web-sandbox.oaiusercontent.com)。
- 重新整理對話/送出訊息,讓 GPT 顯示你的 App。
如果 Widget:
- 根本沒出現;
- 顯示為灰色/空白;
- 在主控台出現紅色錯誤
——幾乎可以肯定是 React 端的問題:取用不存在的屬性、錯誤的匯入、歪掉的 hook 等。
Network 分頁也很有用。你會看到:
- 你的應用 JS bundle 的載入(如果是 404/500——問題在 dev 伺服器/隧道端);
- 你的 Widget 透過 window.fetch 發出的請求,以及 4xx/5xx 的回應。
最簡單的 debug 橫幅
很實用的小技巧——在 Widget 的根元件加一個小小的「debug 橫幅」,在 Dev Mode 顯示環境與 build 版本。
例如:
// 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>
);
}
在 Widget 的根元件中:
// src/app/widget/page.tsx
import { DebugBanner } from "@/components/DebugBanner";
export default function GiftGeniusWidget() {
return (
<div>
<DebugBanner />
{/* 其餘的禮物搜尋 UI */}
</div>
);
}
如果你打開了 ChatGPT、啟動了 App,卻看不到橫幅——代表你的 JS 根本沒到瀏覽器:可能是建置錯誤、endpoint 有問題,或是 Widget 根本沒在 MCP 伺服器裡註冊。
本地狀態與錯誤處理
你的 Widget 應該已經會顯示不同狀態:載入、成功、錯誤。如果沒有——現在正是加上的時候。
迷你樣式:
const [status, setStatus] = useState<"idle"|"loading"|"error"|"success">("idle");
async function handleSearch(query: string) {
try {
setStatus("loading");
// 透過 window.openai.callTool 或 Apps SDK 的 hook 呼叫 MCP tool
setStatus("success");
} catch (e) {
console.error("Search failed", e);
setStatus("error");
}
}
在 JSX:
{status === "error" && (
<div style={{ color: "red" }}>發生問題,請再試一次。</div>
)}
對除錯來說很關鍵:
- 不要吞掉例外(否則主控台空空的,而 UI 只是「卡住」);
- 在 UI 明確顯示錯誤,否則使用者會以為 App 死了。
6. 把 Dev Mode 納入除錯:它做了什麼,以及別冤枉它
現在把 ChatGPT Dev Mode 放進圖景。之前我們只看你的程式碼。但有時一切在本機都運作良好,在 Inspector 裡也都 OK,ChatGPT 卻回「Error talking to [AppName]」或根本不提供你的 App。
Dev Mode 做了什麼
Dev Mode 是 ChatGPT 的一個模式,你可以:
- 建立/編輯自己的 Apps;
- 指定 MCP 伺服器的 endpoint(通常是 https://your-domain/mcp 或 /api/mcp);
- 在不發佈到 Store 的情況下快速更新 manifest 與中繼資料。
從除錯角度看,Dev Mode 只是另一層設定:
- 若 URL 寫錯;
- 若忘了最後的 /mcp;
- 若隧道換了新網域,你沒更新設定
——ChatGPT 就根本連不到你的伺服器。
Dev Mode 壞掉的典型情境
經典劇情:
- 你啟了隧道 https://abcd.ngrok.io,在 Dev Mode 設了它,一切正常。
- 隔天重啟 ngrok,變成 https://efgh.ngrok.io。
- Dev Mode 裡仍然是 https://abcd.ngrok.io/mcp。
- ChatGPT 顯示「Error talking to GiftGenius」。
此時將 MCP Inspector 指向 http://localhost:3000/api/mcp,會發現一切正常。這代表 MCP 活著,但 ChatGPT 指向錯了地方。
解法:進入 Dev Mode 設定,更新 URL,別忘了最後的 /mcp。
Dev Mode vs Store
本課僅談 Dev Mode——你的沙箱。在這裡常改 URL、重接隧道、改工具 Schema 都沒問題。
當你之後要上 Store,endpoint 會被更嚴格地固定,此類操作就不妥了。不過距離 Store 還有幾個模組,現在先放心地在 Dev Mode 裡摔打與修復。
7. 迷你除錯流程:當「什麼都不工作」時
現在把一切整理成實作流程。其實就是開頭那三個層級的步驟化版本。
假設你打開 ChatGPT,選了 GiftGenius,說「幫我為極客朋友挑一個 30$ 以內的禮物」,然後:
- GPT 一句不提你的 App;
- 或顯示「Error talking to GiftGenius」;
- 或打開的是空白/灰色的 Widget。
怎麼不陷入絕望?
步驟 1(MCP/伺服器層):用 Inspector 與日誌檢查 MCP
先忽略 GPT 與 UI。我們只看伺服器。
- 確認 npm run dev 已啟動,且 endpoint(/api/mcp)有回應。
- 把 MCP Inspector 連到 http://localhost:3000/api/mcp 或你的隧道。
- 檢查握手——應能看到 tools 清單。
- 手動呼叫 GPT 應該會用到的工具(例如 search_gifts),填入類似的參數。
如果在這一步就掛——修 MCP:Schema、業務邏輯、網路呼叫。利用日誌與 traceId 來鎖定壞點。
步驟 2(協定/Dev Mode 層):檢查 Dev Mode 與 URL
若在 Inspector 一切良好,但 ChatGPT 仍然看不到你的 App 或回報連線問題:
- 打開該 App 的 Dev Mode 設定。
- 看看 MCP 用的是哪個 URL。
- 和你的伺服器/隧道實際在聽的位址比對(若伺服器需要,記得最後要有 /mcp)。
很多時候,問題就在這裡。
步驟 3(UI 層):用 DevTools 檢查 Widget
如果 ChatGPT 成功呼叫了工具(從 MCP 日誌可見),但 Widget 行為很怪:
- 在 ChatGPT 頁面打開瀏覽器 DevTools。
- Console 分頁——選擇你的 Widget 的 iframe 內容。
- 查看 JS 錯誤。
- Network 分頁——確認:
- Widget 的 JS bundle 載入沒有 404/500;
- 額外請求(透過 fetch/window.openai.fetch)回來的是有意義的結果。
同時看看你的 DebugBanner:如果沒出現,代表根本沒進到 React 樹。
步驟 4:用 Dev Mode 重現 bug 回報
拿到同事/使用者的 bug 回報時,盡量保留出問題的精確提示詞。在 Dev Mode 可以很快重現:
- 啟動 npm run dev,開隧道。
- 在 Dev Mode 選你的 App。
- 貼上有問題的提示詞。
- 同時:
- 從 MCP 日誌看有哪些 JSON 請求進來;
- 必要時在 Inspector 用相同參數重放 tools/call。
這樣就能把「有時候不行」變成可重現的情境。
8. 讓除錯更順手的幾個小程式片段
為了加深印象,替我們的 GiftGenius 再補幾個實用片段。
環境設定與日誌層級
在伺服器設定某處明確寫出 MCP 的 endpoint 與日誌層級很方便:
// src/config.ts
export const config = {
mcpEndpoint:
process.env.NODE_ENV === "development"
? "http://localhost:3000/api/mcp" // 隧道會代理這個
: "https://api.giftgenius.com/api/mcp",
logLevel: process.env.NODE_ENV === "development" ? "DEBUG" : "ERROR",
};
在 logToolEvent 中也可以考慮 logLevel,避免在正式環境洗版。
記錄 MCP 結構化錯誤
處理工具時,盡量攔截可預期的錯誤並回傳清楚的訊息,而不是全部都 throw:
export async function searchGiftsTool(args: { q: string }) {
const traceId = crypto.randomUUID();
logToolEvent("start", { tool: "search_gifts", traceId, args });
try {
// ... 正常程式碼 ...
return { content: [{ type: "text", text: "找到 3 個禮物" }] };
} catch (err) {
logToolEvent("error", { tool: "search_gifts", traceId, error: String(err) });
return {
content: [{ type: "text", text: "禮物搜尋發生錯誤。請稍後再試。" }],
isError: true,
};
}
}
如此一來,ChatGPT 會看到結果被標記為 isError,可以正確向使用者說明問題;而你也能從日誌看到發生了什麼。
9. 本機除錯 ChatGPT App 的常見錯誤
錯誤 1:用「看 GPT 回答」來除錯,而不是用伺服器與檢查器。
直接看模型回什麼、然後猜哪裡有 bug 很誘人。但模型是最上層。如果 MCP 伺服器自己就不工作(用 Inspector 手動也不行)——別指望 GPT 會有奇蹟。先把 MCP 穩住,再接 ChatGPT。
錯誤 2:不看日誌,或把所有東西都打到日誌。
沒有日誌就像瞎了眼:你不知道呼叫了哪個工具、用了什麼參數、最後結果如何。過度日誌又會讓主控台變成無法關聯的「綠雨」。最好擁有精簡、結構化的日誌,包含 tool、args(去識別)、traceId、status 與執行時間。
錯誤 3:在日誌中保留敏感資料。
把 token、完整 e‑mail、卡號寫進日誌,是安全與 OpenAI 政策上的雙重地雷。日誌裡只該放真正有助於除錯的資訊,個資要遮罩或乾脆不記。
錯誤 4:把所有問題都怪在 Dev Mode 身上。
Dev Mode 常成代罪羔羊:「一定是 OpenAI 壞了」。其實很多時候是你忘了在隧道重啟後更新 URL,或路徑填錯(/ 寫成了 /mcp)。在寫信求援前,先進 Dev Mode 設定,對一下 endpoint 與實際伺服器地址。
錯誤 5:忽略 DevTools 與 Widget 的錯誤。
空白或灰色的 Widget 幾乎總是前端 JavaScript 錯誤。如果你只看 MCP 日誌,卻不在 ChatGPT 裡打開 DevTools,就只看到了半張圖。養成按 F12 看 Console/Network 的習慣,能省下大把時間。
錯誤 6:用「魔法延遲」來「修」 bug。
有時會想來個 setTimeout 或 Thread.sleep 式延遲「讓東西都載好」。在 MCP/Next/React 的世界幾乎總是錯的:問題通常在 Schema、錯的 endpoint,或程式錯誤,而不是「伺服器來不及」。比起挖洞用延遲填,先找清楚斷點在哪(Inspector → Dev Mode → Widget)。
錯誤 7:還沒確認本機可用就急著部署到 Vercel。
想「快點上線」可以理解,但把壞掉的 MCP 丟上 Vercel,只會得到兩層問題:本機與正式。這個模組我們刻意要求:先 MCP Jam/Inspector → 一切 OK,Dev Mode → 基本情境可跑,最後才部署。
GO TO FULL VERSION