本文同步自 2026 iThome 鐵人賽原文 ,發布日期為 2026-08-05。完整系列可見 iThome 系列頁 。
安安~我是ChiYu~
昨天把專案切到固定 tag,Web、API、測試和 build 也都跑起來了。照這個進度,今天最容易 做的事情,就是立刻替網站加上第一支 WebMCP Tool。
但我先把 Inspector 關掉。
不是它做錯了什麼,只是今天還輪不到它上班。我想先用一般使用者的身分,從活動列表開始, 親手搜尋、看詳情、收藏,再一路走到報名與取消。如果連我自己都說不清楚任務從哪裡開始、 改了什麼狀態,以及哪一步必須停下來,Agent 只會很有效率地把混亂重播一次。
按鈕也不能直接等於 Tool。把「搜尋活動」「收藏活動」「確認報名」翻成三個英文名稱,catalog 確實會立刻變熱鬧,但產品邊界還是一團霧。今天真正要整理的是人類 Journey:使用者想完成 什麼、網站回了什麼,以及決定權最後留在誰手上。
活動網站很適合拿來拆這件事。搜尋與查看詳情是唯讀;收藏會改變 session 狀態,但還能 Undo;報名與取消會影響使用者權益,送出以前應該讓人看清楚內容。同一個網站就能放進不同 風險的操作,不必為了讓 Demo 看起來更豪華,又臨時搬來付款、OAuth 和管理後台。
今天固定使用同一筆公開活動:
| 項目 | 固定值 |
|---|---|
| 活動 ID | evt-webmcp-intro |
| 活動名稱 | WebMCP 入門工作坊 |
| 條件 | 台北、免費、入門 |
| 時間 | 2027 年 1 月 23 日 10:00–12:00 |
| 剩餘名額 | 8 |
我把活動資料固定下來,後面的搜尋結果、route、測試斷言與 Inspector trace 才能互相 核對。文章若寫「前端高峰會」,程式卻回傳「WebMCP 入門工作坊」,兩邊就算都亮綠燈, 也只是各自成功,拼不起同一條證據鏈。

圖 1:三條 Journey 都先由人類走過一次。後面加入 Agent 時,才知道它可以協助到哪裡,又該在哪裡停下來。
用可見表單完成搜尋,沒有 WebMCP 也要能用
開啟 http://127.0.0.1:5173/events,設定:
- 地點:台北
- 費用:免費
- 程度:入門
按下「搜尋活動」後,結果只留下 evt-webmcp-intro,也就是「WebMCP 入門工作坊」。卡片
顯示日期與摘要,也保留 opaque ID,接著由「查看詳情」進入下一步。

圖 2:人類使用可見表單完成搜尋;右下方 audit trail 記錄的是 human 操作。
我刻意先確認這條流程在沒有 document.modelContext 時仍能使用。WebMCP 在這個專案裡
扮演的是 progressive enhancement;瀏覽器不支援,使用者照樣可以填表單、按搜尋、讀取
結果。若加完 Agent 能力後,人類反而不能用了,那不叫漸進增強,比較像換了一扇新門,
順手把原本的門封起來。
搜尋找到活動後,下一個問題也跟著出現:網站要怎麼知道使用者目前正在看哪一場?答案不能 靠 Agent 猜「current」或「active」,而是交給詳情 route。
詳情 route 決定目前活動,收藏狀態交給 Server
按「查看詳情」後,route 變成:
/events/evt-webmcp-intro
詳情頁列出 2027 年 1 月 23 日 10:00–12:00、台北前端共學空間與剩餘 8 名。使用者可以 收藏活動,也可以前往報名。收藏成功後,按鈕與狀態訊息會同步更新;再按一次只會得到 冪等結果,不會趁機多塞一筆。

圖 3:詳情 route 提供完整活動上下文;收藏是低風險寫入,報名則進入另一個流程。
route 在這裡不只是拿來顯示網址。它直接回答「目前是哪一場活動」,讓畫面、API 與後續
Tool 共用 evt-webmcp-intro。等 Agent 進場後,也不需要拿「current」「active」這些看似
合理、其實查無此人的字串代替活動 ID。
收藏則讓我看到低風險寫入該怎麼呈現。它會寫入目前 session,畫面立即顯示已收藏,也 提供 Undo。重新整理後,狀態仍由 Server 回傳,不靠按鈕文字自說自話。這比純前端布林值 更接近產品行為,不過它依然只是 session Demo,不能假裝成永久帳號收藏。
收藏做錯還能 Undo,報名就不能這麼隨便。接下來我把資料填好,但先不讓任何自動化替人 按下最後送出。
報名與取消先準備,最後一步留給人類
從詳情頁前往:
/events/evt-webmcp-intro/register
報名頁顯示活動、姓名與 Email。表單可以先帶入資料,按鈕則明確寫著「我確認並送出
報名」。只有人類真的按下它,/registrations 才會出現一筆有效報名。

圖 4:填好欄位不等於已報名;正式 POST 發生在人類確認之後。
「我的報名」頁也沒有放一顆按下去就直接刪除的按鈕。使用者先選「準備取消」,看完活動 名稱與取消後果,再決定保留報名或確認取消。兩條高影響流程用的是同一個原則:資料可以 先準備,影響要先說清楚,最後一步由人類完成。
這裡先把可見 UI 當成基線。之後 Agent 會協助準備報名與取消,但它走到確認畫面就得停下來; 不能因為參數都填對了,順便把使用者的決定也一起送出去。
用 Playwright 重播 Journey,準備階段必須維持零 POST
手動走過一次還不夠。今天把三條人類 Journey 交給 browser test 重播:
npx playwright test `
tests/browser/events-human.spec.ts `
tests/browser/saved-event.spec.ts `
tests/browser/registration.spec.ts `
tests/browser/cancellation.spec.ts
這一輪得到 8 passed。案例涵蓋鍵盤搜尋、沒有 modelContext 時的 fallback、收藏與 Undo、
報名準備階段零 POST,以及取消 dialog 的 human confirmation。
我最在意的不是畫面有沒有順利跳頁,而是「準備」和「送出」真的分開。報名頁可以先顯示 姓名與 Email,但 network 在準備階段必須維持零 POST;只有人類確認後,Server 才能建立 報名。取消也一樣,打開 dialog 不得改變狀態,確認按鈕才送出 mutation。
這條界線之後會原封不動交給 prepare Tool。Agent 可以省下填表和整理摘要的時間,不能省略 使用者本人。
功能先凍結,明天只把搜尋交給 Playwright
走完這一輪,活動網站已經有資訊讀取、可復原寫入與需要人類確認的操作。再加付款、OAuth、 資料庫或管理後台,專案會變大,今天的問題卻不會因此變清楚。功能就停在這裡。
三條 Journey 都會保留,但第一個交給機器重播的任務,我選搜尋。理由一點也不炫:它是 唯讀,條件與結果都看得見,腳本就算跌倒,也不會多收藏一筆或占掉一個名額。連這段都還 沒拆清楚,就急著自動報名,多少有點把屋頂扛在肩上找地基。
目前 Demo 使用 session 與 Server authority 驗證操作,但它不是正式會員系統。這個 non-goal 得寫清楚,否則 session Demo 很容易在文章裡被讀成完整 RBAC,責任突然膨脹得 比功能還快。
活動案例還有一個實際優點:每個結果都能回到畫面檢查。搜尋後有卡片,收藏後有狀態, 報名後會出現在「我的報名」。Agent 之後真的參與其中,人類也不用只盯著一串聊天紀錄, 猜它剛才是不是偷偷做了什麼。
今天從頭走完以後,搜尋表單、詳情 route、收藏狀態與人類確認都有了可重播的基線。這也 接住了昨天留下的問題:我們不只拿得到同一個網站,現在還知道它原本應該怎麼被人使用。
明天先把其中最安全的一小段交給 Playwright:仍然搜尋同一場活動,只把剛才的操作寫成 瀏覽器腳本。等程式開始找欄位、按按鈕,我們就能看見它究竟理解了「搜尋活動」,還是只 記住我今天按過哪一顆按鈕。