A API Imperative usa document.modelContext.registerTool() para definir ferramentas JavaScript de forma programática. Isso é ideal para aplicativos de página única (SPAs, na sigla em inglês) em que as ferramentas precisam ser adicionadas ou removidas com base na rota atual ou no estado do usuário.
O WebMCP opera em uma arquitetura de produtor/consumidor:
- As páginas da Web são produtoras de ferramentas: você define e registra ferramentas usando
document.modelContext.registerTool(). Isso é tudo o que uma página da Web precisa implementar. Não é necessário chamargetTools()ouexecuteTool()na página para expor ferramentas aos agentes. - Os agentes de navegador e extensão são os principais consumidores: os agentes fornecidos pelo navegador ou por ele, incluindo o agente integrado do navegador, as extensões do navegador, o DevTools e os assistentes no nível do sistema, descobrem e invocam ferramentas registradas fora de banda usando mecanismos de plataforma interna.
- Agentes na página e arcabouços de testes:
getTools()eexecuteTool()existem especificamente para assistentes JavaScript na página (por exemplo, um widget de chat incorporado ou<iframe>) e pacotes de testes automatizados que precisam descobrir e invocar ferramentas diretamente no contexto da página.
Registro e ciclo de vida
As ferramentas são registradas transmitindo um objeto de definição de ferramenta e um objeto de opções opcional (ModelContextRegisterToolOptions), que aceita dois membros opcionais:
signal: umAbortSignalusado para cancelar o registro da ferramenta quando ela não for mais necessária.exposedTo: uma matriz de strings de origem que controla quais documentos na árvore de documentos podem descobrir e executar a ferramenta em limites de frames.
Processamento do ciclo de vida com AbortController
O WebMCP não fornece um método unregisterTool(). Para cancelar o registro de uma ferramenta, transmita um AbortSignal durante o registro e cancele esse sinal quando a ferramenta não for mais necessária.
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();
O cancelamento do sinal de registro apenas cancela o registro da ferramenta, não as execuções que já estão em andamento. As ferramentas que iniciam um trabalho de longa duração também precisam respeitar o sinal por execução descrito abaixo. Caso contrário, o cancelamento da inscrição vai deixar solicitações órfãs e atualizações de interface desatualizadas.
Como controlar a exposição de ferramentas entre origens com exposedTo
Por padrão, uma ferramenta registrada é particular para o documento de registro e os frames de mesma origem. Quando o aplicativo estiver incorporado em um <iframe> (ou incorporar widgets de terceiros) e você precisar de frames ancestrais ou irmãos para descobrir e invocar a ferramenta, especifique as origens permitidas usando 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"],
});
- Handshake de ativação mútua: a descoberta de ferramentas entre origens é uma ativação bidirecional. O frame do provedor de ferramentas autoriza a origem do consumidor usando
exposedTo, e o frame do consumidor precisa solicitar explicitamente essa origem do provedor usandogetTools({ fromOrigins: [...] }). SemfromOrigins,getTools()consulta apenas documentos de mesma origem. - Correspondência de origem: a origem do documento de registro sempre tem acesso. As origens especificadas em
exposedTorecebem permissão para descobrir a ferramenta usandodocument.modelContext.getTools({ fromOrigins: [...] })e executá-la usandodocument.modelContext.executeTool(). - Requisito de segurança: todas as origens em
exposedToPRECISAM ser potencialmente confiáveis (HTTPS ou localhost). Transmitir uma origem inválida ou não segura faz com queregisterTool()rejeite com umSecurityError. - Escopo:
exposedTocontrola a descoberta entre documentos na página em frames na árvore de documentos.
Definição de parâmetros
Os parâmetros são definidos usando a propriedade inputSchema. Precisa ser um objeto de esquema JSON que descreve os dados estruturados que a ferramenta espera.
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 }
});
Como escrever descrições de ferramentas e parâmetros
O agente já recebe o inputSchema completo, então o description só precisa dizer o que a ferramenta faz e quando usá-la. NÃO repita nomes de parâmetros, tipos, valores enum, padrões ou limites. Em vez disso, declare-os no esquema (enum, default, minimum/maximum). As descrições de parâmetros seguem a mesma regra: diga o que o valor significa, não o tipo ou os valores permitidos.
O navegador não valida argumentos em relação a inputSchema nem aplica valores de default. Portanto, faça as duas coisas em 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 },
});
Padrões de execução
O callback execute sempre recebe dois argumentos: o objeto de entrada e um objeto de opções que contém um AbortSignal — execute(inputObject, { signal }).
Quando usar async execute
Use async quando a ferramenta envolver operações que retornam uma Promise ou levam tempo para serem concluídas:
- Chamadas de rede: busca de dados de uma API.
- Armazenamento assíncrono: acesso ao IndexedDB.
- Eventos externos: aguardar a conclusão de uma mudança de estado ou animação específica.
async execute(input, { signal }) {
const response = await fetch(`/api/data/${input.id}`, { signal });
return await response.json();
}
Quando usar execute (síncrono)
Use uma função síncrona padrão para operações imediatas. Uma ferramenta síncrona é executada até o fim antes que o cancelamento seja observado, então ela pode ignorar o sinal:
- Lógica pura: matemática, filtragem ou classificação de dados que já estão na memória.
- Estado síncrono: leitura de
localStorageou de um gerenciador de estado síncrono.
execute(input) {
return input.items.filter(item => item.active);
}
Como processar erros em execute
Quando execute gera uma exceção (ou retorna uma promessa rejeitada), o agente recebe apenas uma falha de execução genérica. A mensagem de erro não é encaminhada ao agente. Se a ferramenta encontrar um erro que pode ser corrigido ou recuperado (como entrada inválida, itens ausentes ou um status HTTP com falha), NÃO throw new Error(...). Em vez disso, retorne um payload de erro estruturado pelo valor de retorno normal da ferramenta para que o agente de IA receba os detalhes do diagnóstico e possa se autocorrigir ou tentar novamente:
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;
}
Só lance novamente quando a execução for cancelada (signal.aborted é true).
Cancelando execuções em andamento
O cancelamento é iniciado pelo usuário ou pelo agente. OBRIGATÓRIO: qualquer ferramenta que faça solicitações de rede ou outro trabalho assíncrono de longa duração PRECISA respeitar signal. Caso contrário, as execuções canceladas vão continuar consumindo recursos e poderão gravar dados desatualizados na interface depois que o agente tiver avançado.
- APIs que aceitam um indicador (leitores
fetch(),addEventListener(),ReadableStream): transmitasignaldiretamente. - APIs que não (
setTimeout,setInterval, loops de sondagem, pipelines com uso intenso deawait): chamesignal.throwIfAborted()entre as etapas e/ou rejeite emsignal.addEventListener('abort', ...). Os timers não aceitam sinais, então você precisa limpá-los no listener de cancelamento.
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 }
});
Sinalização de ferramentas de impacto
Desde o Chrome 154, o annotations oferece suporte a consequentialHint. Defina como true para ferramentas que realizam ações de alto risco, irreversíveis ou no mundo real, como reservar viagens, transferir dinheiro, enviar mensagens ou excluir dados. Ele sinaliza aos agentes que solicitem confirmação explícita do usuário antes da execução.
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.
}
});
OBRIGATÓRIO: trate consequentialHint como uma dica, não como um mecanismo de aplicação. Ela não bloqueia a execução por conta própria, e os navegadores anteriores à dica não vão mostrar nenhuma solicitação. Mantenha as confirmações no app que a ação já exige.
Ferramentas de depuração de sinalização
Desde o Chrome 156, o annotations é compatível com o debugging. Defina annotations.debugging como true para ferramentas criadas para depuração e ferramentas para desenvolvedores, em vez de tarefas do usuário final. Exemplos: despejar o estado interno do app, alternar flags de recursos ou propagar dados de teste. Isso permite que os agentes diferenciem as ferramentas exclusivas para desenvolvedores daquelas destinadas a agir em nome do usuário. O padrão é false. Os agentes in-page e os arcabouços de testes podem ler o valor de RegisteredTool.annotations.debugging usando getTools().
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.
}
});
Agentes in-page e testes (getTools e executeTool)
As páginas da Web não precisam de getTools() ou executeTool() para expor ferramentas aos agentes de IA. Basta chamar document.modelContext.registerTool() para que agentes de navegador, extensões de navegador, DevTools e assistentes no nível do sistema descubram e executem suas ferramentas fora de banda.
Os métodos getTools() e executeTool() existem especificamente para consumidores em execução no ambiente JavaScript:
- Agentes JavaScript na página: um assistente de IA, um widget de copiloto ou uma interface de chat incorporada diretamente na página (ou em execução em um
<iframe>) que precisa descobrir quais ferramentas a página ao redor ou os frames acessíveis na árvore de documentos oferecem e invocá-los em nome do usuário. - Testes e depuração automatizados: pacotes de testes (como Playwright, Vitest ou testes de unidade no navegador) que inspecionam metadados de ferramentas registradas e simulam invocações de ferramentas de agentes sem exigir um harness de LLM externo ativo.
Como descobrir as ferramentas disponíveis com getTools()
Os agentes na página chamam document.modelContext.getTools() para recuperar uma matriz de objetos RegisteredTool do documento atual e de todos os frames acessíveis na árvore de documentos.
Desde o Chrome 155, RegisteredTool.inputSchema é retornado diretamente como um objeto JavaScript (uma cópia profunda do esquema fornecido no registro). A modificação desse objeto não altera o esquema real da ferramenta registrada.
// 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(", ")}`);
});
Para descobrir ferramentas registradas em iframes descendentes de origem cruzada (que precisam conceder acesso explicitamente via exposedTo durante o registro), transmita fromOrigins no dicionário de opções:
// 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"],
});
Como invocar ferramentas com executeTool()
Os agentes in-page e os scripts de teste chamam document.modelContext.executeTool(tool, inputObject, options) para executar uma ferramenta registrada. A ferramenta é executada no documento em que foi registrada, e a promessa é resolvida para o resultado em string retornado pelo callback execute da ferramenta.
Desde o Chrome 155, executeTool() aceita um objeto JavaScript simples diretamente para inputObject, correspondendo ao formato de entrada que o callback execute da sua ferramenta recebe.
// 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);
}
A promessa retornada será rejeitada se:
inputObjectnão é um objeto ou não pode ser serializado em uma string JSON (por exemplo, referências circulares ou valoresBigInt). Os valores omitidos pela serialização JSON (como funções eundefined) serão omitidos do objeto recebido pela ferramenta, em vez de causar uma rejeição.- A execução é cancelada por
options.signal. - A ferramenta não foi registrada antes da execução ou encontrou um erro de tempo de execução não processado.
Padrão de fábrica de ferramentas
Para transmitir contexto (como lojas ou instâncias de aplicativos) para suas ferramentas, use funções de fábrica.
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 }
};
}
Tipos do TypeScript
Em projetos TypeScript, NÃO escreva manualmente declarações para document.modelContext. Instale o pacote oficial do webmcp-types (npm install --save-dev webmcp-types) e adicione-o a tsconfig.json, já que ele não é selecionado automaticamente:
{
"compilerOptions": {
"types": ["webmcp-types"]
}
}
document.modelContexté digitado como opcional, então proteja-o comif (document.modelContext)antes de usar.- Com um
inputSchemainline, o tipo de entradaexecuteé inferido do esquema. - Os tipos nomeados, como
WebMCP.ModelContextTool, são globais (não é necessário importar) e úteis para digitar fábricas de ferramentas.
Suporte a frameworks
OBRIGATÓRIO: se o app usa React, Angular ou Vue, registre as ferramentas pela integração oficial do WebMCP desse framework, listada abaixo. NÃO chame document.modelContext.registerTool() de componentes nem escreva seu próprio wrapper de ciclo de vida. Cada integração já lida com a detecção de recursos, a limpeza de AbortSignal e as ferramentas de escopo para o ciclo de vida do componente ou do injetor. Essas APIs são experimentais. Por isso, antes de escrever o código, procure a assinatura atual nos tipos ou na documentação do pacote.
| Framework | Pacote | API | Documentos |
|---|---|---|---|
| React 18+ | use-webmcp-tool |
useWebMCP(options): uma chamada por ferramenta |
npmjs.com/package/use-webmcp-tool |
| Angular | @angular/core, @angular/forms/signals |
provideExperimentalWebMcpTools([...]) (provedores de apps ou rotas), declareExperimentalWebMcpTool(...) (serviços internos), provideExperimentalWebMcpForms() + a opção experimentalWebMcpTool em Signal Forms |
angular.dev/ai/webmcp |
| Vue | @vueuse/core |
Elemento combinável useWebMCP(options) |
vueuse.org/core/usewebmcp |
Todas as regras deste guia ainda se aplicam quando você usa estas integrações:
- Mantenha o mesmo formato de definição da ferramenta:
name,description,inputSchema,execute(input, { signal })eannotations. - Siga as regras para descrições e anotações.
- Valide a entrada e aplique os padrões em
execute. - Transmita
signalpara o trabalho de longa duração. - Para oferecer uma ferramenta apenas em alguns momentos, transmita
enabled(React, Vue). NÃO envolva o hook ou a chamada combinável em uma condição. - As integrações podem encapsular resultados e erros (por exemplo, como
{ content, isError }). Verifique a documentação da integração antes de confiar em como os erros chegam ao agente.
Observações sobre a API
- annotations: (opcional) um dicionário para metadados de ferramentas.
- readOnlyHint: (opcional) defina como
truese a ferramenta não modificar nenhum estado e apenas ler dados. Isso ajuda os agentes a decidir quando é seguro chamar a ferramenta. - consequentialHint: (opcional, Chrome 154+) defina como
truepara ações de alto risco, irreversíveis ou do mundo real, para que os agentes solicitem a confirmação do usuário primeiro. - untrustedContentHint: (opcional) defina como
truequando a ferramenta retornar conteúdo que seu site não controla, como texto gerado pelo usuário ou respostas da API de terceiros. - debugging: (opcional, Chrome 156+) defina como
truequando a ferramenta for destinada à depuração e a ferramentas para desenvolvedores, não a tarefas do usuário final. O valor padrão éfalse.
- readOnlyHint: (opcional) defina como
- Opções de inscrição:
- signal: (opcional) um
AbortSignalusado para cancelar o registro da ferramenta quando ela não for mais necessária. - exposedTo: (opcional) uma matriz de strings de origem segura que controla quais documentos na árvore de documentos podem descobrir e executar a ferramenta além dos limites do frame.
- signal: (opcional) um
- Opções de descoberta de ferramentas (
ModelContextGetToolOptions):- fromOrigins: (opcional) uma matriz de strings de origem segura para consultar em frames acessíveis. Chamar
getTools()semfromOriginsconsulta apenas documentos de mesma origem.
- fromOrigins: (opcional) uma matriz de strings de origem segura para consultar em frames acessíveis. Chamar
- Formato de retorno: a função
executepode retornar qualquer valor serializável em JSON (objeto, matriz, string, número, booleano). A mensagem de uma exceção gerada porexecutenão é encaminhada ao agente. Portanto, retorne erros acionáveis como payloads de erro estruturados (por exemplo,{ error: "..." }) usando o valor de retorno da ferramenta para que o agente possa ler e agir de acordo com eles. - Contexto seguro: o WebMCP exige HTTPS. Todas as origens em
exposedToefromOriginstambém precisam ser potencialmente confiáveis. - Descontinuado/removido:
navigator.modelContext(descontinuado no Chromium 150),unregisterTool(),provideContext()eclearContext()não são mais compatíveis.
Estratégias de fallback
A API WebMCP Imperative precisa ser usada com a detecção de recursos para garantir a compatibilidade com navegadores que ainda não são compatíveis com o WebMCP.
if ('modelContext' in document) {
// Register tools
}