Observability: logging calls, errors, and cancellations
In production, "the agent couldn't book" only gets solved with a log. Every tool call, through any door, leaves a line: surface, tool, result, and time. No personal data. In this lesson you trigger the possible outcomes and read what each one leaves in the log.
3Error, refusal, cancellation, and the MRTR request
4Why the log keeps the field name and not the value
Goal: log every call across the three surfaces with result and time, with no personal data, and read the possible outcomes in the log.
01 · From the request to the line in the log
The log sits at the boundary, not inside the rule. Click each step.
Click each block in the diagram to see what it does.
02 · The outcomes that matter
Each one calls for a different action from whoever runs the site.
1. erro
Input that the rule does not accept: invalid topic, workshop with no spots left.
Many errors on the same tool = a confusing description or schema for the agent.
Kitmotivo: tema inválido: cobol
2. recusado
The confirmation worked. It is not a site failure.
Many refusals = the agent is proposing the wrong thing; look at what it asked for.
3. cancelado
The agent gave up (or the person left) before the response: executeTool was canceled by the signal.
Measured in the kit: the cancellation reaches execute as AbortError and the booking does not happen.
4. pediu-pessoa
In MCP 2026-07-28, the first round of an action with confirmation returns input_required.
That is why a booking through MCP leaves two lines: the request and the outcome.
03 · The wrapper
The same file serves the page and the server. On the page, in production, the line would go to your backend (sendBeacon, for example); in the kit it stays in memory.
File · kit/pagina/registro.mjs
// registro.mjs — observabilidade nas fronteiras: uma linha por chamada de ferramenta, em qualquer superfície.
// Roda no navegador (página) e no Node (servidor MCP). Não guarda dado pessoal: só o NOME dos campos de entrada.
// Resultado: ok | erro (regra recusou) | recusado (a pessoa disse não) | cancelado (AbortSignal) | excecao
// | pediu-pessoa (MCP: a 1ª volta do MRTR devolve input_required e o cliente pergunta ao usuário).
const linhas = [];
let destino = null; // função que recebe cada linha (no servidor: grava JSONL; na página: sendBeacon em produção)
export function aoRegistrar(fn) { destino = fn; }
export function linhasRegistradas() { return linhas.map(l => ({ ...l })); }
export function classificar(r) {
if (r && typeof r === 'object') {
if (r.resultType === 'input_required') return 'pediu-pessoa';
if ('erro' in r) return 'erro';
if (r.reservado === false) return 'recusado';
}
return 'ok';
}
export function anotar(linha) {
const l = { t: new Date().toISOString(), ...linha };
linhas.push(l);
globalThis.__registro = linhas; // os testes leem daqui
destino?.(l);
return l;
}
// Embrulha o execute de uma ferramenta: mede o tempo e anota o resultado, sem mudar o que ela devolve.
export function observado(superficie, ferramenta, fn) {
return async (input = {}, opcoes) => {
const inicio = performance.now();
const base = { superficie, ferramenta, campos: Object.keys(input).sort() };
try {
const r = await fn(input, opcoes);
anotar({ ...base, resultado: classificar(r), ms: Math.round(performance.now() - inicio), ...(r?.erro ? { motivo: r.erro } : {}) });
return r;
} catch (e) {
anotar({ ...base, resultado: e.name === 'AbortError' ? 'cancelado' : 'excecao', ms: Math.round(performance.now() - inicio), motivo: e.name });
throw e;
}
};
}
04 · Trigger the outcomes and read the log
The script searches, gets the topic wrong, refuses, cancels, and books through MCP with a refusal. It writes logs/chamadas.jsonl.
== Chromium 156.0.8078.4 (Playwright) · modo: nativo
html buscar_oficinas ok 0 ms
webmcp buscar_oficinas ok 0 ms
webmcp buscar_oficinas erro 3 ms · tema inválido: cobol
webmcp reservar_vaga recusado 184 ms
webmcp reservar_vaga cancelado 319 ms · AbortError
mcp buscar_oficinas ok 0 ms
mcp reservar_vaga pediu-pessoa 26 ms
mcp reservar_vaga recusado 2 ms
REGISTRO: 8 chamadas · ok 3 · erro 1 · recusado 2 · cancelado 1 · pediu-pessoa 1 · dado pessoal no registro: não
gravado em logs/chamadas.jsonl
saída: 0
{"t":"2026-10-19T20:03:13.159Z","superficie":"mcp","ferramenta":"reservar_vaga","campos":["id","nome"],"resultado":"recusado","ms":2}
The last line is a real line from the file: the fields id and nome show up by name, never by value. The milliseconds vary on each run.
Someone swapped campos for entrada: input "to debug". People's names ended up in the log, and the script exits with 1. A log with personal data becomes a liability (LGPD): log what helps you operate, not what identifies someone.
05 · The leak rehearsal
On a throwaway copy of the kit.
Terminal · log with personal data
cd ~/projetos/formacao-webmcp-expert-v72/kit
E=$(bash scripts/copia.sh a3)
# o erro comum: registrar a entrada inteira "para depurar"
sed -i 's/campos: Object.keys(input).sort()/entrada: input/' $E/pagina/registro.mjs
cd $E && node scripts/observar.mjs nativo | grep "REGISTRO"; echo "saída: ${PIPESTATUS[0]}"
06 · Practice
Read the log, watch the leak get caught, and decide what your site logs. Each exercise ends in a done when.
E1 · The outcomes in the log
5 min
Run the logging block and count the lines by outcome.
Done when:node scripts/observar.mjs nativo; echo "saída: $?" → REGISTRO: 8 chamadas · ok 3 · erro 1 · recusado 2 · cancelado 1 · pediu-pessoa 1 · dado pessoal no registro: não and saída: 0.
E2 · Catch the leak
3 min
Run the log rehearsal with the whole input.
Done when: The block prints dado pessoal no registro: SIM and saída: 1.
E3 · Your site's log
15 min
For your site's tools, write which fields go into the log (name only), where the line goes in production, and who looks at the summary each week.
Done when: The file registro-[your site].md has one line per tool, the log destination, and the owner, and no field with an input value.
07 · Lesson quiz
The explanation appears after the answer.
Question 1 of 4
The MCP booking with confirmation left two lines in the log. Why?
Correct
This is the MRTR from the 2026-07-28 revision: the tool returns input_required, the client asks, and the call is repeated. There is no session in the protocol.
Not this one
This is the MRTR from the 2026-07-28 revision: the tool returns input_required, the client asks, and the call is repeated. There is no session in the protocol.Review: The outcomes that matter
Question 2 of 4
The log shows many "recusado" lines in reservar_vaga. What should you look at first?
Correct
Recusado = the person said no. The protection worked; many refusals suggest the agent is proposing the wrong thing. Invalid input shows up as an error.
Not this one
Recusado = the person said no. The protection worked; many refusals suggest the agent is proposing the wrong thing. Invalid input shows up as an error.Review: The outcomes that matter
Question 3 of 4
Why does the log store campos: ["id", "nome"] and not the values?
Correct
The name of the person booking is personal data. To operate, all you need to know is that the call had an id and a name, the outcome, and the time. The rehearsal shows the script catching the leak.
Not this one
The name of the person booking is personal data. To operate, all you need to know is that the call had an id and a name, the outcome, and the time. The rehearsal shows the script catching the leak.Review: The wrapper
Question 4 of 4
Where does the observado() wrapper live in the kit?
Correct
The log sits at the boundary (form, WebMCP tool, MCP tool), around execute. The rule stays pure and the same registro.mjs serves the page and the server.
Not this one
The log sits at the boundary (form, WebMCP tool, MCP tool), around execute. The rule stays pure and the same registro.mjs serves the page and the server.Review: From the request to the line in the log
Quiz complete
You got {a} of {n} right. If you get them all right, the lesson is marked as completed.
Choose an option. The explanation appears after your attempt.
08 · In three sentences
LineSurface, tool, field names, outcome, and time: one per call.
OutcomeError, refusal, cancellation, and an MRTR request tell different stories.
PrivacyThe input value stays out of the log; the script catches whoever forgets.