Built by The Foundry, an autonomous build pipeline I run. A Haiku scout finds a developer pain point, a Sonnet agent writes the spec, and aider driving Sonnet builds it overnight.
This repo was produced end to end by that pipeline. I commissioned the system, approved each phase of it and reviewed what it shipped.
A linter for the tool definitions you hand to an LLM. It reads MCP, OpenAI function-calling and Anthropic tool-use schemas, applies twelve rules, and scores each tool out of 100. The rules cover the things that make an agent pick the wrong tool or call it wrongly: a description too short to disambiguate, a parameter with no description, a parameter with no type.
Built on 2 March 2026. Twelve rules, three formats, three output formats, 23 tests.
It isn't published to npm, so run it from source:
git clone https://github.com/solstice035/tool-lint.git
cd tool-lint
npm install
npm run build
node dist/bin/tool-lint.js check examples/mcp-tools.jsonnpm link puts it on your path as tool-lint.
$ tool-lint check bad-tools.json
bad-tools.json
get_data [61/100]
⚠ Tool "get_data" has a very short description (9 chars)
bad-tools.json > get_data > description
→ Expand the description to at least 20 characters with specific details about what the tool does
⚠ Parameter "x" in tool "get_data" is missing a description
bad-tools.json > get_data > parameters.properties.x
→ Add a description explaining what "x" is used for and what values are expected
✖ Parameter at "parameters.properties.flag" is missing a type definition
bad-tools.json > get_data > parameters.properties.flag
→ Add a "type" field (e.g., "string", "number", "boolean", "object", "array")
Summary
──────────────────────────────────────────────────
Files checked: 1
Tools analyzed: 1
Total violations: 4
Average score: 61/100
tool-lint check [files...] lint files, directories, globs, or "-" for stdin
--format <text|json|sarif> output format (default: text)
--min-score <n> fail if any tool scores below n
--severity <level> minimum severity to report (error|warning|info)
--ignore <rules> comma-separated rule IDs to skip
--config <path> path to a config file
-q, --quiet summary only
-v, --verbose include passing checks
tool-lint rules list the rules
tool-lint init write a .toollintrc.json
SARIF output is there so GitHub can show findings in the Security tab.
| Rule | Severity |
|---|---|
missing-tool-name |
error |
missing-tool-description |
error |
missing-required-field |
error |
no-type-specified |
error |
duplicate-param-names |
error |
vague-tool-description |
warning |
missing-param-description |
warning |
vague-param-name |
warning |
too-many-params |
warning |
deeply-nested-schema |
warning |
ambiguous-enum |
info |
oversized-description |
info |
Every tool starts at 100 and loses 15 per error, 8 per warning and 3 per info. The score is a rough signal, not a measurement: it says how many rules a definition tripped, weighted by how much each one matters.
The same tool in each of the three shapes it understands, which differ mainly in where the schema hangs:
{ "name": "get_weather", "description": "Retrieves current weather for a location",
"inputSchema": { "type": "object", "properties": { "location": { "type": "string", "description": "City name or ZIP code" } }, "required": ["location"] } }MCP uses inputSchema, Anthropic uses input_schema, and OpenAI uses parameters. A file can hold a single tool or an array of them.
- A
.toollintrc.jsonin the project root is not picked up on its own. You have to pass--config .toollintrc.json.tool-lint initwrites the file but nothing reads it by default. - Only
ignoreis read from that config. TheminScore,rulesandformatkeys are parsed and then unused, so rule severity can't be changed from the file.--ignoreand--min-scoreon the command line do work. initwrites its rules as{"enabled": true}objects, which is a third shape again, and also unused.- Reading from stdin writes to a fixed path,
/tmp/tool-lint-stdin.json, so two runs at once will collide.
npm install
npm run build
npm test # 23 testsMIT. See LICENSE.