Generate least-privilege Claude Code permission rules for MCP tools from their live annotations: readOnlyHint tools go to allow, everything else to ask, one mcp__server__tool rule each, never a glob.
翻译准备中
安全TypeScript · DockerfileGitHub @basitalisandhu在线运营者未确认
试用过了吗? 点一下就好,无需登录。
个人 GitHub 账号下公开仓库的所有者可以登录并验证所有权,自行管理介绍,无需管理员批准。
登录并生成验证文件,在 30 分钟内将其提交到此仓库的默认分支。其他分支或分叉仓库中的文件无效。然后在下方检查文件,即可无需管理员批准直接管理项目。请保留文件以维持管理权限。
如需登录,将前往登录页面。登录后请回到此处,再次点击所选的验证按钮。
在项目文件夹中运行 /nomorevibe verify,即可验证所有权并自行管理介绍。
一分钟完成验证Generate least-privilege Claude Code permission rules for MCP tools from their live annotations: readOnlyHint tools go to allow, everything else to ask, one mcp__server__tool rule each, never a glob. Merges into settings files, prints a diff, and --check exits 1 when annotations drift. TypeScript CLI on npm.
翻译准备中
取自仓库 README
正在加载完整 README。 以下为已保存的摘录。
claude-mcp-allow: least-privilege Claude Code permission rules for MCP tools
Generate permissions.allow and permissions.ask rules for every MCP tool Claude Code can reach, from the annotations each server reports on tools/list: a tool that says readOnlyHint: true (and not destructiveHint: true) gets an allow rule, everything else gets an ask rule, one exact mcp__ __ rule per tool and never a glob.查看完整 README ↗Generate permissions.allow and permissions.ask rules for every MCP tool Claude Code can reach, from the annotations each server reports on tools/list: a tool that says readOnlyHint: true (and not destructiveHint: true) gets an allow rule, everything else gets an ask rule, one exact mcp__<server>__<tool> rule per tool and never a glob. --write merges the rules into the settings file you choose, --diff shows what would change, and --check reconnects later and exits 1 when a server's annotations have drifted from the rules you saved.
It is for people who use Claude Code with MCP servers and want read-only tools to stop prompting without allowing everything, and for teams who commit .claude/settings.json and want CI to notice when a server's annotations change.
No install and no token needed; run it from a project whose MCP servers you want rules for:
npx github:basitalisandhu/claude-mcp-allow # print the proposed rules, change nothing
npx github:basitalisandhu/claude-mcp-allow --diff --write
npx github:basitalisandhu/claude-mcp-allow --check # later, or in CI: exit 1 on drift
npx github: fetches the source from GitHub and builds it on first use (pin a release with #v0.4.0). For a server that marks its tools, the report looks like this:
annotated (project, .mcp.json) 7 tools: 2 allow, 5 ask
allow get_user readOnlyHint is true
allow list_items readOnlyHint is true
ask delete_item destructiveHint is true
ask update_item annotated but not marked read-only
Claude Code asks before every MCP tool call unless a permission rule covers it. Its permission engine does not read MCP tool annotations: the request to auto-allow readOnlyHint tools and keep prompting for destructiveHint ones (anthropics/claude-code#87452) was closed as not planned, and on Claude Code on the web the connector prompt offers only "Allow once", so read-only tools ask on every call and unattended runs stall (anthropics/claude-code#99112). The permissions docs give you the rule syntax (mcp__<server>, mcp__<server>__<tool>, and mcp__<server>__* only after a literal server prefix) and leave the rule writing to you. So people hand-roll hooks that guess by verb, or allow a whole server, or keep clicking.
This tool writes the rules for you, from what the servers themselves report, and tells you when that report changes.
Annotations are untrusted hints. The MCP project's own guidance on tool annotations is that every annotation property is a hint and clients must treat them as untrusted unless the server is trusted, and that many servers ship without them. This tool therefore never auto-allows a tool that is not marked read-only: without annotations a tool goes to ask, a tool with destructiveHint: true goes to ask even when it also claims readOnlyHint: true, and a tool whose tools/list entry carries _meta["anthropic/requiresUserInteraction"]: true goes to ask regardless, because Claude Code prompts for it on every call even when an allow rule matches. --heuristic is opt-in, allows only unannotated tools whose name starts with a read verb and a separator (get_, list-, search_ and so on), and is recorded in the file so --check applies the same judgement later. Every rule is one tool on one server; the tool never emits mcp__* or mcp__<server>__*, never touches deny rules or rules it did not write, and records the decision and a SHA-256 of each tool's annotations next to the rules, so --check can detect drift: an annotation that changed, a tool that disappeared, or an allowed tool that would no longer be allowed. Running the tool starts every configured stdio server with its configured command, environment and arguments, exactly as Claude Code would; review your configuration before running it against servers you do not trust.
Requires Node 20 or newer.
The quickest route needs nothing but Node: npx github:basitalisandhu/claude-mcp-allow <options>, as in the Quick start. The container image below also works without any login.
Every release is published by publish-github-packages.yml in two places on GitHub Packages: the npm package @basitalisandhu/claude-mcp-allow and the container image ghcr.io/basitalisandhu/claude-mcp-allow. The package is not on npmjs.com yet; when it is, it will use the same scoped name.
Point the @basitalisandhu scope at GitHub Packages in ~/.npmrc:
@basitalisandhu:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
GitHub's npm registry asks for a token even to install public packages. That is a GitHub limitation, not a setting of this repository: use a personal access token (classic) with the read:packages scope, exported as GITHUB_TOKEN. Then:
npx @basitalisandhu/claude-mcp-allow # run without installing
npm install -g @basitalisandhu/[email protected] # or install the claude-mcp-allow command
The image is built for linux/amd64 and linux/arm64, runs as the non-root node user (home /home/node), and is tagged with the version and latest; pin the version. The working directory is /work, so mount the project there:
docker run --rm -v "$PWD:/work" ghcr.io/basitalisandhu/claude-mcp-allow:0.4.0 --diff
The container sees only what you mount. To include user-scope servers, also mount ~/.claude.json at /home/node/.claude.json. Stdio servers are started inside the container, so only servers whose command exists in the image (Node.js is available) can be reached; HTTP servers work as long as the container can reach them. For anything else, run the npm package on the host.
The image is signed with cosign (keyless) and carries a build provenance attestation; an SPDX SBOM is attached to the GitHub release. To check it before running it:
cosign verify ghcr.io/basitalisandhu/claude-mcp-allow:0.4.0 \
--certificate-identity-regexp '^https://github.com/basitalisandhu/claude-mcp-allow/\.github/workflows/publish-github-packages\.yml@refs/tags/v' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
gh attestation verify oci://ghcr.io/basitalisandhu/claude-mcp-allow:0.4.0 --owner basitalisandhu
To build the image from a checkout: docker build -t claude-mcp-allow .
git clone https://github.com/basitalisandhu/claude-mcp-allow && cd claude-mcp-allow
npm install && npm run build
node dist/cli.js --help
claude-mcp-allow [--cwd dir] [--scope local|project|user] [--heuristic] [--write] [--diff] [--prune] [--check] [--server name] [--timeout ms] [--managed-dir dir]
| Flag | What it does |
|---|---|
--cwd <dir> | The project directory. Defaults to the current directory. .mcp.json, .claude/settings.json and the projects[<dir>] entry of ~/.claude.json are read relative to it. |
--scope local|project|user | Which settings file --write, --diff and --check use: .claude/settings.local.json (default; at the git repository root when the directory is inside one, as Claude Code does), .claude/settings.json, or ~/.claude/settings.json. |
--heuristic | Also allow unannotated tools whose name matches ^(get|list|read|search|find|fetch|describe|show|query|count|lookup)[_-]. Annotated tools are never affected. |
--write | Merge the proposed rules into the settings file for --scope. Existing keys, key order, indentation and every rule the tool did not write are preserved. |
--diff | Print the rules --write would add and remove, as + allow, + ask, - allow and - ask lines. Combine with --write to see and apply in one run. |
--prune | With --write or --diff, remove only marker-recorded rules and marker entries for servers absent from the current project's configuration. Off by default. With user scope, this can remove generated rules for other projects: inspect --diff --prune first. Configured but filtered, disabled or unavailable servers are preserved; incomplete configuration loading refuses pruning. |
--check | Reconnect to every server recorded in the settings file, recompute the annotation hashes, and exit 1 on drift. Requires a file written by --write. |
--server <name> | Only this server. Repeatable. Matches the configured name, plugin:<plugin>:<server> for plugin servers, or the server segment of the rule. |
--timeout <ms> | Per-server limit for connecting and for tools/list. Default 20000. |
--managed-dir <dir> | Read and from instead of the platform policy directory, so an administrator can preview a policy before deploying it. See . |
Exit codes: 0 success, 1 drift found by --check, 2 usage or configuration error (an unknown flag, a settings file that is not valid JSON, --check without a marker).
The proposed permissions block is printed on stdout as JSON; the per-server report, warnings and the wrote line go to stderr, so claude-mcp-allow > rules.json captures only the block.
The tool reads the same places Claude Code does, in the documented precedence, and tells you where each server came from. When one name is defined in several places, the highest-precedence definition wins and the others are reported as shadowed. Plugin servers are matched against the others by endpoint (URL or command), as the docs describe.
| Precedence | Source | File |
|---|---|---|
| 0 | organisation-managed | managed-mcp.json and managedMcpServers in managed-settings.json at the platform policy path |
| 1 | local scope | ~/.claude.json, projects["<cwd>"].mcpServers |
| 2 | project scope | <cwd>/.mcp.json |
| 3 | user scope | ~/.claude.json, top-level mcpServers |
| 4 | enabled plugins | .mcp.json at each plugin root and mcpServers in its .claude-plugin/plugin.json, found through ~/.claude/plugins/installed_plugins.json and enabledPlugins in your settings files |
Organisation-managed servers are reported with origin managed; the rules for them are under Organisation-managed servers. Connectors fetched from claude.ai are not read. CLAUDE_CONFIG_DIR moves ~/.claude (settings and plugins; .claude.json is read from there when it exists) and CLAUDE_CODE_PLUGIN_CACHE_DIR moves the plugins root. Servers listed in disabledMcpjsonServers or disabledMcpServers are skipped. A server that matches a deniedMcpServers entry from user, project, local or managed settings is skipped as blocked by deniedMcpServers: entries match a server by name, by command and arguments (exact, in order), or by a serverUrl without *. Wildcard serverUrl entries are not evaluated yet and each one is named in a warning. ${VAR} and ${VAR:-default} are expanded in command, args, env, url and headers; an unset variable without a default is left as written with a warning, and the credential variables Claude Code reads as empty in a remote server's url and headers (such as ANTHROPIC_API_KEY and NPM_TOKEN) are read as empty here too. ${CLAUDE_PLUGIN_ROOT}, ${CLAUDE_PLUGIN_DATA} and ${CLAUDE_PROJECT_DIR} are expanded for plugin servers.
Claude Code has two ways for an organisation to provide MCP servers, and this tool applies the documented rules for both:
managed-mcp.json has exclusive control. When the file exists and parses, only its servers and the managedMcpServers entries load; servers from .mcp.json, ~/.claude.json and plugins are reported as skipped. Its entries may use command, args, env and ${VAR} like any .mcp.json.managedMcpServers in managed-settings.json adds remote servers alongside the user's own. An entry is refused, with the reason in a warning, unless its type is http, sse or streamable-http, its url is https://, it has no command, args, env or headersHelper, it uses no ${VAR} reference, its name uses only letters, numbers, hyphens and underscores, and no key or value contains a control or invisible formatting character. The array form used by Claude Desktop's setting of the same name is ignored with a warning.managed-mcp.json entry wins and the other is reported as shadowed. A managed server takes precedence over a local, project or user server of the same name, and a user can still turn one off with disabledMcpServers.The files are read from /Library/Application Support/ClaudeCode on macOS, /etc/claude-code on Linux and WSL, and C:\Program Files\ClaudeCode on Windows. To try a policy before it reaches anyone's machine, put the two files in a folder and pass --managed-dir. With this policy:
// policy/managed-mcp.json
{ "mcpServers": { "annotated": { "command": "node", "args": ["<clone>/tests/fixtures/annotated-server.mjs"] } } }
// policy/managed-settings.json
{
"managedMcpServers": {
"annotated": { "type": "http", "url": "https://mcp.example.com/annotated" },
"intranet": { "type": "http", "url": "http://intranet.example.com/mcp" },
"tenant": { "type": "http", "url": "https://${TENANT}.example.com/mcp" }
}
}
a project whose .mcp.json defines the unannotated fixture gives this report on stderr (absolute paths shortened to /demo):
$ claude-mcp-allow --managed-dir ../policy
note: annotated from /demo/policy/managed-settings.json is shadowed by the managed definition (/demo/policy/managed-mcp.json)
warning: intranet (managed, /demo/policy/managed-settings.json) skipped: managedMcpServers entries must define an HTTPS URL
warning: tenant (managed, /demo/policy/managed-settings.json) skipped: managedMcpServers entries must use literal values without ${VAR} references
warning: unannotated (project, .mcp.json) skipped: managed-mcp.json has exclusive control
annotated (managed, /demo/policy/managed-mcp.json) 7 tools: 2 allow, 5 ask
allow get_user readOnlyHint is true
allow list_items readOnlyHint is true
ask delete_item destructiveHint is true
ask update_item annotated but not marked read-only
ask read_and_wipe destructiveHint is true (readOnlyHint is also true; the destructive hint wins)
ask grant_access _meta["anthropic/requiresUserInteraction"] is true; Claude Code prompts on every call
ask get_status no annotations
The rules on stdout are then the two allow and five ask rules for annotated only. allowedMcpServers is not evaluated; a deniedMcpServers match would skip the server.
Tools from a plugin-bundled server get rules in the form Claude Code uses for them, mcp__plugin_<plugin>_<server>__<tool>, with any character outside A-Z, a-z, 0-9, _ and - replaced by _.
oauth key, and remote servers that answer 401 or 403: Claude Code completes the sign-in through /mcp; this tool does not run OAuth flows. Each one is named in a warning.ws and sdk transports, and entries with neither command nor url.${user_config.*}, which this tool does not resolve.deniedMcpServers entry by name, by command and arguments, or by a URL without *; a serverUrl entry containing * is not evaluated yet and is named in a warning.--timeout is reported as failed, and the rest continue.--write puts in the fileRules are appended to permissions.allow and permissions.ask, and a sibling key records what was generated:
"claudeMcpAllow": {
"generatedBy": "claude-mcp-allow 0.4.0",
"generatedAt": "2026-10-03T22:38:08.944Z",
"heuristic": false,
"servers": {
"annotated": {
"get_user": "allow:cd6fc3f14d1a476831d8b3ad5283d55120abdc84f8fdff6a2b040e092f81afcc",
"delete_item": "ask:8c2fe572a84b307ba8332b68b40a6b8ccb02f58b7b81e61943e7ac03862756fd"
}
}
}
Each value is the decision and the SHA-256 of the tool's annotations plus its requiresUserInteraction flag (the description and schema are not hashed, so wording changes do not count as drift). On the next --write, rules recorded here are replaced by the new result; rules you wrote by hand stay, and the tool warns when a hand-written allow covers a tool it classified ask (the ask rule is evaluated first and prompts). An allow rule for a tool that appears in permissions.deny is not added, because deny rules win. The published settings schema accepts additional top-level keys, so the marker does not make the file invalid.
--check reads this key, reconnects to each recorded server (or the ones you name with --server) and reports drift when a hash differs, a tool is no longer listed, an allowed tool would not be allowed now, a recorded server is no longer configured, or a server cannot be reached; it reports info for new tools that are not covered yet and for hand-written allow rules on ask tools. Exit 1 on any drift.
A project with two fixture servers from this repository in .mcp.json (one annotated, one not) and a user-scope server that needs OAuth, from npx @basitalisandhu/claude-mcp-allow with no flags:
annotated (project, .mcp.json) 7 tools: 2 allow, 5 ask
allow get_user readOnlyHint is true
allow list_items readOnlyHint is true
ask delete_item destructiveHint is true
ask update_item annotated but not marked read-only
ask read_and_wipe destructiveHint is true (readOnlyHint is also true; the destructive hint wins)
ask grant_access _meta["anthropic/requiresUserInteraction"] is true; Claude Code prompts on every call
ask get_status no annotations
unannotated (project, .mcp.json) 15 tools: 0 allow, 15 ask
ask get_weather no annotations
ask list_files no annotations
ask search_docs no annotations
ask fetch-url no annotations
ask lookup_user no annotations
ask count_rows no annotations
ask show-status no annotations
ask query_db no annotations
ask getdata no annotations
ask send_email no annotations
ask delete_all no annotations
ask update_record no annotations
ask run_job no annotations
ask get_consent _meta["anthropic/requiresUserInteraction"] is true; Claude Code prompts on every call
ask get_string_flag no annotations
docs (user, ~/.claude.json) skipped: entry has oauth; sign in with /mcp in Claude Code, this tool does not run OAuth flows
{
"permissions": {
"allow": [
"mcp__annotated__get_user",
"mcp__annotated__list_items"
],
"ask": [
"mcp__annotated__delete_item",
"mcp__annotated__get_status",
"mcp__annotated__grant_access",
"mcp__annotated__read_and_wipe",
"mcp__annotated__update_item",
"mcp__unannotated__count_rows",
"mcp__unannotated__delete_all",
"mcp__unannotated__fetch-url",
"mcp__unannotated__get_consent",
"mcp__unannotated__get_string_flag",
"mcp__unannotated__get_weather",
"mcp__unannotated__getdata",
"mcp__unannotated__list_files",
"mcp__unannotated__lookup_user",
"mcp__unannotated__query_db",
"mcp__unannotated__run_job",
"mcp__unannotated__search_docs",
"mcp__unannotated__send_email",
"mcp__unannotated__show-status",
"mcp__unannotated__update_record"
]
}
}
With --heuristic --server unannotated, read verbs with a separator are allowed; getdata has no separator, send_email is not a read verb, and get_consent requires a person:
unannotated (project, .mcp.json) 15 tools: 9 allow, 6 ask
allow get_weather no annotations; name starts with a read verb (--heuristic)
allow list_files no annotations; name starts with a read verb (--heuristic)
allow search_docs no annotations; name starts with a read verb (--heuristic)
allow fetch-url no annotations; name starts with a read verb (--heuristic)
allow lookup_user no annotations; name starts with a read verb (--heuristic)
allow count_rows no annotations; name starts with a read verb (--heuristic)
allow show-status no annotations; name starts with a read verb (--heuristic)
allow query_db no annotations; name starts with a read verb (--heuristic)
ask getdata no annotations; name is not a read verb
ask send_email no annotations; name is not a read verb
ask delete_all no annotations; name is not a read verb
ask update_record no annotations; name is not a read verb
ask run_job no annotations; name is not a read verb
ask get_consent _meta["anthropic/requiresUserInteraction"] is true; Claude Code prompts on every call
allow get_string_flag no annotations; name starts with a read verb (--heuristic)
--write --diff against a .claude/settings.local.json that already allowed Bash(npm test) (the proposed block is printed first and is omitted here):
Diff against .claude/settings.local.json:
+ allow mcp__annotated__get_user
+ allow mcp__annotated__list_items
+ ask mcp__annotated__delete_item
+ ask mcp__annotated__get_status
+ ask mcp__annotated__grant_access
+ ask mcp__annotated__read_and_wipe
+ ask mcp__annotated__update_item
+ ask mcp__unannotated__count_rows
...
+ ask mcp__unannotated__update_record
wrote .claude/settings.local.json: +2/-0 allow, +20/-0 ask
The file afterwards starts with the rule that was already there:
{
"permissions": {
"allow": [
"Bash(npm test)",
"mcp__annotated__get_user",
"mcp__annotated__list_items"
],
"ask": [
"mcp__annotated__delete_item",
--check straight away, then again after the annotated server was changed to report readOnlyHint: false for get_user (the fixture does this when FIXTURE_DRIFT=flip is set):
$ npx @basitalisandhu/claude-mcp-allow --check
ok: no drift against .claude/settings.local.json
$ FIXTURE_DRIFT=flip npx @basitalisandhu/claude-mcp-allow --check
drift annotated: annotations of get_user changed; it is allowed but is no longer read-only (annotated but not marked read-only)
1 drift finding(s) against .claude/settings.local.json
The second run exits 1, which is what you want from a CI job or a pre-commit hook that guards a committed .claude/settings.json.
Each message below is real output. Configuration and usage errors exit with code 2; drift found by --check exits with code 1.
| You see | What it means | Fix |
|---|---|---|
npm error code E401 from npx @basitalisandhu/claude-mcp-allow | GitHub Packages asks for a token even for public packages. | Use npx github:basitalisandhu/claude-mcp-allow, which needs none, or the container image. |
claude-mcp-allow: no MCP servers configured for <dir> | No .mcp.json, ~/.claude.json entry, plugin or managed server applies to that directory. | Run it from the project root or pass --cwd <dir>; see Where servers are read from. |
warning: <dir>/.mcp.json: invalid JSON (...); ignored | The file has a trailing comma, a comment or a cut-off end. | Fix the JSON; Claude Code would not load those servers either. |
x (project, .mcp.json) failed: spawn no-such-cmd-zz ENOENT | The server's command is not on your PATH. | Install the server, or use the full path in command. Its tools get no rules until it lists them. |
A server shows failed: ... timed out | It did not answer tools/list within --timeout (20 seconds by default). | Start it once by hand to see why, or raise --timeout. |
claude-mcp-allow: .claude/settings.local.json has no claudeMcpAllow marker; run claude-mcp-allow --write first | --check compares with rules this tool wrote; there are none in that file. | Run --write with the same --scope first. |
claude-mcp-allow: --scope must be local, project or user (got "team") | Only the three settings files are written. | Pick one; local is the default and is not committed. |
--check reports drift right after a server upgrade | The server changed an annotation, removed a tool, or an allowed tool is no longer read-only. | Review the change, then run --write again to record it. |
Why does Claude Code still prompt for a tool that has an allow rule?
Four documented reasons. A tool whose tools/list entry sets _meta["anthropic/requiresUserInteraction"] to true prompts on every call, in every mode, and allow rules do not skip it; this tool puts such tools in ask so the file matches the behaviour. Rules are evaluated deny, then ask, then allow, first match wins, so an ask rule from any settings file (yours, the project's, or your organisation's) prompts even when a more specific allow rule also matches. permissions.allow rules in a project's .claude/settings.json only apply after you accept the workspace trust dialog for that folder. And when your organisation has set a claude.ai connector tool to ask, allow rules for that tool do not take effect. Run /permissions in Claude Code to see every rule and the file it comes from.
Can I trust readOnlyHint?
Only as far as you trust the server. Annotations are declared by the server and nothing checks them; a server can call a tool read-only and still write. That is why the default never allows a tool that is not marked read-only, why --heuristic is a separate choice, and why --check exists: once you have reviewed and saved the rules, a server that later changes a tool's annotations, drops a tool, or starts claiming something different fails the check instead of silently widening what runs without a prompt. Treat the generated allow list the way you would treat a dependency lockfile: review it when it changes.
Why one rule per tool instead of mcp__<server>__*?
The wildcard form is valid and Claude Code accepts it after a literal server prefix, but it allows every tool the server has now and every tool it adds later, including a destructive one shipped in a future version. One exact rule per tool keeps the allowlist readable in review, lets --diff show exactly which capability a change adds, and lets --check reason about each tool's own annotations. Unanchored globs such as mcp__* are skipped by Claude Code with a warning and never auto-approve anything, so the tool never writes them either.
Which settings file should the rules go in, and can I commit it?
local (the default) writes .claude/settings.local.json, your personal file for this project, which Claude Code keeps out of git when it creates the file; if you created it by hand, add it to .gitignore yourself. project writes .claude/settings.json, which you commit so teammates get the same rules, and which is the file to guard with --check in CI. user writes ~/.claude/settings.json, which applies to every project on your machine; rules for project-scoped servers rarely belong there. Whatever the file, a deny rule from any settings file still wins over an allow rule from another.
npm install
npm test # builds, then runs vitest against the fixture servers in a temporary home directory
npm run typecheck
The suite starts the two stdio fixtures (tests/fixtures/annotated-server.mjs, tests/fixtures/unannotated-server.mjs) and a Streamable HTTP fixture that requires a bearer token, inside a fake home directory with servers in every scope, and compares output against the golden files in tests/golden/. Regenerate the goldens with UPDATE_GOLDEN=1 npm test and review the diff. See CONTRIBUTING.md, docs/good-first-issues.md and SECURITY.md.
More tools by the same author: https://github.com/basitalisandhu
tools/list for schema dialect, annotation and naming problems before a client rejects it.MIT, see LICENSE. Copyright 2026 Muhammad Basit Ali.
managed-mcp.jsonmanaged-settings.json<dir>-h, --help, -V, --version | The usual. |