DEV Community

aicoding-guide
aicoding-guide

Posted on Originally published at aicoding-guide.com

Auto-approve MCP tools in Claude Code permissions: the mcp__ syntax and its wildcard limits

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"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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 in claude 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.json first.

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__*"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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__*"
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

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
The Bash(npm run build) style of narrowing by argument does not work for MCP tools. When Claude Code loads a settings file, it skips any mcp__ rule that has parentheses. Skipped rules are listed in the invalid-settings dialog when an interactive session starts, and in claude doctor output. To match a parameter on an MCP tool, pass a deny rule with --disallowedTools instead.

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

  • allow takes mcp__<server> for a whole server and mcp__<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 --disallowedTools to match a parameter

Top comments (0)