Imperative API 會使用 document.modelContext.registerTool(),以程式輔助方式定義 JavaScript 工具。這項功能非常適合單頁應用程式 (SPA),可根據目前的路徑或使用者狀態,新增或移除工具。
WebMCP 採用供應商/消費者架構:
- 網頁是工具產生器:您可以使用
document.modelContext.registerTool()定義及註冊工具。網頁只要導入這些項目即可。您不需要在頁面上呼叫getTools()或executeTool(),即可向代理提供工具。 - 瀏覽器和擴充功能代理是主要消費者:由瀏覽器或透過瀏覽器提供的代理 (包括瀏覽器內建代理、瀏覽器擴充功能、開發人員工具和系統層級助理),會透過內部平台機制,在頻外探索及叫用已註冊的工具。
- 網頁內代理和測試控管工具:
getTools()和executeTool()專為網頁內 JavaScript 助理 (例如嵌入式即時通訊小工具或<iframe>) 和自動化測試套件而設計,這些套件需要直接在網頁內容中探索及叫用工具。
註冊和生命週期
如要註冊工具,請傳遞工具定義物件和選用的選項物件 (ModelContextRegisterToolOptions),後者支援兩個選用成員:
signal:用於在不再需要工具時取消註冊工具的AbortSignal。exposedTo:來源字串陣列,用於控制文件樹狀結構中的哪些文件,可以跨框架邊界探索及執行工具。
使用 AbortController 處理生命週期
WebMCP 不提供 unregisterTool() 方法。如要取消註冊工具,您必須在註冊時傳遞 AbortSignal,並在不再需要工具時取消該信號。
const controller = new AbortController();
await document.modelContext.registerTool({
name: "get_user_preferences",
description: "Retrieves the user's saved preferences.",
inputSchema: { type: "object", properties: {} },
execute() {
const prefs = localStorage.getItem("user_prefs");
return prefs ? JSON.parse(prefs) : { theme: "light" };
},
annotations: { readOnlyHint: true }
}, { signal: controller.signal });
// To unregister the tool (e.g., on component unmount):
controller.abort();
取消註冊信號只會取消註冊工具,不會取消已在執行的作業。啟動長時間執行工作的工具也必須遵循下述的每次執行信號,否則取消註冊會留下孤立的要求和過時的 UI 更新。
使用 exposedTo 控管跨來源工具的公開程度
根據預設,註冊的工具僅限註冊文件和同源框架使用。如果應用程式嵌入 <iframe> (或嵌入第三方小工具),且需要上層或同層頁框來探索及叫用工具,請使用 exposedTo 指定允許的來源。
const controller = new AbortController();
await document.modelContext.registerTool({
name: "get_cart_items",
description: "Retrieves items currently in the user's shopping cart.",
inputSchema: { type: "object", properties: {} },
async execute(input, { signal }) {
return await cartService.getItems({ signal });
},
annotations: { readOnlyHint: true },
}, {
signal: controller.signal,
exposedTo: ["https://checkout.example.com"],
});
- 雙向選擇加入握手:跨源工具探索是雙向選擇加入。工具供應商框架會透過
exposedTo授權消費者來源,而消費者框架必須透過getTools({ fromOrigins: [...] })明確要求該供應商來源。如果沒有fromOrigins,getTools()只會查詢相同來源的文件。 - 來源比對:登記文件的來源一律可以存取。
exposedTo中指定的來源會獲得權限,可透過document.modelContext.getTools({ fromOrigins: [...] })探索工具,並透過document.modelContext.executeTool()執行工具。 - 安全性規定:
exposedTo中的每個來源都必須是潛在可信任來源 (HTTPS 或 localhost)。如果傳遞無效或不安全的來源,registerTool()會拒絕並傳回SecurityError。 - 範圍:
exposedTo控制文件樹狀結構中跨頁框的網頁內跨文件探索。
定義參數
參數 (params) 是使用 inputSchema 屬性定義。這必須是 JSON 結構定義物件,用於描述工具預期的結構化資料。
await document.modelContext.registerTool({
name: "calculate_area",
description: "Calculates the area of a rectangle.",
inputSchema: {
type: "object",
properties: {
width: { type: "number", description: "The width of the rectangle." },
height: { type: "number", description: "The height of the rectangle." }
},
required: ["width", "height"]
},
execute(input) {
// input is { width: 10, height: 20 }
return input.width * input.height;
},
annotations: { readOnlyHint: true }
});
撰寫工具和參數說明
代理已收到完整的 inputSchema,因此 description 只需要說明工具的功能和使用時機。請勿在說明中重複參數名稱、類型、enum 值、預設值或限制,而應在結構定義 (enum、default、minimum/maximum) 中宣告。參數說明也遵循相同規則:說明值的意義,而非類型或允許的值。
瀏覽器不會根據 inputSchema 驗證引數,也不會套用 default 值,因此請在 execute 中執行這兩項操作。
const SORT_ORDERS = ["newest", "price_low_to_high"];
await document.modelContext.registerTool({
name: "search_products",
// Don't list the sort orders here, even via SORT_ORDERS: `enum` already does.
description: "Searches the product catalog by keyword. Use when the user wants to find or compare products.",
inputSchema: {
type: "object",
properties: {
query: { type: "string", description: "Words to search for." },
sort: { type: "string", enum: SORT_ORDERS, default: "newest", description: "Order of the results." },
},
required: ["query"],
},
async execute({ query, sort = "newest" }, { signal }) {
if (typeof query !== "string" || !SORT_ORDERS.includes(sort)) {
return { error: "Pass a search query and a supported sort order.", sortOrders: SORT_ORDERS };
}
return await productService.search({ query, sort }, { signal });
},
annotations: { readOnlyHint: true },
});
執行模式
execute 回呼一律會收到兩個引數:輸入物件,以及包含 AbortSignal 的選項物件 (execute(inputObject, { signal }))。
使用 async execute 的時機
如果工具涉及會傳回 Promise 或需要時間完成的作業,請使用 async:
- 網路呼叫:從 API 擷取資料。
- 非同步儲存空間:存取 IndexedDB。
- 外部事件:等待特定狀態變更或動畫完成。
async execute(input, { signal }) {
const response = await fetch(`/api/data/${input.id}`, { signal });
return await response.json();
}
execute (同步) 的使用時機
如要立即執行作業,請使用標準同步函式。同步工具會執行到完成,才會觀察到取消信號,因此可能會忽略該信號:
- 純邏輯:數學、篩選或排序已在記憶體中的資料。
- 同步狀態:從
localStorage或同步狀態管理工具讀取。
execute(input) {
return input.items.filter(item => item.active);
}
處理 execute 中的錯誤
當 execute 擲回 (或傳回遭拒的 Promise) 時,代理只會收到一般執行失敗。錯誤訊息不會轉寄給服務專員。如果工具遇到可採取行動或可復原的錯誤 (例如輸入無效、缺少項目或 HTTP 狀態失敗),請不要 throw new Error(...)。請改為透過工具的一般傳回值,傳回結構化錯誤酬載,讓 AI 代理取得診斷詳細資料,並自行修正或重試:
execute({ itemName }) {
const item = menuItems.find((entry) => entry.name.toLowerCase() === itemName.toLowerCase());
if (!item) {
// Return the error as a value instead of throwing, so the agent receives the message:
return {
error: `Menu item "${itemName}" not found.`,
availableItems: menuItems.map((entry) => entry.name),
};
}
return item;
}
只有在執行作業取消時,才重新擲回 (signal.aborted 為 true)。
取消執行中的作業
取消作業由使用者或服務專員發起。強制:任何執行網路要求或其他長時間非同步工作的工具,都必須遵循 signal,否則已取消的執行作業會繼續消耗資源,並在代理程式繼續執行後,將過時資料寫入 UI。
- 接受信號的 API (
fetch()、addEventListener()、ReadableStream讀取器):直接傳遞signal。 - 不支援取消作業的 API (
setTimeout、setInterval、輪詢迴圈、await密集管道):在步驟之間呼叫signal.throwIfAborted(),和/或在signal.addEventListener('abort', ...)上拒絕。計時器不接受信號,因此您必須在取消事件監聽器中自行清除。
await document.modelContext.registerTool({
name: "fetch_tool",
description: "Fetch the text content of a URL and show it on the page.",
inputSchema: {
type: "object",
properties: {
url: { type: "string", description: "The URL to fetch" },
priority: {
type: "string",
enum: ["high", "low", "auto"],
description: "Relative network priority for the request",
},
},
required: ["url"],
},
execute: async ({ url, priority }, { signal }) => {
const output = document.querySelector("pre");
output.textContent = "Loading…";
try {
// Passing `signal` aborts the network request the moment execution is cancelled.
const response = await fetch(url, { priority, signal });
if (!response.ok) {
output.textContent = "";
return { error: `Request failed with status ${response.status}` };
}
const text = await response.text();
output.textContent = text;
return text;
} catch (err) {
// Roll back the UI state this execution set, so a cancelled run leaves no trace.
output.textContent = "";
if (signal.aborted) {
// The execution was cancelled: re-throw instead of returning an error value.
throw err;
}
// Return other errors as a value instead of throwing, so the agent receives the message.
return { error: err instanceof Error ? err.message : String(err) };
}
},
annotations: { readOnlyHint: true }
});
標記重要工具
自 Chrome 154 起,annotations 支援 consequentialHint。如果工具會執行高風險、不可逆或實際動作 (例如預訂行程、轉移資金、傳送訊息或刪除資料),請將這項設定設為 true。這會指示代理在執行前,先要求使用者明確確認。
await document.modelContext.registerTool({
name: 'book_flight',
description: 'Book a flight for the user with confirmed flight details.',
inputSchema: {
type: 'object',
properties: {
flightId: { type: 'string', description: 'ID of the flight to book' },
passengers: { type: 'number', description: 'Number of tickets to purchase' },
},
required: ['flightId', 'passengers'],
},
execute: async ({ flightId, passengers }, { signal }) => {
// Forward `signal` to the transaction API to honor cancellation:
return await bookFlightReservation({ flightId, passengers }, { signal });
},
annotations: {
readOnlyHint: false, // This tool mutates state (it makes a booking).
consequentialHint: true, // Spends money and is not trivially reversible.
untrustedContentHint: false, // Output is first-party data, not third-party content.
}
});
強制:請將 consequentialHint 視為提示,而非強制機制。這項功能本身不會阻擋執行,而且早於提示的瀏覽器完全不會顯示提示。保留動作本身會觸發的應用程式內確認訊息。
檢舉偵錯工具
自 Chrome 156 版起,annotations 支援 debugging。如果工具是專為偵錯和開發人員工具而建構,而非使用者工作,請將 annotations.debugging 設為 true。例如:傾印內部應用程式狀態、切換功能旗標,或植入測試資料。這樣一來,代理就能區分開發人員專用工具和代表使用者執行的工具。預設值為 false。網頁內代理程式和測試控管工具可透過 getTools() 從 RegisteredTool.annotations.debugging 讀取。
await document.modelContext.registerTool({
name: "dump_app_state",
description: "Returns a snapshot of the internal application store for debugging.",
execute() {
return appStore.getState();
},
annotations: {
readOnlyHint: true, // Only reads state.
debugging: true, // Developer tooling, not an end-user action.
}
});
網頁內代理和測試 (getTools 和 executeTool)
網頁不需要 getTools() 或 executeTool(),就能向 AI 代理公開工具。只要呼叫 document.modelContext.registerTool(),瀏覽器代理、瀏覽器擴充功能、開發人員工具和系統層級助理,就能在頻外探索及執行工具。
getTools() 和 executeTool() 方法專為在 JavaScript 環境中執行的消費者而設計:
- 網頁內 JavaScript 代理:直接嵌入網頁 (或在
<iframe>內執行) 的 AI 助理、Copilot 小工具或對話介面,需要探索周圍網頁或文件樹狀結構中可存取框架提供的工具,並代表使用者叫用這些工具。 - 自動化測試和偵錯:測試套件 (例如 Playwright、Vitest 或瀏覽器內單元測試) 可檢查已註冊的工具中繼資料,並模擬代理工具叫用,不需使用實際的外部 LLM 測試架構。
使用 getTools() 探索可用工具
網頁內代理程式會呼叫 document.modelContext.getTools(),從目前文件和文件樹狀結構中任何可存取的頁框,擷取 RegisteredTool 物件陣列。
自 Chrome 155 起,RegisteredTool.inputSchema 會直接以 JavaScript 物件的形式傳回 (註冊時提供的結構定義深層副本)。變更這個物件不會影響已註冊工具的實際結構定義。
// Example: An in-page AI assistant discovering tools available on the page:
const tools = await document.modelContext.getTools();
// In-page agents can listen for tools being registered or unregistered dynamically:
document.modelContext.addEventListener("toolchange", async () => {
const updatedTools = await document.modelContext.getTools();
console.log(`Active tools updated: ${updatedTools.map((t) => t.name).join(", ")}`);
});
如要探索在跨源子系 iframe 中註冊的工具 (必須在註冊期間透過 exposedTo 明確授予存取權),請在選項字典中傳遞 fromOrigins:
// Query tools registered in the embedded cart frame that exposed them to this checkout origin:
const cartTools = await document.modelContext.getTools({
fromOrigins: ["https://cart.example.com"],
});
使用 executeTool() 叫用工具
網頁內代理和測試指令碼會呼叫 document.modelContext.executeTool(tool, inputObject, options),執行已註冊的工具。工具會在註冊的文件上執行,且 Promise 會解析為工具 execute 回呼傳回的字串化結果。
自 Chrome 155 起,executeTool() 可直接接受 inputObject 的一般 JavaScript 物件,與工具 execute 回呼接收的輸入格式相符。
// 1. Discover the tool (typically done during agent initialization or test setup):
const tools = await document.modelContext.getTools();
const areaTool = tools.find((t) => t.name === "calculate_area");
if (areaTool) {
const controller = new AbortController();
// Example input payload matching the tool's inputSchema:
const inputObject = { width: 10, height: 20 };
const options = { signal: controller.signal };
// 2. Pass input arguments as a JavaScript object, with a JSON string fallback for Chrome <155:
let resultString;
try {
resultString = await document.modelContext.executeTool(areaTool, inputObject, options);
} catch (err) {
if (err?.message?.startsWith("Failed to parse input")) {
resultString = await document.modelContext.executeTool(
areaTool,
JSON.stringify(inputObject),
options
);
} else {
// Re-throw real tool execution or cancellation errors:
throw err;
}
}
console.log("Execution output string:", resultString);
}
如果發生下列情況,傳回的 Promise 會遭到拒絕:
inputObject不是物件,或無法序列化為 JSON 字串 (例如循環參照或BigInt值)。請注意,JSON 序列化省略的值 (例如函式和undefined) 會從工具接收的物件中省略,而非導致拒絕。- 執行作業會透過
options.signal中止。 - 工具在執行前未註冊,或發生未處理的執行階段錯誤。
工具工廠模式
如要將情境 (例如商店或應用程式執行個體) 傳遞至工具,請使用工廠函式。
export function createInventoryTool(inventoryManager) {
return {
name: "get_inventory",
description: "Lists items in the inventory.",
inputSchema: { type: "object", properties: {} },
execute() {
return inventoryManager.getItems();
},
annotations: { readOnlyHint: true }
};
}
TypeScript 類型
在 TypeScript 專案中,請勿手寫 document.modelContext 的宣告。安裝官方 webmcp-types 套件 (npm install --save-dev webmcp-types),並新增至 tsconfig.json,因為系統不會自動擷取:
{
"compilerOptions": {
"types": ["webmcp-types"]
}
}
document.modelContext的類型為選用,因此請先使用if (document.modelContext)保護,再使用。- 使用內嵌
inputSchema時,系統會從結構定義推論execute輸入類型。 WebMCP.ModelContextTool等具名類型為全域類型 (不需匯入),適合用於輸入工具工廠。
框架支援
必要:如果應用程式使用 React、Angular 或 Vue,請透過該架構的官方 WebMCP 整合功能註冊工具 (請見下方清單)。請勿從元件呼叫 document.modelContext.registerTool(),或自行編寫生命週期包裝函式。每項整合功能都已處理功能偵測、AbortSignal 清理,以及元件或注入器生命週期的範圍工具。這些 API 仍處於實驗階段,因此在編寫程式碼之前,請先在套件的類型或文件中查詢目前的簽章。
| 架構 | 套件 | API | 文件 |
|---|---|---|---|
| React 18+ | use-webmcp-tool |
useWebMCP(options):每個工具呼叫一次 |
npmjs.com/package/use-webmcp-tool |
| Angular | @angular/core、@angular/forms/signals |
provideExperimentalWebMcpTools([...]) (應用程式或路徑供應商)、declareExperimentalWebMcpTool(...) (內部服務)、provideExperimentalWebMcpForms() + 信號表單上的 experimentalWebMcpTool 選項 |
angular.dev/ai/webmcp |
| Vue | @vueuse/core |
useWebMCP(options) 可組合函式 |
vueuse.org/core/usewebmcp |
使用這些整合功能時,仍須遵守本指南中的所有規則:
- 工具定義形狀應保持不變:
name、description、inputSchema、execute(input, { signal })和annotations。 - 遵守說明和註解的規則。
- 驗證輸入內容,並在
execute中套用預設值。 - 將
signal傳遞至長時間執行的工作。 - 如要只在部分時間提供工具,請傳遞
enabled(React、Vue)。請勿在條件中包裝掛鉤或可組合函式呼叫。 - 整合項目可能會包裝結果和錯誤 (例如
{ content, isError })。請先查看整合項目的說明文件,再依據錯誤傳送至代理的方式採取行動。
API 附註
- annotations:(選用) 工具中繼資料的字典。
- readOnlyHint:(選用) 如果工具不會修改任何狀態,且只會讀取資料,請設為
true。這有助於代理判斷何時可以安全呼叫工具。 - consequentialHint:(選用,Chrome 154 以上版本) 針對高風險、不可逆或實際操作,設為
true,讓代理先要求使用者確認。 - untrustedContentHint:(選用) 如果工具傳回的內容不受網站控制 (例如使用者生成的文字或第三方 API 回應),請設為
true。 - debugging:(選用,Chrome 156 以上版本) 如果工具用於偵錯和開發人員工具,而非使用者工作,請設為
true。預設值為false。
- readOnlyHint:(選用) 如果工具不會修改任何狀態,且只會讀取資料,請設為
- 報名選項:
- signal:(選用)
AbortSignal,用於在不再需要工具時取消註冊。 - exposedTo:(選用) 安全來源字串陣列,用於控制文件樹狀結構中的哪些文件,可跨頁框邊界探索及執行工具。
- signal:(選用)
- 工具探索選項 (
ModelContextGetToolOptions):- fromOrigins:(選用) 安全來源字串陣列,用於在可存取的頁框中查詢。呼叫
getTools()時,如果不使用fromOrigins,則只會查詢同源文件。
- fromOrigins:(選用) 安全來源字串陣列,用於在可存取的頁框中查詢。呼叫
- 傳回格式:
execute函式可傳回任何可 JSON 序列化的值 (物件、陣列、字串、數字、布林值)。從execute擲回的例外狀況訊息不會轉送給代理,因此請透過工具的傳回值,以結構化錯誤酬載 (例如{ error: "..." }) 的形式傳回可採取行動的錯誤,讓代理讀取並採取行動。 - 安全環境:WebMCP 必須使用 HTTPS。
exposedTo和fromOrigins中的所有來源也必須是可能值得信任的來源。 - 已淘汰/移除:不再支援
navigator.modelContext(在 Chromium 150 中淘汰)、unregisterTool()、provideContext()和clearContext()。
備用策略
使用 WebMCP Imperative API 時,應搭配功能偵測,確保與尚未支援 WebMCP 的瀏覽器相容。
if ('modelContext' in document) {
// Register tools
}