Strict input-schema lint
Status: implemented. Tracked as epic WOR-1236 and child WOR-1241.
An under-constrained inputSchema lets malformed input reach the server, which the runtime-fault taxonomy (Real Faults in MCP Software, arXiv:2603.05637) ties to a class of parameter and type-validation faults. The fuzzer finds these at runtime; the schema lint catches them statically, which is cheaper. The two are complementary: a tool that passes the lint is far less likely to crash under a fuzz sweep.
The rules
Each rule inspects one tool's inputSchema and carries a stable id.
| Rule | Severity | What it flags |
|---|---|---|
| SCH-001 | warning | the object declares properties but no required list, so the server cannot rely on any argument being present |
| SCH-002 | warning | additionalProperties is not false, so unexpected fields are accepted silently |
| SCH-003 | critical | a property declares neither type nor enum, so any value is accepted |
| SCH-004 | warning | a string property has no maxLength, or an array property has no maxItems, so input size is unbounded |
The targets
The findings surface through the tool_quality: block as two assertable targets, alongside the existing description-quality targets:
schema_warnings: count of SCH findings at warning severity.schema_criticals: count of SCH findings at critical severity.
tool_quality:
- name: tool schemas are well constrained
server: local
expect:
- target: schema_criticals
matcher: { schema: { maximum: 0 } }
- target: schema_warnings
matcher: { schema: { maximum: 3 } }
These do not change the default tool_quality: gate; declare them explicitly to opt in.
The autofix
The lint ships a mechanical fix. Given an under-constrained schema it returns a tightened copy that sets additionalProperties: false and adds a required list of every declared property, applied recursively to nested object schemas. It deliberately does not invent a type, a maxLength, or a maxItems, since the right value is the author's to choose, so SCH-003 and SCH-004 stay findings rather than guesses. The examples/tool-schema-lint directory pins a loose schema and its tightened output together with a byte-for-byte test.
Run the lint and the autofix from the command line over a captured tools/list snapshot:
mcptest schema-lint tools.json # report findings, exit 1 if any
mcptest schema-lint tools.json --fix # print the tightened catalog
mcptest schema-lint tools.json --fix --write # tighten the snapshot in place
mcptest schema-lint is the standalone surface; the same lint also runs inside a suite's tool_quality: check. See the CLI reference for every flag. (This is distinct from mcptest lint, which scans suites for deprecated MCP features.)
What it does not do
The lint is structural: it checks that a schema constrains its inputs, not that the constraints are semantically right. A maxLength of one million still passes SCH-004. Pair it with the fuzzer, which exercises the actual runtime handling the schema describes.