前端開發 別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站 · 第 11 / 30 篇

Day 11|什麼是 Declarative API?讓 HTML 表單成為 WebMCP Tool

2026 iThome 鐵人賽 WebMCP Day 11:安安~我是ChiYu~ 昨天打開 Inspector 後,Chrome 終於把目前頁面的 Tool 列了出來。面板裡有名稱、description, 還有一大段 input schema。 看到 catalog 當然很開心,但我很快又卡在下一個問題…

快速導覽 查看本文目錄

本文同步自 2026 iThome 鐵人賽原文 。完整系列可見 iThome 系列頁 。

安安~我是ChiYu~

昨天打開 Inspector 後,Chrome 終於把目前頁面的 Tool 列了出來。面板裡有名稱、description, 還有一大段 input schema。

看到 catalog 當然很開心,但我很快又卡在下一個問題:我並沒有另外寫一份 JSON Schema, 這些欄位到底從哪裡冒出來?

如果直接研究完成版搜尋頁,表單、API、畫面更新與商業規則會全部擠在一起。每個東西都像 答案,也就很難知道真正的答案是哪一個。因此我先把正式網站放到旁邊,另外做一張最小搜尋 表單,只觀察 HTML 如何形成 Declarative Tool。

先切到只有 Declarative Tool 核心的版本

今天使用的程式里程碑是 v3-day-11。先把版本切對,再往下看表單,不然目前的 main 已經 累積後續的自動送出與參數說明,很容易把明後天才會出現的答案一起帶進來:

git switch --detach v3-day-11
npm ci

這個版本只替原本可操作的搜尋表單加入 Declarative Tool 的核心身分,還沒有 toolautosubmit、respondWith() 與正式 Agent submission。今天只驗證三件事:

  1. 人類仍能使用同一張表單搜尋活動。
  2. toolname 與 tooldescription 會形成穩定的 Tool 身分。
  3. 表單欄位會提供 schema 所需的參數語意。

tag 內的 Lab 路徑仍保留早期名稱 labs/day-08-declarative-tool/。資料夾名稱沒有跟著文章天數 搬家,重播座標仍以 v3-day-11 為準。

HTML 表單語意形成 Declarative Tool 契約

圖 1:Tool 的身分來自 form,表單欄位則提供參數語意。這張圖說明 E2 契約;實際產生的 schema 仍要以瀏覽器輸出為準。

Declarative API 如何把現有表單變成 Tool?

Declarative Tool 不是要我們用 JavaScript 另外重寫一套功能,而是替現有 HTML <form> 加上 WebMCP 標註。

瀏覽器會讀取表單的 toolname、tooldescription,再從欄位的 name、label、選項與限制 條件整理出結構化 input schema。於是同一張表單會有兩種讀法:人類看到輸入框、下拉選單與 按鈕;Agent 看到 Tool 名稱、用途與可接受的參數。

原本存在的表單補上必要標註後,就能成為瀏覽器可以公開給 Agent 的 Tool,不必另外維護一張 「Agent 專用搜尋表單」。

Declarative Tool 可以做什麼在這個活動網站的例子
把既有 HTML form 公開成 Tool將活動搜尋表單公開為 search_events
從表單欄位形成結構化參數整理出 query、location、price、level
讓 Agent 將參數填回可見表單把「台北、免費、入門」放進對應欄位
保留人類原本的操作方式沒有 WebMCP 時仍可手動填表並搜尋
視設定決定是否自動送出後續再加入 toolautosubmit 與 Tool result

Declarative Tool 適合原本就能用表單表達的任務,例如搜尋、篩選、建立客服需求或準備預約 資料。它的優點是 Human UI 與 Agent Tool 共用同一份欄位語意,不必各自維護一套契約。

但它不是所有功能的萬用轉接頭。像「讀取目前 route 的活動」或「依頁面狀態動態註冊、移除 Tool」,沒有一張固定表單可以承載,就比較適合明天要介紹的 Imperative API。

還有一條界線要先畫清楚:表單成功註冊成 Declarative Tool,只代表網站已把能力與參數說明 交給瀏覽器。它不能保證 Agent 一定會選中,也不會替 server 完成權限、安全與商業規則檢查。 真正的 discovery、參數選擇與 invocation,仍要交給後面的 Inspector trace。

同一張搜尋表單繼續服務人類

這份 Lab 原本就是一張可以操作的 HTML 表單。人類選擇地點、費用與程度,按下「搜尋活動」 後,頁面仍會顯示符合條件的結果。

我沒有複製另一張 Agent 專用表單,只在原本的 <form> 補上兩個 attribute:

<form
  id="event-search"
  toolname="search_events"
  tooldescription="依關鍵字、地點、費用與程度搜尋目前公開活動,並更新使用者可見的活動列表。">
  <!-- query、location、price、level -->
</form>

toolname 是 Tool 的穩定識別;tooldescription 告訴 Agent 這支 Tool 會做什麼、資料範圍在哪, 以及結果會如何呈現在頁面上。

這兩個 attribute 必須成對出現。移除其中任何一個,這張 form 就不再註冊為 Tool。也就是說, 按鈕上的「搜尋活動」可以改成「找活動」,search_events 卻不應跟著 UI 文案每天換名字。

property name 與允許值,原本就藏在表單欄位裡

Tool 有了名稱,接著輪到參數。Declarative API 會從表單欄位整理 property name、說明與允許 值。HTML constraint 也留在同一張表單裡,但是否被映射成特定 JSON Schema keyword,仍要看 當下瀏覽器輸出,不能只靠原始碼先替它宣布答案:

HTML 線索人類在畫面上看到什麼Tool schema 得到什麼
<label for="location">「地點」參數用途
name="location"不直接顯示穩定的 property name
<option value="taipei">「台北」可接受的 enum value
maxlength="100"關鍵字長度限制本篇只驗證 HTML constraint,不預先宣稱 schema 映射結果

這裡最容易被忽略的是 name。id 讓 label 能找到欄位,name 才是提交資料與 schema 使用的 鍵。畫面上兩者常常剛好寫成一樣,所以少寫一個時,肉眼不一定立刻看得出來。

toolparamdescription 可以替個別參數補上更精確的說明。沒有這個 attribute 時,瀏覽器會先 使用關聯 label 的內容;若 label 不足以解釋輸入格式或資料來源,再補上參數說明,比每個欄位 預先貼一段重複文案更實在。

欄位少了 name,畫面照常出現,契約卻少一塊

接著我故意把穩定鍵拿掉:

<label>
  地點
  <select>
    <option value="taipei">台北</option>
  </select>
</label>

瀏覽器還是會畫出一個寫著「地點」的下拉選單,人類也看得懂。問題出在資料送出時沒有欄位 名稱,Tool 無法穩定取得 location 這個 property。畫面沒有爆炸,契約只是安靜地缺了一角; 這種 bug 最擅長在 Demo 時裝沒事。

另一個常見誤會是把 placeholder 當成規格。placeholder 適合提示輸入範例,卻會跟著文案調整; 拿它代替 name 或欄位說明,等於讓資料契約跟著行銷文字一起漂流。

我替這張表單留下的規則很單純:可見文字要讓人類理解,name 與允許值要讓資料契約保持 穩定,HTML constraint 則繼續保護人類操作。兩邊描述的是同一個搜尋任務;Chrome 最後合成 哪一段 schema,交給實際 catalog 回答。

實際驗收表單與 Tool 契約

啟動網站後,開啟 Lab、選擇台北並送出,先確認人類操作仍能得到「WebMCP 入門工作坊」。 畫面能用之後,再執行兩支 focused tests:

npm test -- tests/unit/day-08-declarative.test.ts
npx playwright test tests/browser/day-08-declarative.spec.ts

unit test 鎖住 search_events 與 description,避免重構時偷偷改掉 Tool 身分;browser test 則真的操作表單,確認多了 WebMCP attribute 之後,人類 fallback 沒有被弄壞。

這些結果屬於 E2:程式契約與 deterministic tests 可以重播。它們還不能證明真實 Agent 會從 自然語言選中 search_events,也沒有證明 Inspector 在這個 Lab 上完成 discovery。

目前 main 的讀者版 Lab 已經累積後續功能,所以會看到 toolparamdescription 與 toolautosubmit。那不是今天的程式壞掉,而是未來版本已經往前走;要重播本文,就留在 剛才切好的固定 tag。

表單會描述能力了,動態頁面卻沒有固定 form

今天完成後,同一張搜尋表單有了兩種讀法:人類照常看 label、選條件、按按鈕;瀏覽器則從 toolname、tooldescription 與欄位語意整理出 Tool 契約。

搜尋表單一直待在頁面上,Declarative API 很適合它。活動詳情就麻煩了:使用者進入某場活動 時才應該看見 get_event_details,離開後舊 Tool 也得跟著消失。明天改用 Imperative API, 處理 Tool 的註冊、去重與解除,看看上一場活動的 event ID 會不會忘記下班。

參考資料

讀完了!你覺得這篇如何?

你的回饋會決定下一篇要寫什麼。

還有疑問

Learning path

別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站

這篇是系列中的第 11 / 30 篇。可以順著列表補完整脈絡,也可以直接跳到需要的段落。

  1. 01 Day 01|網站真的會被 Agent 使用嗎?我先丟了一句自然語言
  2. 02 Day 02|換到全新資料夾後,網站還能不能跑?
  3. 03 Day 03|先別急著做 Tool,我把活動網站自己走了一遍
  4. 04 Day 04|按鈕只多包一層,哪種 Playwright Locator 撐得住?
  5. 05 Day 05|只改按鈕四個字,Playwright 為什麼停在 click 以前?
  6. 06 Day 06|Playwright、REST API、MCP 與 WebMCP,各自負責哪一段?
  7. 07 Day 07|Agent 偶爾答對還不夠,我先出二十道測試題
  8. 08 Day 08|WebMCP Tool 該怎麼設計?把「點按鈕」改成「搜尋活動」
  9. 09 Day 09|網站該公開哪些 WebMCP Tool?從操作盤點到五支正式能力
  10. 10 Day 10|Inspector 裝好了,為什麼 Tool 面板還是一片空白?
  11. 11 Day 11|什麼是 Declarative API?讓 HTML 表單成為 WebMCP Tool 目前閱讀
  12. 12 Day 12|Tool 註冊完還不算完成:離開 route 後誰把它收回來?
  13. 13 Day 13|第一次按下 Inspector 的 Send,先被 Gemini 429 擋在門外
  14. 14 Day 14|五支 Tool 不再往外長:把產品範圍寫成會亮紅燈的契約
  15. 15 Day 15|讓人類與 Agent 共用同一套活動搜尋規則
  16. 16 Day 16|Agent 怎麼知道「我現在看的活動」是哪一場?
  17. 17 Day 17|Tool 回太少做不完,回太多又把內部資料倒給 Agent
  18. 18 Day 18|活動服務暫時失敗時,Agent 應該重試還是停下來?
  19. 19 Day 19|同一場活動收藏兩次,網站為什麼只能留下同一筆?
  20. 20 Day 20|表單填好了,為什麼這次報名仍然不能算通過?
  21. 21 Day 21|一句「這個我不要了」,Agent 到底敢不敢直接取消?
  22. 22 Day 22|五支 Tool 如何共用同一套規則,串成三條完整流程?
  23. 23 Day 23|在問 Agent 聰不聰明前,先證明網站沒有壞
  24. 24 Day 24|活動摘要叫 Agent 洩漏祕密,它會把這句話當資料還是命令?
  25. 25 Day 25|localhost 成功不算交付:怎麼證明公開網址跑的是同一份程式?
  26. 26 Day 26|把自然語言送進公開站,Agent 真的會自己選對 Tool 嗎?
  27. 27 Day 27|寫入型 Tool 怎麼驗收?把 Trace、Network、UI 與 Server 狀態放在一起看
  28. 28 Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層?
  29. 29 Day 29|把網站交給 Agent 前,我最後會檢查哪十件事?
  30. 30 Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼?

Further reading

Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼? 封面圖 2026-09-30 Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼? Day 29|把網站交給 Agent 前,我最後會檢查哪十件事? 封面圖 2026-09-29 Day 29|把網站交給 Agent 前,我最後會檢查哪十件事? Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層? 封面圖 2026-09-28 Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層?

留言討論

用 GitHub 帳號留言,留言會同步到這個 Blog 的 GitHub Discussions。

閱讀導覽

約 6 分鐘

本文目錄

本篇所在系列 · 第 11 / 30 篇

別再讓 Agent 猜按鈕:30 天打造並實測 Agent-ready 的 WebMCP 活動網站

  1. Day 01|網站真的會被 Agent 使用嗎?我先丟了一句自然語言
  2. Day 02|換到全新資料夾後,網站還能不能跑?
  3. Day 03|先別急著做 Tool,我把活動網站自己走了一遍
  4. Day 04|按鈕只多包一層,哪種 Playwright Locator 撐得住?
  5. Day 05|只改按鈕四個字,Playwright 為什麼停在 click 以前?
  6. Day 06|Playwright、REST API、MCP 與 WebMCP,各自負責哪一段?
  7. Day 07|Agent 偶爾答對還不夠,我先出二十道測試題
  8. Day 08|WebMCP Tool 該怎麼設計?把「點按鈕」改成「搜尋活動」
  9. Day 09|網站該公開哪些 WebMCP Tool?從操作盤點到五支正式能力
  10. Day 10|Inspector 裝好了,為什麼 Tool 面板還是一片空白?
  11. Day 11|什麼是 Declarative API?讓 HTML 表單成為 WebMCP Tool
  12. Day 12|Tool 註冊完還不算完成:離開 route 後誰把它收回來?
  13. Day 13|第一次按下 Inspector 的 Send,先被 Gemini 429 擋在門外
  14. Day 14|五支 Tool 不再往外長:把產品範圍寫成會亮紅燈的契約
  15. Day 15|讓人類與 Agent 共用同一套活動搜尋規則
  16. Day 16|Agent 怎麼知道「我現在看的活動」是哪一場?
  17. Day 17|Tool 回太少做不完,回太多又把內部資料倒給 Agent
  18. Day 18|活動服務暫時失敗時,Agent 應該重試還是停下來?
  19. Day 19|同一場活動收藏兩次,網站為什麼只能留下同一筆?
  20. Day 20|表單填好了,為什麼這次報名仍然不能算通過?
  21. Day 21|一句「這個我不要了」,Agent 到底敢不敢直接取消?
  22. Day 22|五支 Tool 如何共用同一套規則,串成三條完整流程?
  23. Day 23|在問 Agent 聰不聰明前,先證明網站沒有壞
  24. Day 24|活動摘要叫 Agent 洩漏祕密,它會把這句話當資料還是命令?
  25. Day 25|localhost 成功不算交付:怎麼證明公開網址跑的是同一份程式?
  26. Day 26|把自然語言送進公開站,Agent 真的會自己選對 Tool 嗎?
  27. Day 27|寫入型 Tool 怎麼驗收?把 Trace、Network、UI 與 Server 狀態放在一起看
  28. Day 28|Agent 選錯、傳錯或答錯時,怎麼知道問題卡在哪一層?
  29. Day 29|把網站交給 Agent 前,我最後會檢查哪十件事?
  30. Day 30|網站終於會說話:做完五支 Tool 後,我學到什麼?
看完整系列 30 篇