← back to Agent Permission Config Check
Opening settings.json or an mcpServers block and skimming it before committing is a normal way to catch an obvious problem once, and it works fine for a two-server config checked right after you write it. It is a different job from confirming the config is still tight today, after every onboarding session or MCP server someone else has added since -- most of which nobody thinks to re-read, because they didn't add it themselves.
| Step | Common gap by eye | What this checker does |
|---|---|---|
| Spotting a Bash allow-all rule | Obvious in a two-line example; buried and easy to skim past among dozens of narrowly-scoped rule strings in a config that grew over months of onboarding. | Flags a bare "Bash" or "Bash(*)" entry by rule, every time, regardless of how many other rules surround it. |
| Spotting an unscoped destructive command | Requires recognizing that Bash(rm) with no colon-scoped path is functionally unlimited -- easy to read as "the same shape as the other rules" without checking each one's scope. | Checks every allow rule's scope and cross-references it against the deny list in the same pass. |
| Spotting a plain-http MCP url | A url that looks superficially fine (a real domain, a real path) is easy to approve without noticing it starts with http:// rather than https://. | Checks the literal scheme of every server's url, every time. |
| Spotting a hardcoded token in env/headers | Obvious in a two-line example; much less obvious in a real server block where most values legitimately are literals (a model name, a log level) and the one live token blends in. | Applies the same known-token-format and reference-pattern check to every env/headers value, every server, every time. |
| Re-checking after later, unrelated changes | Nothing prompts a re-read of a server block you didn't just add -- drift accumulates silently between the checks that do happen. | Costs nothing to re-run; wiring the CLI into CI on every PR makes the re-check automatic rather than something to remember. |
Nothing, for a short config checked once. It stops scaling the way these files actually grow: an mcpServers block that accumulates entries from several onboarding sessions means re-reading all of them, correctly, every time anyone adds or edits one — and nobody re-checks a server block they didn't just touch. This tool checks every allow/deny rule and every server entry the same way, every time you run it, in the time it takes to paste the file in.
It is obvious in a two-line example. It is much less obvious in a real mcpServers block with several servers, each carrying a handful of env or header entries, most of which are legitimately literal (a model name, a port number, a log level) — the one entry that is a live token blends into the rest unless something specifically checks each value's shape. This tool applies the same known-token-format and reference-pattern checks to every value, every time, rather than relying on a reviewer's eye catching the one that matters.
Only if the reviewer already treats permission config with the same scrutiny as application code — and settings.json / .mcp.json are usually written once during setup, outside the normal review flow that catches a risky application-code change. Even when it is reviewed, "Bash" and "Bash(*)" read as ordinary configuration next to dozens of other rule strings unless the reviewer is specifically looking for the allow-all shape. This tool flags it by rule, every time, regardless of whether the reviewer already knows to look.
No. The browser page parses the JSON you paste entirely in your own browser and never sends any of it anywhere — exactly like the rest of this tool. The CLI reads only local files, with no network call, ever.
node cli.mjs check --root .
node cli.mjs check --file .mcp.json --json
Exits 1 if any high/medium finding is present, so it works as a CI gate on the same PR that adds a server or widens a permission rule. See why these configs need a linter, and this checker's own named limits.