English · Español
Break 00 — Malformed tool-call schema: missing required field¶
🇪🇸 Rompemos el JSON-Schema del tool
conjugatequitando el arrayrequired. El modelo emite tool calls con argumentos parciales ({"verb": "eat"}sintense); el servidor MCP cae al executor conKeyError; el cliente del agente recibe un error genérico que no diagnostica la causa. La lección es por qué la validación tiene que vivir en el cliente.
This /break exercise targets the defense-in-depth structure of agent-tool contracts. The bug is one removed line in a JSON-Schema; the failure surface is across three layers — model, MCP server, agent parser.
Anchors: theory/01-function-calling-formats.md, theory/02-mcp-architecture.md, theory/05-mcp-wire-and-100-line-server.md, .claude/commands/break.md.
Hypothesis¶
The learner predicts: "If I remove the required field from the tool schema, the LLM (Mini-GPT) — without schema-mask constraints on every field — will occasionally emit tool calls missing arguments. The MCP server's dispatch will accept them (they're valid JSON), pass them to the conjugation function, which will raise KeyError. The error propagates back as JSON-RPC error code -32603 (internal error) instead of the correct -32602 (invalid params). The agent parser doesn't know whether to retry or to give up."
The break¶
In src/minimcp/server.py:
CONJUGATE_SCHEMA = {
"type": "object",
- "required": ["verb", "tense", "person"],
+ # /break: removed `required` — schema no longer enforces presence
"properties": {
"verb": {"enum": ["work","play", ..., "write"]},
"tense": {"enum": ["infinitive", ..., "simple future"]},
"person": {"enum": ["1st singular", "2nd singular", "3rd singular"]},
},
}
One key removed. The schema is still valid JSON-Schema — required is optional. But for the conjugation tool, the omission means a tool call with only {"verb": "eat"} passes schema validation.
Predict, then run¶
The Phase 32 agent loop generates tool calls by sampling from Mini-GPT with the schema as a soft constraint. Without required, the model occasionally truncates early ("{\"verb\": \"eat\"}"), especially when the grammar prompt is ambiguous (e.g., "conjugate eat for me").
Predictions¶
- Tool calls without all three args: 15-25% of agent calls (when temperature > 0).
- MCP server response:
- If the conjugation function uses
args["tense"], it raisesKeyError. - The default exception handler returns JSON-RPC code
-32603("Internal error") — not-32602("Invalid params") — because the validation happens inside the function, not at the schema boundary. - Agent parser: receives
-32603and retries (because-32603is sometimes transient). The retry has the same problem. The agent loops 3-5 times before giving up. - End-to-end failure rate: ~20% of tool calls give an unrecoverable error. The §A13 grammar tutor's overall accuracy drops by ~15 pp on the 30-example eval.
Write your predictions in learners/borja/phase-31/notes/breaks.md before running.
Observe¶
Run the Phase 31 round-trip with the broken schema:
Diagnostics to plot:
- Histogram of JSON-RPC error codes seen over 100 tool calls: in the broken state,
-32603should appear in ~20% of responses. In the baseline, ~0%. - Server log: the
KeyErrorshould appear in the server's stderr (not in the JSON-RPC response body — which is the diagnostic-loss problem). - Agent log: count the retries per session. Broken: average ~2.5 retries per call. Baseline: ~0.05.
Symptom Borja will see¶
- Server log shows
KeyError: 'tense'traces. - Client log shows JSON-RPC error code
-32603("Internal error") — the wrong code for "client passed bad args". - The agent retries because
-32603is conventionally retryable. - §A13 grammar-tutor end-to-end accuracy drops ~15 pp.
Hidden cause (one sentence)¶
Removing required from the JSON-Schema makes the schema accept partial arguments; the validation that would have caught it on the server side is gone, so the failure surfaces deep in the tool implementation as a KeyError, mapped to the wrong JSON-RPC error code.
Hint cascade¶
- Print the agent's tool-call JSON for 5 sample sessions. Are all three args always present?
- Run the server in verbose mode. What error code is returned for partial-args calls?
- Compare your tool schema to
theory/05-mcp-wire-and-100-line-server.md§"A ~100-line MCP server" — what field is missing?
Fix diff¶
CONJUGATE_SCHEMA = {
"type": "object",
+ "required": ["verb", "tense", "person"],
"properties": {
...
},
}
And — defense-in-depth — add explicit validation in _handle before dispatch:
def _handle(req: dict) -> dict | None:
...
if method == "tools/call":
name = req["params"]["name"]
args = req["params"].get("arguments", {})
if name not in _TOOLS:
return _error(rid, -32601, f"Tool '{name}' not found")
fn, schema = _TOOLS[name]
# Validate args against schema BEFORE dispatch.
try:
jsonschema.validate(args, schema)
except jsonschema.ValidationError as e:
return _error(rid, -32602, f"Invalid params: {e.message}")
...
The fix is two layers: correct schema and explicit validation. Either alone is fragile; both together produce the right error code at the right boundary.
Why this teaches the concept¶
The lesson is where validation happens determines what error you see. A schema with required lets the MCP server reject partial args at the protocol boundary (-32602). Without required, the server passes invalid input to the tool implementation, which fails with a Python exception, which becomes a generic -32603. The agent parser cannot distinguish "I sent malformed input" from "the network is flaky" — they both look like internal errors.
Defense-in-depth means: validate at every boundary. The model emits a candidate; the agent parses and validates the JSON shape; the MCP client validates against the tool schema before sending; the MCP server re-validates on receipt; the tool implementation can still raise if invariants are violated. Five layers, each with the right error code for the right failure. Skipping any one of them — like removing required — pushes the failure to a layer that doesn't have the context to diagnose it.
For the §A13 grammar tutor, this is the difference between an agent that succeeds 95% and one that thrashes at 65%. The break is small; the consequence is the agent's reputation.
Reference¶
- JSON-RPC 2.0 error code conventions — why
-32602and-32603mean different things. - JSON-Schema
requiredkeyword — the missing keyword. - MCP specification §"Tool argument validation" — the protocol explicitly delegates argument validation to JSON-Schema, with the implicit assumption that schemas are well-formed.
Next: restore required and run lab/02-mcp-roundtrip.md to confirm the error-code distribution returns to the baseline.