First tool: registerTool with inputSchema and execute
A WebMCP tool is an object with a name, a description, an input schema, and an execute function. The page registers it; the agent lists it with getTools and calls it with executeTool. In this lesson you read the kit's search tool, call it the way an agent would, and register your first one.
1The fields of a tool and the rules for name and description
2inputSchema in JSON Schema, closed and described
3executeTool: an object goes in, JSON text comes back
4Your first tool in extras.mjs
Goal: register a tool with a name, description, inputSchema, and execute, and call it the way an agent would, seeing both the result and the screen.
01 · From definition to result
Click each step.
Click each block in the diagram to see what it does.
Spec from 10/16/2026 and Chromium 156.0.8078.4. If it changes, lesson 1 will say so: check the date first.
02 · The kit's search tool
Excerpt from kit/pagina/ferramentas.mjs. The same agenda.buscar serves both the form and the tool.
Excerpt · kit/pagina/ferramentas.mjs
{
name: 'buscar_oficinas',
title: 'Buscar oficinas',
description: 'Lista as oficinas da agenda por tema. Só lê: não reserva nada. Use apenasComVaga=true para ver só as que ainda têm vaga.',
inputSchema: {
type: 'object',
properties: {
tema: { type: 'string', enum: TEMAS, description: 'Tema da oficina.' },
apenasComVaga: { type: 'boolean', description: 'true = só oficinas com vaga.' }
},
additionalProperties: false
},
annotations: { readOnlyHint: true },
execute: comErroUtil(async input => {
const lista = agenda.buscar(input);
ui.mostrarResultados(lista, input); // the person sees the same thing the agent received
return { total: lista.length, oficinas: lista };
})
}
Excerpt · registering with detection (ferramentas.mjs)
const mc = document.modelContext; // current name (10/16/2026)
if (!mc || typeof mc.registerTool !== 'function') {
aviso?.('Este navegador não expõe WebMCP. Tudo funciona pelo formulário.');
return { modo: 'sem', registradas: 0 };
}
const pagina = new AbortController();
addEventListener('pagehide', () => pagina.abort(), { once: true });
await registrar(mc, defs, pagina.signal);
03 · Calling it the way an agent would
listar.mjs runs getTools; executar.mjs runs executeTool with the object you pass and shows the screen afterward.
"resultado (string)": the browser returns JSON text, as the spec says. The list on the screen also ended up with 1 workshop.
== Chromium 156.0.8078.4 (Playwright) · modo: nativo
navigator.modelContext (nome antigo): undefined
entrada em texto JSON em vez de objeto: TypeError
execute que lança: UnknownError: Tool was executed but the invocation failed. For example, the script function threw an error
== Chromium 156.0.8078.4 (Playwright) · modo: polyfill
navigator.modelContext (nome antigo): undefined
entrada em texto JSON em vez de objeto: TypeError
execute que lança: UnknownError: Tool execution failed
Old examples passed JSON.stringify(input) to work around a September implementation: today it throws a TypeError in both. And the reason for a throw never reaches the agent (a generic UnknownError). That's why the kit returns { erro } as data (lesson 7).
04 · Your first tool
The block copies the page to a sandbox, writes an extras.mjs with contar_vagas, and checks it. To make it stick in the kit, paste the same list into kit/pagina/extras.mjs.
Terminal · sandbox with contar_vagas
cd ~/projetos/formacao-webmcp-site-v72/kit
E=${TMPDIR:-/tmp}/ensaio-webmcp-site/a3; rm -rf $E; mkdir -p $E; cp -r pagina/. $E/
cat > $E/extras.mjs <<'FIM'
export function extras(agenda) {
return [{
name: 'contar_vagas',
description: 'Soma as vagas livres das oficinas de um tema. Só lê: não reserva nada.',
inputSchema: {
type: 'object',
properties: { tema: { type: 'string', enum: ['html', 'javascript', 'webmcp', 'mcp'], description: 'Tema da oficina.' } },
required: ['tema'],
additionalProperties: false
},
annotations: { readOnlyHint: true },
async execute({ tema }) {
const lista = agenda.buscar({ tema });
return { tema, vagas: lista.reduce((soma, o) => soma + o.vagas, 0) };
}
}];
}
FIM
PAGINA=$E node scripts/listar.mjs nativo | tail -1
PAGINA=$E node scripts/executar.mjs nativo contar_vagas '{"tema":"html"}'; echo "saída: $?"
Write another read-only tool in extras.mjs, for example proxima_oficina (the one with the nearest date). Name with _, a description that says what it doesn't do, closed schema.
Done when:node scripts/executar.mjs nativo [name] '{}' → resultado (string): with the expected JSON and saída: 0.
06 · Lesson quiz
The explanation appears after your answer.
Question 1 of 4
What does executeTool return in the October 16, 2026 spec?
Correct
executeTool returns Promise: the browser serializes execute's return value to JSON. The caller does JSON.parse, like testar.mjs.
Not that one
executeTool returns Promise: the browser serializes execute's return value to JSON. The caller does JSON.parse, like testar.mjs.Review: The kit's search tool
Question 2 of 4
Which tool name does registerTool reject?
Correct
The name accepts 1 to 128 characters: ASCII letters and numbers, _ - and a period. A space isn't allowed, and registration rejects it with InvalidStateError.
Not that one
The name accepts 1 to 128 characters: ASCII letters and numbers, _ - and a period. A space isn't allowed, and registration rejects it with InvalidStateError.Review: From definition to result
Question 3 of 4
Why does the kit's search call ui.mostrarResultados inside execute?
Correct
The agent and the person work on the same page. If the agent searches and the screen doesn't change, the person loses the thread. The spec doesn't require it; it's a design decision of the kit.
Not that one
The agent and the person work on the same page. If the agent searches and the screen doesn't change, the person loses the thread. The spec doesn't require it; it's a design decision of the kit.Review: The kit's search tool
Question 4 of 4
A tutorial says to call executeTool(t, JSON.stringify(entrada)). What happens in Chromium 156?
Correct
The input is an object in the spec. The workaround with text came from a September implementation, and today it throws a TypeError in both the native API and the polyfill (measured on 10/19/2026).
Not that one
The input is an object in the spec. The workaround with text came from a September implementation, and today it throws a TypeError in both the native API and the polyfill (measured on 10/19/2026).Review: The kit's search tool
Quiz complete
You got {a} of {n} right. Get them all right and the lesson is marked as complete.
Pick an answer. The explanation shows up after you try.
07 · In three sentences
DefinitionA name with a rule, a clear description, a closed inputSchema, and an execute that returns an object.
CallgetTools lists; executeTool takes an object and returns JSON text.
On screenThe execute uses the same rule as the form and shows the person what the agent received.