toolmenu
This server exposes toolmenu utilities for inspecting, comparing, session-testing, and explaining checks for MCP servers.
snapshot_menu — Start or connect to an MCP server, list its tools, report findings and token cost, and optionally save the menu as a baseline.
diff_menus — Compare two saved menus to detect breaking changes, token changes, and the required version bump.
check_session — Run a read-only tool-call session against a server, re-list tools after calls, and report menu changes caused by each call.
explain_rule — Explain a specific toolmenu rule or list all rules, including default levels and how to change them.
Connects to Atlassian's hosted MCP server to snapshot and diff its tool menu, run session checks for mid-session changes, and verify protocol compliance and client-visible tool availability.
Connects to GitHub's hosted MCP server to snapshot and diff its tool menu, run session checks for mid-session changes, and verify protocol compliance and client-visible tool availability.
Connects to Notion's hosted MCP server to snapshot and diff its tool menu, run session checks for mid-session changes, and verify protocol compliance and client-visible tool availability.
Connects to Sentry's hosted MCP server to snapshot and diff its tool menu, run session checks for mid-session changes, and verify protocol compliance and client-visible tool availability.
Connects to Supabase's hosted MCP server to snapshot and diff its tool menu, run session checks for mid-session changes, and verify protocol compliance and client-visible tool availability.
Connects to Vercel's hosted MCP server to snapshot and diff its tool menu, run session checks for mid-session changes, and verify protocol compliance and client-visible tool availability.
License
MIT
An MCP server can pass its own tests and still fail in the client. The client's SDK rejects a tool list the server's own test client accepted. A default built from the clock makes every listing different, so no prompt cache ever matches. A patch release makes a parameter required. None of it shows until someone's agent has fewer tools, or a bigger bill, and no error to explain it.
toolmenu does what a client does, in order, and checks each step against the spec, the tool's own schemas and each client's documented limits:
No LLM is involved, so the same server gives the same findings on every run and it fits in CI. It doesn't tell you whether an agent picks the right tool; that takes evals with a model (what it can't catch).
npx toolmenu snapshot -- node dist/server.js # or: npx toolmenu snapshot https://example.com/mcp
npx toolmenu init -- node dist/server.js # CI: a baseline, a workflow, a comment on every PRNeeds Node 22 or later. Your server can be in any language: toolmenu starts it (stdio) or connects to it (HTTP).
Contents: What it catches · A finding · In CI · Commands · Tested on real servers · What it can't catch · Docs
Related MCP server: MCP Observatory
What it catches
Each row is something a run on real servers found (docs/FINDINGS.md has the versions and how to reproduce each).
What goes wrong | Seen on | |
Lists and results a client rejects | The official SDK client validates | 12 of 1,089 registry servers: 8 lose every tool, 4 lose answers |
A menu that never matches twice | A default built from the clock, an order from a hash set, a container ID in a description. Every listing or every restart is a new menu, so cached prompts miss | PayPal ( |
A menu that changes mid-conversation | Most clients put tools first in the prompt, so a tool added by an "unlock" call means the conversation after it is processed again. 2026-07-28 rules per-connection menus out | One unlock re-processed 40–57K cached tokens in the author's own logs |
Releases that break callers | Removed tools, new required parameters, narrowed types, a tool that became destructive, with the version bump the change needs | The official filesystem server made |
Sign-in a strict client can't finish | OAuth discovery that differs from the RFCs: a | 12 of 79 vendor endpoints that answer 401 name a parent or slash-variant |
Tools one client can't use | More tools than the client sends, names its API refuses or rewrites, descriptions cut before the instruction that matters | 6 servers in Docker's catalog list more than VS Code's 128 a request |
Tokens spent on nothing | Unused | Notion's server: the same 9 unused |
It also has heuristics for what agents misread (a parameter called just id, writes
with no annotations, two tools fighting over one word). Those are marked as guesses and
are never errors. Every rule is in docs/RULES.md.
What a finding looks like
A server whose unlock_toolset tool adds two tools to the menu mid-conversation, run
with toolmenu session:
step 3: call unlock_toolset {"toolset":"audits"} · answered · menu changed · +2 tools · list_changed received
ERROR session/mid-insert
+2 tools inserted at position 1 (list_team_audits, get_team_audit). Invalidates the cached prompt:
the tool list and the conversation after it are processed again.
cost: the whole tool list (this server's part: ~406 tokens, estimate) and the conversation after it
→ Next: Add new tools at the end of the list, not in the middle; better, list them from the start.
WARN session/unlock-coverage · unsure
unlock_toolset looks like it unlocks tools, and the run got through 1 of its 2 toolset values. […]
not unlocked: "reports"
→ Next: Unlock every value: session --init puts each one in the starter.Every finding has the same parts: a level (ERROR, WARN, INFO; --fail-on
decides which fail the run), a rule id you can look up (toolmenu explain session/mid-insert) or turn off in the config, where it happened (the step, the
tool, the parameter path), what toolmenu saw, and a → Next: step.
How much to trust a finding depends on what it rests on, and the rule id's prefix says which:
Rule family | What it rests on |
| The protocol: the official JSON schema for the server's version (pinned in |
| Two menus compared. A breaking change is one where a call that passed validation before now fails it |
| Behaviour toolmenu observed: the menu changing after a step, between two calls, between two processes or connections, or served to a bad credential. The finding names the step or the call |
| How agents read the menu: names, words, descriptions. Tuned on real servers, but heuristics |
A finding marked · unsure, in any family, rests on names and words rather than on
something toolmenu saw happen (above: "looks like it unlocks tools"). It says what was
seen, not what it means. No LLM is involved anywhere: the same server gives the same
findings on every run.
In CI: one PR comment
npx toolmenu init -- <your server command> snapshots the server into menu.json (the
baseline) and writes this workflow for your project (Node, Python, Go or Rust). Commit
both. Credentials you pass go into the workflow as ${{ secrets.… }}, never as text.
# .github/workflows/toolmenu.yml
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
toolmenu:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with: { node-version: 22 }
- run: npm ci && npm run build
- uses: niksa90/toolmenu@v0.27.1
with:
command: node dist/server.js
baseline: menu.json # your committed snapshot
scenario: scenario.yml # optional: run a session tooEvery pull request then gets one comment that updates on each push. It leads with a
verdict (can this menu replace the baseline as it is?), then the breaking changes, the
token change, the version bump the change needs, and anything session caught:
That's toolmenu review --format markdown on two real releases of the official
filesystem server. The same findings go to GitHub code scanning (SARIF), to GitLab,
Jenkins and others as JUnit, or into one offline HTML page:
Inputs, servers behind auth, forks and gate: review are in
docs/github-action.md; SARIF, JUnit, HTML and the badge in
docs/ci.md.
Commands
Command | What it does | |
| Connects, lists the menu, checks it (the spec, a second process, your client's limits, token cost) and writes | |
| Plays a scenario, or builds one with | |
| Compares two menu files: breaking, minor, notice; token change; the semver bump | |
| Should the new menu replace the baseline? One verdict and an exit code | |
| Walks an HTTP server's OAuth discovery as a spec-following client does, without logging in | |
| Signs in to a server behind OAuth once, in a browser; | |
|
| |
| Installs a package's published versions one by one and diffs them. Runs third-party code: use a container | |
| Writes the baseline and the CI workflow | |
| toolmenu as an MCP server and a Claude Code plugin, so the agent building your server can check it while it works | |
| What a rule checks, its default level and how to change it |
--client <name> checks the menu for one client, from that client's own docs: Claude
Code, the Claude API, OpenAI, Codex, VS Code (Copilot Chat), Copilot cloud agent and code
review, Gemini CLI, the Gemini API or Antigravity (docs). A client
that doesn't document a limit doesn't get one: Cursor's and Windsurf's aren't checked.
session calls tools for real. Without allow_writes: true it refuses any tool not
marked readOnlyHint: true, and --call-everything is refused over HTTP, because a
hosted server's tools act on someone's real account.
Everything is also exported as a library, with types (docs). All protocol versions from 2024-11-05 to 2026-07-28 are supported, through the official TypeScript SDK; the table in docs/PROTOCOLS.md is written by a test that runs each one.
Tested on real servers
Rules are checked against public MCP servers before they ship, and a rule that changes
a finding on one has to say why in the changelog. The run on 2026-11-09
is the baseline for a weekly one (bench/scripts/weekly.sh),
which compares each week with the last:
Panel | Servers | What ran |
Registry stdio servers (npm), pinned versions | 1,089 |
|
Docker's MCP catalog, at the digests it pins | 237 | The same: 4,117 of 4,237 tools called (41 servers didn't list tools) |
Servers vendors publish to run locally (GitHub, AWS Labs, Microsoft, PayPal, Stripe, Notion, HashiCorp, Grafana…) | 98 | The same, with dummy credentials: 2,213 of 2,292 tools called (20 didn't list tools) |
Hosted servers: 109 vendor endpoints and a seeded 2,000 from the registry | 2,109 |
|
Read those numbers for what they are. Most servers pass most checks: about 1 in 90 of the registry's stdio servers fails in the official SDK client. A sealed container with made-up input shows whether a tool crashes, hangs or breaks its own schema, not whether it does its job: more than half of the calls there came back as a readable tool error, the right answer to made-up input or a dummy key. Hosted servers got a few read-only calls each.
The reference server, @modelcontextprotocol/server-everything, gets no false finding,
and a clean fixture server must get none from any command; the test suite fails if one
appears. What the runs found, including what toolmenu itself got wrong and how it was
fixed, is in docs/FINDINGS.md. The corpora, the scripts and the
datasets are in bench/.
What it can't catch
toolmenu sees what the server sends. Plenty of agent failures happen elsewhere:
Whether the agent picks the right tool. That needs evals with a model. toolmenu stays deterministic on purpose;
routes.ymlpins keyword routing, which is as far as it goes.What the client does with the menu. Clients merge several servers, sort, filter and cut. A stable server menu is necessary for cache hits, not sufficient. The
--clientprofiles cover what each client documents, and nothing it doesn't.A feature with no tools. If the agent reaches for something your menu doesn't have, no menu check sees the gap.
Whether a tool does its job. A call that answers is checked against the schema it declares, not against what it should have done.
Security. It isn't a security scanner or a full conformance suite.
Token counts are estimates (o200k_base) of what the model reads: each tool's name,
description and input schema. Vendors tokenize, cache and bill differently.
Docs
snapshot: rules, where the tokens go,
routes.yml, the config file,scansession: scenarios,
--init,--auto,--call-everything, every session rulediff, review, catalogs, history: how changes are classified, the verdicts, operations behind a search tool
clients: what each
--clientprofile checks, and its sourcesusage: HTTP and OAuth, proxies, the MCP server and plugin, the library, every option
About
Built by Nikša Perović while running a 115-tool MCP server and watching agents trip over its menu. It started as a check for menus that change at the wrong time (the story, and where it was wrong) and grew into a client-side test for the whole server as real servers kept failing in other places.
Found a false positive, or a failure toolmenu should catch?
Open an issue with the menu (a
menu.json is enough) and what you expected.
Available Tools
6 toolscheck_authARead-only
Check an HTTP MCP server's OAuth discovery the way a client that follows the spec walks it, without logging in: the 401 and its WWW-Authenticate, the protected resource metadata, the authorization server's metadata, PKCE and registration. Use it when a server answers 401 or a client cannot sign in. Sends unauthenticated requests to the URL and to the metadata addresses it names; no credentials, no tokens.
Inputs: url (required).
check_sessionADestructive
Call an MCP server's tools as an agent would and check what comes back: its tools marked readOnlyHint whose arguments the schema can fill are called, each result is checked against the tool's outputSchema, and the menu is listed after each call, with each change reported with the call that caused it. Calls only read-only tools; never one marked openWorldHint unless open_world is set. A command server is started by running command as given, so only pass a command you would run yourself.
Inputs: cwd, env, url, command, +3 more.
explain_ruleARead-only
Explain a toolmenu rule by the name a finding gives (naming/vague-id): what it checks, its default level and how to change it. Without rule, lists every rule.
Inputs: rule.
review_baselineARead-only
Say whether a new menu file should replace the baseline: one verdict (unchanged, safe, breaking-released, breaking, review), what breaks callers, and which findings the new menu adds, clears or makes worse. Use it before overwriting a committed menu.json. Reads the two files only and starts no server.
Inputs: baseline (required), candidate (required), cwd, release.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 6 tools
Most tools are clearly separated by what they touch: snapshot_menu reads a static tool list, check_session actually calls tools, check_auth walks OAuth discovery, explain_rule is documentation. The one fuzzy boundary is review_baseline vs diff_menus, since both read two menu files and both report client-breaking changes; only the extra verdict and the 'before overwriting menu.json' framing distinguish them.
All six names follow a clean verb_noun pattern (snapshot_menu, check_session, check_auth, review_baseline, diff_menus, explain_rule) in consistent snake_case. The verb choice is predictive: snapshot/check/diff/review/explain telegraph the operation.
Six tools for a focused domain (auditing MCP server tool menus) — each one covers a distinct phase: static snapshot, live call check, auth check, diff, baseline verdict, rule lookup. Nothing looks redundant or bolted on at this count.
The lifecycle is nearly closed: capture a menu, exercise it live, check auth, diff against a baseline, and get a verdict before overwriting. The visible gap is that there is no explicit 'promote/accept new menu as baseline' operation — snapshot_menu can save a file but the swap is left to file handling — a minor wraparound an agent can manage.
Maintenance
Related MCP Connectors
Monitor MCP servers, API contracts and AI outputs for schema drift. Alerts on breaking changes.
Statically audits MCP tool surfaces for token cost, schema quality, and design issues.
MCP Spec Compliance MCP — audits any MCP server.json against the official Model Context Protocol
Check an MCP endpoint resolves: liveness, live tool list, schema drift. Free.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA basic MCP server template that provides a foundation for building custom tools, resources, and prompts. Serves as a starting point for developers to create their own MCP server functionality.-
- AlicenseBqualityBmaintenanceFinds problems in your MCP servers before your users do. Scans your Claude config, checks every server's capabilities, actually calls tools to make sure they work, and catches breaking changes between versions.13665 npm121MIT
- AlicenseAqualityDmaintenanceMCP server that validates LLM-generated tool-call arguments, lints tool definitions, and produces retry messages for AI assistants.352 npm1MIT
- AlicenseAqualityBmaintenanceA stdio MCP server that audits other MCP servers over the live protocol. It connects to any MCP target (stdio or HTTP), lints every tool's schema for agent-usability, then actually calls the tools with deliberately broken inputs to see how the server handles them, and returns a 0–100 conformance score with a per-dimension breakdown rendered as Markdown.619 npm6MIT