API Declarativa do WebMCP: Formulários HTML como Tools para IA
O que é a API Declarativa
A API Declarativa transforma formulários HTML padrão em tools acessíveis por agentes de IA — sem escrever uma linha de JavaScript. Adicione atributos especiais ao <form> e aos campos, e o browser gera o JSON Schema sozinho.
Pra quem isso resolve problema:
- Formulários de contato, busca e cadastro que já existem no site
- Sites que dependem de
<form>para interações (a maioria, convenhamos) - Progressive enhancement — funciona pra humanos mesmo sem WebMCP
- Times que não querem (ou não precisam) JavaScript complexo no frontend
Nota: A API Declarativa e a API Imperativa coexistem sem atrito. Este site (prompt.api.br) usa a API Imperativa via astro-webmcp para busca e navegação, enquanto a API Declarativa pode ser adicionada a qualquer formulário individual.
Atributos do formulário
toolname (obrigatório)
Define o nome da tool. O agente usa esse nome para identificar e chamar a ferramenta.
<form toolname="subscribe_newsletter">
Regras:
- Deve ser único na página
- Use snake_case ou camelCase
- Máximo recomendado: 30 caracteres
- Verbos que descrevem a ação:
submit_feedback,search_products,book_appointment
tooldescription (obrigatório)
Descreve o que a tool faz. O agente usa essa descrição pra decidir quando chamar.
<form toolname="subscribe_newsletter"
tooldescription="Subscribe to the weekly newsletter with email address.">
Regras:
- Máximo recomendado: 500 caracteres
- Descreva o resultado da ação, não o formulário em si
- Linguagem positiva (o que faz, não o que não faz)
toolautosubmit (opcional)
Quando presente, o formulário é submetido automaticamente ao ser invocado pelo agente. Sem esse atributo, o agente preenche os campos mas o usuário precisa clicar Submit manualmente.
<!-- Submissão automática pelo agente -->
<form toolname="quick_search"
tooldescription="Search products by keyword"
toolautosubmit>
Dica: Use
toolautosubmitpara ações de baixo risco (busca, filtros). Pra ações irreversíveis (compras, exclusões), omita — força confirmação humana. Parece paranoia, mas é o tipo de detalhe que evita dor de cabeça com usuário real.
Atributos dos campos
toolparamdescription
Dá contexto semântico sobre o que cada campo espera. Sem ele, o browser usa o conteúdo do <label> associado ou aria-description.
<select name="department" required
toolparamdescription="Determines which team handles this request">
<option value="support">Technical Support</option>
<option value="billing">Billing Questions</option>
<option value="sales">Sales Inquiry</option>
</select>
Hierarquia de fallback para descrição do parâmetro:
toolparamdescription(prioridade máxima)<label>associado ao campoaria-description- Sem descrição (campo incluído no schema sem description)
Exemplo completo — Formulário de suporte
<form toolname="support_request"
tooldescription="Submit a customer support request. Routes to appropriate team based on category."
action="/api/support"
method="POST">
<label for="name">Full Name</label>
<input type="text" id="name" name="name" required
toolparamdescription="Customer's full name for identification">
<label for="email">Email</label>
<input type="email" id="email" name="email" required
toolparamdescription="Email address for response delivery">
<label for="category">Category</label>
<select id="category" name="category" required
toolparamdescription="Determines which team handles this request">
<option value="returns">Return my purchase</option>
<option value="shipping">Check package status</option>
<option value="technical">Website help</option>
</select>
<label for="message">Message</label>
<textarea id="message" name="message" required
toolparamdescription="Detailed description of the issue (max 1000 chars)"></textarea>
<label>
<input type="checkbox" name="urgent">
Mark as urgent
</label>
<button type="submit">Submit Request</button>
</form>
JSON Schema gerado pelo browser
Para o formulário acima, o browser gera automaticamente este schema e expõe ao agente:
[{
"name": "support_request",
"description": "Submit a customer support request. Routes to appropriate team based on category.",
"inputSchema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Customer's full name for identification"
},
"email": {
"type": "string",
"description": "Email address for response delivery"
},
"category": {
"type": "string",
"anyOf": [
{ "type": "string", "const": "returns", "title": "Return my purchase" },
{ "type": "string", "const": "shipping", "title": "Check package status" },
{ "type": "string", "const": "technical", "title": "Website help" }
],
"enum": ["returns", "shipping", "technical"],
"description": "Determines which team handles this request"
},
"message": {
"type": "string",
"description": "Detailed description of the issue (max 1000 chars)"
},
"urgent": {
"type": "boolean"
}
},
"required": ["name", "email", "category", "message"]
}
}]
Repare:
- Campos com
requiredviramrequiredno schema <select>geraenum+anyOfcom os values e textos dos<option><input type="checkbox">viraboolean<input type="email">virastring(o browser não infere formatos)toolparamdescriptionviradescriptionna property
SubmitEvent ampliado
O WebMCP estende o SubmitEvent com duas propriedades novas:
agentInvoked (boolean)
Indica se o submit veio de um agente de IA (true) ou de ação humana (false).
respondWith(promise)
Permite retornar um resultado estruturado ao agente. Exige preventDefault() antes.
Exemplo — Validação e resposta ao agente
const form = document.querySelector('form[toolname="support_request"]');
form.addEventListener('submit', (event) => {
event.preventDefault();
// Validação customizada
const formData = new FormData(form);
const message = formData.get('message');
if (message.length < 10) {
if (event.agentInvoked) {
// Retorna erro estruturado ao agente
event.respondWith(Promise.resolve(JSON.stringify({
error: 'Message too short. Minimum 10 characters required.',
currentLength: message.length
})));
} else {
// Feedback visual para humano
showError('Mensagem muito curta. Mínimo 10 caracteres.');
}
return;
}
// Processamento
const resultPromise = submitSupportRequest(formData)
.then(result => JSON.stringify({
success: true,
ticketId: result.id,
message: `Ticket #${result.id} created. Response within 24h.`
}))
.catch(err => JSON.stringify({
error: 'Submission failed. Please try again.',
details: err.message
}));
if (event.agentInvoked) {
event.respondWith(resultPromise);
} else {
resultPromise.then(() => showSuccess('Ticket criado!'));
}
});
Atenção: Se você não chamar
respondWith()quandoagentInvokedétrue, o agente fica sem feedback. Sempre forneça um resultado — nem que seja só um “ok, deu certo”.
Eventos de ativação
toolactivated
Dispara quando o agente começa a preencher campos do formulário. Non-cancelable — você não pode impedir o preenchimento, mas pode reagir a ele.
window.addEventListener('toolactivated', (event) => {
const { toolName } = event;
console.log(`Agente está preenchendo: ${toolName}`);
// Possíveis reações:
// - Mostrar indicador visual
// - Desabilitar edição humana temporariamente
// - Carregar dados necessários
highlightForm(toolName);
});
toolcancel
Dispara quando a operação é cancelada pelo agente ou quando form.reset() é invocado.
window.addEventListener('toolcancel', (event) => {
const { toolName } = event;
console.log(`Operação cancelada: ${toolName}`);
// Limpar indicadores visuais
removeHighlight(toolName);
});
CSS Pseudo-classes
O WebMCP adiciona pseudo-classes CSS para sinalizar visualmente quando um agente está interagindo com um formulário:
:tool-form-active
Aplicada ao <form> enquanto o agente está interagindo (preenchendo campos).
:tool-submit-active
Aplicada ao botão de submit enquanto o agente está processando a submissão.
Exemplo de estilos
/* Indicador visual: agente está preenchendo o form */
form:tool-form-active {
outline: 2px dashed light-dark(#2563eb, #60a5fa);
outline-offset: 4px;
position: relative;
}
form:tool-form-active::before {
content: '🤖 AI Agent is filling this form';
position: absolute;
top: -2rem;
left: 0;
font-size: 0.75rem;
color: light-dark(#2563eb, #60a5fa);
background: light-dark(#eff6ff, #1e3a5f);
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
/* Indicador no botão de submit */
button:tool-submit-active,
input[type="submit"]:tool-submit-active {
outline: 2px dashed light-dark(#dc2626, #f87171);
outline-offset: 2px;
opacity: 0.7;
pointer-events: none;
}
Quando desativam
As pseudo-classes são removidas quando:
- O formulário é submetido com sucesso
- O agente cancela a operação
- O usuário chama
form.reset()
Dica: Use essas pseudo-classes pra dar feedback visual claro ao usuário. Tem um agente operando ali — transparência gera confiança.
Registro e desregistro dinâmico
A API Declarativa suporta registro dinâmico com base na presença dos atributos:
Registrar: Adicionar atributos
// Transformar form existente em tool
const form = document.getElementById('search-form');
form.setAttribute('toolname', 'search_products');
form.setAttribute('tooldescription', 'Search products by keyword and category');
// Tool registrada na hora!
Desregistrar: Remover atributos
// Remover tool
form.removeAttribute('toolname');
// Ou remover description (ambos são required)
form.removeAttribute('tooldescription');
// Tool desregistrada!
Isso abre patterns como:
- Registrar tools depois que o usuário faz login
- Remover tools enquanto um processo está em andamento
- Condicionar tools ao estado da aplicação
Combinando Declarativa + Imperativa
As duas APIs convivem na mesma página sem conflito. O padrão natural:
- Imperativa para lógica (busca, navegação, cálculos)
- Declarativa para formulários (contato, checkout, reservas)
<!-- Tool declarativa: formulário de contato -->
<form toolname="send_message"
tooldescription="Send a contact message to our team"
toolautosubmit
action="/api/contact">
<label for="email">Email</label>
<input type="email" name="email" required>
<label for="msg">Message</label>
<textarea name="msg" required></textarea>
<button type="submit">Send</button>
</form>
<script>
// Tool imperativa: busca de FAQ
const mc = document.modelContext ?? navigator.modelContext;
if (mc?.registerTool) {
mc.registerTool({
name: 'search_faq',
description: 'Search frequently asked questions',
inputSchema: {
type: 'object',
properties: { query: { type: 'string' } },
required: ['query']
},
execute: async ({ query }) => {
const results = await searchFAQ(query);
return JSON.stringify(results);
},
annotations: { readOnlyHint: true }
});
}
</script>
O agente enxerga as duas — send_message (declarativa) e search_faq (imperativa) — e escolhe qual usar com base no que o usuário pediu.
Mapeamento HTML → JSON Schema
| Elemento HTML | Tipo no Schema | Notas |
|---|---|---|
<input type="text"> | string | — |
<input type="email"> | string | Sem format validation no schema |
<input type="number"> | number | — |
<input type="checkbox"> | boolean | — |
<input type="hidden"> | string | Incluído no schema |
<textarea> | string | — |
<select> | string + enum | Options viram enum values |
<select multiple> | array de string | — |
Campo com required | Adicionado ao required[] | — |
Boas práticas para formulários declarativos
Nomes de tools
| ❌ Ruim | ✅ Bom | Por quê |
|---|---|---|
form1 | book_appointment | Descritivo, indica ação |
myForm | filter_products | Verbo + substantivo |
submit | submit_review | Específico sobre o que submete |
Descrições
| ❌ Ruim | ✅ Bom |
|---|---|
| ”A form" | "Submit a product review with rating (1-5) and text feedback" |
| "Use this to do stuff" | "Book a restaurant table for a specific date, time and party size" |
| "Don’t use for orders" | "Search our FAQ by keyword. Returns top 5 matching questions.” |
Quando usar toolautosubmit
| Cenário | toolautosubmit? | Justificativa |
|---|---|---|
| Busca/filtro | ✅ | Baixo risco, reversível |
| Newsletter signup | ✅ | Ação simples, não destrutiva |
| Compra/pagamento | ❌ | Alto risco, irreversível |
| Exclusão de conta | ❌ | Destrutiva, requer confirmação |
| Envio de mensagem | Depende | Se for suporte sim, se for público não |
Acessibilidade e progressive enhancement
Para visitantes humanos
Formulários com atributos WebMCP continuam funcionando normalmente:
- Os atributos
toolname,tooldescription,toolparamdescriptionsão ignorados por browsers sem suporte - O formulário submete normalmente via
actionemethod - Estilos CSS padrão se aplicam (pseudo-classes WebMCP são no-op em browsers sem suporte)
Para agentes
- O browser gera JSON Schema automaticamente dos campos
- O agente recebe a mesma interface estruturada da API Imperativa
toolautosubmitdefine se o agente pode completar a ação sozinho
Esse é o ponto forte: um formulário, dois públicos, zero código duplicado. Já pensou no ganho de manutenção?
Formulários multi-step
Formulários com múltiplas etapas? Registre cada etapa como tool separada:
<!-- Step 1 -->
<form toolname="booking_select_date"
tooldescription="Select date and party size for restaurant booking"
toolautosubmit>
<input type="date" name="date" required>
<input type="number" name="guests" min="1" max="12" required
toolparamdescription="Number of guests (1-12)">
<button type="submit">Next</button>
</form>
<!-- Step 2 (aparece depois do submit do step 1) -->
<form toolname="booking_select_time"
tooldescription="Select preferred time slot for the booking"
toolautosubmit>
<select name="time" required
toolparamdescription="Available time slot">
<option value="19:00">7:00 PM</option>
<option value="20:00">8:00 PM</option>
<option value="21:00">9:00 PM</option>
</select>
<button type="submit">Confirm</button>
</form>
O agente vê apenas a tool da etapa atual — conforme a UI muda, as tools declarativas se registram/desregistram automaticamente.
Troubleshooting
| Problema | Causa | Solução |
|---|---|---|
| Tool não aparece | Falta toolname ou tooldescription | Ambos são obrigatórios |
| Campos não no schema | Form fora do DOM | Garantir que form está renderizado |
agentInvoked sempre false | Testando manualmente | Use a extensão Model Context Tool Inspector |
| Pseudo-classes não aplicam | Browser sem suporte | Verificar Chrome 149+ com flag |
| respondWith não funciona | Faltou preventDefault | Chamar e.preventDefault() antes |
Próximos passos
- Segurança — Proteja formulários contra manipulação
- API Imperativa — Para lógica programática
- Best Practices — Estratégias de nomeação e design