安安~我是ChiYu~
昨天的 search_events 已經找到「WebMCP 入門工作坊」,還順便帶回一個不透明的活動 ID:evt-webmcp-intro。今天我沿著搜尋結果進入詳情頁,準備問下一個很自然的問題:
請整理我現在看的活動名稱、地點、時間與剩餘名額。
這句話對人類毫無難度。頁面上那麼大一個活動名稱,總不會是在介紹隔壁棚的活動。但對
Tool 來說,「現在看的」不是參數,也不是合法 ID;如果網站沒有提供頁面脈絡,Agent 就
只能開始猜 current、active、1,甚至 current_event。
猜中算運氣,猜錯才是日常。今天要處理的,就是讓 Tool 知道使用者正站在哪一頁,同時
也要確保他離開之後,舊 Tool 不會繼續躲在背景裡加班。
這種把 Tool 綁定到目前頁面情境的做法,可以稱為 route-aware context。
活動詳情頁本來就知道自己正在顯示哪一場活動,因此網站可以在註冊 Tool 時,把目前活動的
opaque ID 一起綁進 handler。Agent 不必閱讀網址、解析畫面,更不需要發明 current 這類
不存在的 ID。
它省掉的是重複猜測,不是後端驗證。Tool 仍然只能讀取公開活動,離開詳情頁後也必須解除
註冊。
搜尋基線驗收完後,今天需要的詳情頁程式差異封存在 v3-day-15。這個 tag 記錄的是程式
里程碑,不是文章篇號;先切到這個版本:
git switch --detach v3-day-15
npm ci
npm run dev
這個版本新增正式活動詳情頁、route-aware 的 get_event_details、對應樣式與 browser test。
今天可以驗證兩件事:站在詳情頁時,空 input 能取得目前活動;離開詳情頁後,舊 Tool 會從
catalog 退場。先有這個版本邊界,後面看到 Agent 猜 current 或敲到舊 Tool 時,才知道是
模型行為、瀏覽器生命週期,還是網站契約出了問題。
我先開啟這個頁面:
/events/evt-webmcp-intro
如果 get_event_details 仍強迫模型傳入 event_id,就等於要求 Agent 把網址中已經存在的
資料再抄一次。網站明明知道答案,卻把考卷推回給模型,這個分工有點說不過去。
因此,詳情頁上的 get_event_details 接受空物件 {}。Tool 執行時會從目前 route 的
closure 取得 evt-webmcp-intro,再向 server 查詢公開活動:
input 有 event_id → 驗證格式,再讀取指定的公開活動
input 為 {} → 使用目前詳情 route 綁定的 opaque ID
沒有合法 context → 不註冊 Tool,或回傳明確錯誤
這裡省掉的是重複輸入,不是 server 驗證。route 提供可信的查詢目標;活動是否存在、是否
公開,仍由後端判斷。換句話說,{} 只在這張詳情頁代表「目前活動」,並不是一張可以到處
兌換資料的空白支票。

圖 1:Agent 呼叫 get_event_details 時傳入 {};Tool 從詳情 route 取得正確 ID,回傳活動時間、地點與剩餘名額。
這一題是 SEL-02。Agent 沒有猜 current,也沒有請使用者再念一次網址,而是正確整理出
2027 年 1 月 23 日、台北前端共學空間與剩餘 8 名。到這裡可以證明:自然語言裡的「我
現在看的活動」,已經能落到目前頁面的 get_event_details。
查對活動只是前半場。接著我切回活動列表,想確認詳情頁的 closure 會不會還抓著evt-webmcp-intro 不放。
每次 route 改變,頁面都會重新整理當下需要的 Tool definitions:
await registry.sync(currentDefinitions);
Registry 會比較新舊 catalog。不再需要的 Tool 透過 AbortSignal 解除;同一個 route 重複
render,也不會重複註冊。理想狀態下,catalog 應該回答「這一頁現在能做什麼」,而不是
把使用者曾經走過的每一頁都收藏成 Tool 博物館。
我把今天的驗收拆成兩題:
| Case | 操作 | 驗收重點 | 結果 |
|---|---|---|---|
| SEL-02 | 在活動詳情頁詢問目前活動 | {} 能否取得 route 綁定的活動 |
通過 |
| STALE-01 | 從詳情頁切回列表後繼續對話 | 舊 handler 是否失效,Agent 是否改用新 route 的能力 | 第一輪失敗;修正版 STALE-01-V2 通過 |
STALE-01 的第一輪 Prompt 是:
請先記住我現在看的活動,並告訴我它的名稱。
我原本想測跨頁後能不能延續同一個活動,結果「記住」兩個字先挖了一個坑。Agent 正確
呼叫 get_event_details({}) 之後,又接著呼叫 save_event。在它的理解裡,「記住」近似
「收藏」;但使用者並沒有明確要求寫入,所以這一回合不能算通過。

圖 2:詳情頁公開 get_event_details 與 save_event。黑色區域才是本次 trace;上方英文仍留在輸入框,沒有送出。
這張圖提醒我,測試 Prompt 也屬於契約的一部分。若真正想驗證的是 lifecycle,就不該再
塞一個可能被理解成寫入意圖的詞。下一版要直接說「只讀取,不要收藏或修改資料」。
接著我切回活動列表。這個歷史版本的列表頁 catalog 只剩 search_events,詳情頁上的get_event_details 已經解除註冊:

圖 3:離開詳情頁後,catalog 只剩 search_events。下方保留前一輪 trace;輸入框中的攝影活動文字沒有送出。
Agent 接下來依序嘗試了:
navigate_to_event → catalog 中不存在,瀏覽器拒絕
get_event_details → 詳情頁 Tool 已失效,瀏覽器拒絕
get_saved_events → catalog 中不存在,瀏覽器拒絕
search_events → 目前頁面有效,成功找回活動
這串失敗乍看很熱鬧,實際上要分開判讀。網站的 lifecycle 防線有守住:舊get_event_details 沒有留在 catalog,無效呼叫也沒有真的執行。問題出在 Agent 仍猜了
不存在的 Tool,還試著呼叫已經下班的 handler;最後雖然改用有效的 search_events
找回活動,也只能算復原成功,不能把前面的亂敲門一起當成通過。
Failed to execute ... RegisteredTool 在這裡不是「網站殘留舊 Tool」的證據,正好相反,
它表示瀏覽器拒絕了目前 catalog 以外的呼叫。完整紀錄保留在:
我沒有刪掉第一輪失敗,而是另外建立 STALE-01-V2。這次先把有歧義的「記住」拿掉:
請讀取目前活動的名稱與 event ID;不要收藏,也不要進行其他動作。
在詳情頁,Agent 只呼叫 get_event_details({}),取得活動名稱與evt-webmcp-intro。切回列表後,我再要求它使用剛才的 event ID 查看同一場活動。
這裡最容易誤會,所以我特別講清楚:第二回合不是把詳情頁的舊 handler 留著繼續用。
修正版列表 route 重新註冊了一支新的 get_event_details,而且這一頁必須傳入合法的
opaque event ID。Agent 保留的是對話裡的 evt-webmcp-intro,實際呼叫的則是新 route
當下公開的 Tool。

圖 4:同一段 Inspector 對話跨越詳情與列表 route;第二回合把合法 event ID 交給新 route 的 get_event_details,完整順序以 Copy trace 為準。
這次 Agent 沒有呼叫 save_event,也沒有再猜 catalog 外的 Tool。STALE-01-V2 在
revision 0000009 通過,完整紀錄放在:
舊失敗與新通過必須一起留著,因為它們回答的是兩件不同的事。第一輪暴露 Prompt 歧義與
模型猜 Tool;V2 才證明修正版能完成 route rebind、沿用 opaque ID,而且沒有偷偷寫入。
這些 Inspector 圖片沒有同時拍到網址與 /health 版本座標,因此只能直接證明畫面裡的
行為順序。若要確認 commit、revision 與 image digest,仍要搭配版本證據帳本,不能只靠
截圖右上角那一小塊畫面自行腦補。
把 current 做成一個萬用特殊 ID,看起來很方便,後面卻會冒出更多問題:兩個分頁同時
開著時,它指哪一場?使用者切頁後何時失效?重新整理又該不該沿用?
讓詳情 Tool 只在正確 route 存在,並把合法 ID 綁進 execute context,規則反而比較小。
Agent 可以在對話中保留活動名稱與 ID;每次真的要呼叫時,仍得重新查看目前 catalog,
使用這一頁允許的 Tool 與 input schema。
今天終於讓 get_event_details 找到正確的活動,也確認舊 handler 會在離開頁面後失效。
不過新的問題馬上排隊進場:找到之後到底要回多少資料?只回一句「找到了」很像敷衍,
把 server 物件整包倒出去又太豪邁。明天我會把三種 payload 並排,看看怎麼回得剛剛好。