iT邦幫忙

2026 iThome 鐵人賽

DAY 11
0
Modern Web

網站終於會說話:30 天實作並驗證 Agent-ready 的 WebMCP 活動網站系列 第 11

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

  • 分享至 

  • xImage
  •  

安安~我是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 的核心身分,還沒有
toolautosubmitrespondWith() 與正式 Agent submission。今天只驗證三件事:

  1. 人類仍能使用同一張表單搜尋活動。
  2. toolnametooldescription 會形成穩定的 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 標註。

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

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

Declarative Tool 可以做什麼 在這個活動網站的例子
把既有 HTML form 公開成 Tool 將活動搜尋表單公開為 search_events
從表單欄位形成結構化參數 整理出 querylocationpricelevel
讓 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 映射結果

這裡最容易被忽略的是 nameid 讓 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、選條件、按按鈕;瀏覽器則從
toolnametooldescription 與欄位語意整理出 Tool 契約。

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

參考資料


上一篇
Day 10|Inspector 裝好了,為什麼 Tool 面板還是一片空白?
下一篇
Day 12|Tool 註冊完還不算完成:離開 route 後誰把它收回來?
系列文
網站終於會說話:30 天實作並驗證 Agent-ready 的 WebMCP 活動網站23
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言