명령형 API는 document.modelContext.registerTool()를 사용하여 JavaScript 도구를 프로그래매틱 방식으로 정의합니다. 이는 현재 경로 또는 사용자 상태에 따라 도구를 추가하거나 삭제해야 하는 단일 페이지 애플리케이션 (SPA)에 적합합니다.
WebMCP는 프로듀서/소비자 아키텍처를 기반으로 작동합니다.
- 웹페이지는 도구 생성자입니다.
document.modelContext.registerTool()를 사용하여 도구를 정의하고 등록합니다. 웹페이지에서 구현해야 하는 것은 이것이 전부입니다. 상담사에게 도구를 노출하기 위해 페이지에서getTools()또는executeTool()를 호출할 필요가 없습니다. - 브라우저 및 확장 프로그램 에이전트가 기본 소비자임: 브라우저의 기본 제공 에이전트, 브라우저 확장 프로그램, DevTools, 시스템 수준 어시스턴트 등 브라우저에서 제공하거나 브라우저를 통해 제공되는 에이전트는 내부 플랫폼 메커니즘을 통해 대역 외에서 등록된 도구를 검색하고 호출합니다.
- 인페이지 에이전트 및 테스트 하네스:
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는 도구의 기능과 사용 시기만 알려주면 됩니다. DO NOT repeat parameter names, types, enum values, defaults, or limits in it; declare them in the schema (enum, default, minimum/maximum) instead. 매개변수 설명은 동일한 규칙을 따릅니다. 유형이나 허용되는 값이 아닌 값의 의미를 설명합니다.
브라우저는 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 사용 시점
도구에 프로미스를 반환하거나 완료하는 데 시간이 걸리는 작업이 포함된 경우 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에서 예외가 발생하거나 거부된 프로미스가 반환되면 에이전트는 일반적인 실행 실패만 수신합니다. 오류 메시지는 상담사에게 전달되지 않습니다. 도구에서 조치 가능하거나 복구 가능한 오류 (예: 잘못된 입력, 누락된 항목, 실패한 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를 준수해야 합니다(MUST). 그렇지 않으면 취소된 실행이 계속 리소스를 사용하고 에이전트가 이동한 후 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)
웹페이지는 AI 에이전트에 도구를 노출하기 위해 getTools() 또는 executeTool()가 필요하지 않습니다. 브라우저 에이전트, 브라우저 확장 프로그램, DevTools, 시스템 수준 어시스턴트가 대역 외에서 도구를 검색하고 실행하려면 document.modelContext.registerTool()를 호출하기만 하면 됩니다.
getTools() 및 executeTool() 메서드는 JavaScript 환경 내에서 실행되는 소비자를 위해 특별히 존재합니다.
- 페이지 내 JavaScript 에이전트: 페이지에 직접 삽입되거나
<iframe>내에서 실행되는 AI 어시스턴트, 부조종사 위젯 또는 채팅 인터페이스로, 주변 페이지 또는 문서 트리의 액세스 가능한 프레임에서 제공하는 도구를 검색하고 사용자를 대신하여 호출해야 합니다. - 자동 테스트 및 디버깅: 등록된 도구 메타데이터를 검사하고 실시간 외부 LLM 하네스 없이 에이전트 도구 호출을 시뮬레이션하는 테스트 모음 (예: Playwright, Vitest, 브라우저 내 단위 테스트)입니다.
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)를 호출하여 등록된 도구를 실행합니다. 도구는 등록된 문서에서 실행되며, 프로미스는 도구의 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);
}
다음과 같은 경우 반환된 프로미스가 거부됩니다.
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): 도구당 호출 1회 |
npmjs.com/package/use-webmcp-tool |
| Angular | @angular/core, @angular/forms/signals |
provideExperimentalWebMcpTools([...]) (앱 또는 경로 제공업체), declareExperimentalWebMcpTool(...) (내부 서비스), provideExperimentalWebMcpForms() + Signal 양식의 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: (선택사항) 액세스 가능한 프레임에서 쿼리할 보안 출처 문자열의 배열입니다.
fromOrigins없이getTools()를 호출하면 동일 출처 문서만 쿼리합니다.
- 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
}