Public: anyone can read this; the crew can change it.
The agent door refuses what it did not advertise: MCP arguments checked against each tool's schema, and an id that is digits #73
Check · Review · Land · Live done — Landed and live in production.
Closes #60.
shipped production is serving it deploy 6ecafc0
Sign in to join this conversation.
new above
offline
Closes #60.
src/schema.tsis new and reads exactly the JSON Schema subset the tool table uses —type: "object"withpropertiesandrequired, and per propertyinteger/number/string/boolean/objectwithminimum/maximumorminLength/maxLength.check(schema, args, toolName)returns the app's ownResult, so a refusal is one sentence naming the argument and what was expected: `invalid:titleis a string, not an integer.,invalid: no argument namedseats; open_table takes title, seats_max, turn_seconds, deadline_seconds.,invalid:titleis required; …. It stays a shape-checker: no default invented, no coercion, no rule of the game. A keyword it does not read is a reported failure rather than a silent pass, andunderstood()is howsrc/schema.test.ts` holds every published schema to the subset.rpc'stools/callnow runscheckbefore the action and answersisError: trueat 200 — never a crash, never a stack. The result-to-answer mapping moved into onetoldhelper, so a refused shape and a refused rule read alike. Insrc/actions.ts,find()andwhole()share adecimalhelper: a safe integer, or a string of digits and nothing else, so1e3,1.0," 1","0x1","+1"andInfinityname no table and no bound.What to look at: the tool tests in
src/server.test.tsare table-driven overtools— wrong type, unknown argument, missing required for each, then a good call per tool, plus a check that no table got named123and thattable_statefor a missing id still refuses undernot_found, which is what keeps the door from becoming a second rules engine. Every probe there uses its own forge identity because writes are rationed per person. In the preview:POST /mcpwith{"title":123}and with{"title":"x","seats":6}, and/api/tables/1e3(404) beside/api/tables/<real id>(200).Two things I chose differently from the issue:
src/schema.tsis ~170 lines, not under a hundred — the extra is the readable failure sentences (what arrived, and bounds said in characters) and the schema-keyword reporting; I would rather cut the message quality than the checks if that is the wrong trade. And the1e3vs1000pairing is tested at both levels: the action test insrc/actions.test.tsinserts a table at id 1000 and walks eighteen rejected spellings, while the HTTP 404s for/api/tables/1e3and/tables/1e3live insrc/server.test.ts, which is where this repo keeps route tests.bun test: 119 pass, 0 fail.🤖 Generated with Claude Code
Closes #60