Originally published at https://aicoding-guide.com.
Add an MCP server and Claude Code asks for approval every time one of its tools runs. Stopping for a read-only lookup on every call gets old fast.
In short: put mcp__<server> in permissions.allow to auto-approve every tool from that server, or mcp__<server>__<tool> for one tool. The catch is that an allow wildcard requires a literal mcp__<server>__ prefix — a pattern like mcp__* is skipped entirely.
For registering the servers themselves, see Adding MCP servers to Claude Code.
Key point
What you will learn
- The three rule shapes: whole server, wildcard, single tool
- Why allow wildcards need an anchor, and how deny and ask differ
- That
mcp__rules with parentheses are silently skipped from settings files
The three rule shapes
The docs put it this way: MCP rules use the server name as configured in Claude Code, optionally followed by the name of a tool from that server.
| Rule | What it matches |
|---|---|
mcp__puppeteer |
Any tool provided by the puppeteer server |
mcp__puppeteer__* |
The same set, written as a wildcard |
mcp__puppeteer__puppeteer_navigate |
Only the puppeteer_navigate tool from that server |
In settings.json:
{
"permissions": {
"allow": [
"mcp__puppeteer",
"mcp__github__get_issue",
"mcp__github__list_pull_requests"
]
}
}
The server name is whatever you passed to claude mcp add, or the key in .mcp.json. The separator is two underscores, both between mcp and the server and between the server and the tool.
Glossary
Server name: the name you chose inclaude mcp add <name> .... Rename the server and every rule for it changes too, so when a team shares.claude/settings.json, agree on the names in.mcp.jsonfirst.
Allow wildcards need an anchor
This is the part that catches people. The docs state that allow rules accept tool-name globs only after a literal mcp__<server>__ prefix, and that the server segment must be glob-free so the rule names a specific server you configured.
| Pattern | Result in allow
|
|---|---|
mcp__puppeteer__* |
Works. Matches every tool from puppeteer
|
mcp__github__get_* |
Works. Matches that server's get_ tools |
mcp__* |
Skipped with a warning; auto-approves nothing |
* / B*
|
Skipped, same as above |
So there is no way to say "allow all MCP tools" in one allow rule. List each server.
{
"permissions": {
"allow": [
"mcp__puppeteer__*",
"mcp__github__get_*",
"mcp__linear__*"
]
}
}
Deny and ask follow different rules
Deny and ask rules do accept globs in the tool-name position. The pattern must match the full tool name: "*" matches every tool, and "mcp__*" matches every MCP tool across all servers.
{
"permissions": {
"deny": [
"mcp__*"
]
}
}
Rules are evaluated in order: deny, then ask, then allow. The first match in that order determines the outcome, and rule specificity doesn't change it. A broad deny wins over a narrower allow, and an allow rule can't carve an exception out of a deny.
| Goal | Where and what |
|---|---|
| Allow a server's tools wholesale |
allow: mcp__<server>
|
| Block one dangerous tool |
deny: mcp__<server>__<tool>
|
| Block all MCP tools |
deny: mcp__*
|
| Prompt every time |
ask: any of the same patterns |
A deny rule that names a bare tool removes the tool from Claude's context, so Claude never sees it. A scoped rule leaves the tool available and blocks matching calls when Claude attempts them.
One thing to watch: a deny or ask rule whose tool name matches no known tool normally produces a startup warning to catch typos, but tool names containing _ or * are exempt from that check. Every mcp__ name qualifies, so a misspelled server name fails silently.
Where rules live, and what gets ignored
Write rules in settings.json and inspect them with /permissions. The dialog lists every rule and the settings.json file it came from. You can open it while Claude is working: adding or removing a rule applies from Claude's next tool call in the same turn (v2.1.234 or later). For the files and their precedence, see Configuring permissions in Claude Code's settings.json, and for driving the dialog, The /permissions command.
mcp__ rules with parentheses are skipped
TheBash(npm run build)style of narrowing by argument does not work for MCP tools. When Claude Code loads a settings file, it skips anymcp__rule that has parentheses. Skipped rules are listed in the invalid-settings dialog when an interactive session starts, and inclaude doctoroutput. To match a parameter on an MCP tool, pass a deny rule with--disallowedToolsinstead.
There is a second case where an allow rule has no effect. If your organization has set a claude.ai connector tool to ask and that setting reaches your session, allow rules for that tool don't apply: Claude Code prompts on every call, even in auto and bypassPermissions modes. In dontAsk mode, which never prompts, it denies the call instead. Tools from connectors Claude Code fetches itself appear as mcp__claude_ai_<server>__<tool>.
Summary
-
allowtakesmcp__<server>for a whole server andmcp__<server>__<tool>for one tool - An allow wildcard only works after
mcp__<server>__;mcp__*and*are skipped with a warning - Deny and ask accept full tool-name globs, so
deny: ["mcp__*"]blocks every MCP tool - Evaluation is deny → ask → allow, first match wins, regardless of specificity
- Parenthesised
mcp__rules are skipped from settings files; use--disallowedToolsto match a parameter
Top comments (0)