本文同步自 2026 iThome 鐵人賽原文 ,發布日期為 2026-08-05。完整系列可見 iThome 系列頁

安安~我是ChiYu~

昨天把專案切到固定 tag,Web、API、測試和 build 也都跑起來了。照這個進度,今天最容易 做的事情,就是立刻替網站加上第一支 WebMCP Tool。

但我先把 Inspector 關掉。

不是它做錯了什麼,只是今天還輪不到它上班。我想先用一般使用者的身分,從活動列表開始, 親手搜尋、看詳情、收藏,再一路走到報名與取消。如果連我自己都說不清楚任務從哪裡開始、 改了什麼狀態,以及哪一步必須停下來,Agent 只會很有效率地把混亂重播一次。

按鈕也不能直接等於 Tool。把「搜尋活動」「收藏活動」「確認報名」翻成三個英文名稱,catalog 確實會立刻變熱鬧,但產品邊界還是一團霧。今天真正要整理的是人類 Journey:使用者想完成 什麼、網站回了什麼,以及決定權最後留在誰手上。

活動網站很適合拿來拆這件事。搜尋與查看詳情是唯讀;收藏會改變 session 狀態,但還能 Undo;報名與取消會影響使用者權益,送出以前應該讓人看清楚內容。同一個網站就能放進不同 風險的操作,不必為了讓 Demo 看起來更豪華,又臨時搬來付款、OAuth 和管理後台。

今天固定使用同一筆公開活動:

項目固定值
活動 IDevt-webmcp-intro
活動名稱WebMCP 入門工作坊
條件台北、免費、入門
時間2027 年 1 月 23 日 10:00–12:00
剩餘名額8

我把活動資料固定下來,後面的搜尋結果、route、測試斷言與 Inspector trace 才能互相 核對。文章若寫「前端高峰會」,程式卻回傳「WebMCP 入門工作坊」,兩邊就算都亮綠燈, 也只是各自成功,拼不起同一條證據鏈。

AgentReady Events 三條人類 Journey

圖 1:三條 Journey 都先由人類走過一次。後面加入 Agent 時,才知道它可以協助到哪裡,又該在哪裡停下來。

用可見表單完成搜尋,沒有 WebMCP 也要能用

開啟 http://127.0.0.1:5173/events,設定:

  • 地點:台北
  • 費用:免費
  • 程度:入門

按下「搜尋活動」後,結果只留下 evt-webmcp-intro,也就是「WebMCP 入門工作坊」。卡片 顯示日期與摘要,也保留 opaque ID,接著由「查看詳情」進入下一步。

人類使用篩選條件找到 WebMCP 入門工作坊

圖 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 名。使用者可以 收藏活動,也可以前往報名。收藏成功後,按鈕與狀態訊息會同步更新;再按一次只會得到 冪等結果,不會趁機多塞一筆。

WebMCP 入門工作坊詳情與收藏入口

圖 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:仍然搜尋同一場活動,只把剛才的操作寫成 瀏覽器腳本。等程式開始找欄位、按按鈕,我們就能看見它究竟理解了「搜尋活動」,還是只 記住我今天按過哪一顆按鈕。