Skip to content

Claude Code permissions for MCP tools

On this page

Claude Code decides what it may do without asking through two mechanisms: a permission mode for the session, and allow, ask and deny rules in settings.json files or --allowedTools and --disallowedTools flags. MCP tools use the same rules, named mcp__<server>__<tool>. This guide explains how those pieces work according to Anthropic's Claude Code documentation as of October 2026, where they stop helping a team, and how to pair them with gateway-side rules.

Claude Code changes quickly. Treat the official docs as the reference and this page as a map.

Permission modes

The mode sets the baseline for what prompts you. You can switch modes during a session or set the starting mode with defaultMode in settings. The documented modes are:

ModeWhat it does
defaultPrompts on first use of each tool. Shown as Manual in the CLI and IDE extensions
acceptEditsAccepts file edits and common filesystem commands inside the working directories without asking
planExplores and reads but doesn't edit your source files
autoRuns without routine prompts; a background classifier checks actions such as shell commands and network requests first
dontAskDenies anything that would otherwise prompt. Pre-approved tools and actions that need no approval still run
bypassPermissionsSkips permission prompts, with a few exceptions. Anthropic recommends it only in isolated environments such as containers or VMs

Since Claude Code v2.1.283, eligible interactive terminal and VS Code sessions with no defaultMode set start in auto. They start in Manual instead when the organization has the HIPAA configuration applied, when a settings file sets disableAutoMode, or when auto mode isn't available to the session, for example because the model doesn't support it. claude -p runs start in Manual.

Two settings keep the riskier modes off: permissions.disableBypassPermissionsMode and permissions.disableAutoMode, each set to "disable". They work from any settings file but are most useful in managed settings, where users can't override them. Also note that auto and bypassPermissions don't take effect as a defaultMode from project or local settings; a repository can't switch them on for you.

bypassPermissions is what --dangerously-skip-permissions turns on. What --dangerously-skip-permissions skips covers the exceptions that still apply, Anthropic's container advice and what it means for MCP tools.

Allow, ask and deny rules

Rules live under permissions in a settings file:

  • allow lets Claude Code use a tool without asking.
  • ask prompts every time.
  • deny blocks the tool.

Claude Code checks deny first, then ask, then allow, and the first match wins. Specificity doesn't change that order, so a broad deny beats a narrow allow, and an ask rule prompts even when an allow rule also matches.

A deny rule that names a whole tool (no parentheses) removes the tool from Claude's context entirely, so Claude never sees it. A scoped rule such as Bash(rm *) leaves the tool available and blocks matching calls.

Rules are enforced by Claude Code, not by the model. Writing "never delete issues" in CLAUDE.md shapes what Claude tries; only rules, modes or hooks change what Claude Code permits.

You can view and edit rules with the /permissions command. Its dialog lists every rule together with the settings file it came from, which is the quickest way to find out why something is or isn't prompting.

Naming rules for MCP tools

MCP tools follow the documented naming pattern, where <server> is the name you gave the server when you added it:

RuleMatches
mcp__githubEvery tool from the server named github
mcp__github__*Every tool from that server (wildcard form)
mcp__github__get_*Tools from that server whose names start with get_
mcp__github__create_issueOne tool
mcp__*Every MCP tool from every server (deny and ask rules only)

A few details catch people out:

  • Allow wildcards need a literal server prefix. mcp__github__* works as an allow rule; mcp__* or * as an allow rule is skipped and approves nothing.
  • Server names are normalized. Characters such as . or spaces become _, so a server named my.server is mcp__my_server__*. Servers from plugins use a plugin-scoped name.
  • No argument matching in settings files. Claude Code skips any mcp__ rule written with parentheses. To deny an MCP tool call by parameter, the docs point to a deny rule passed with --disallowedTools instead.
  • Servers can force prompts. A server can mark a tool with _meta["anthropic/requiresUserInteraction"]: true. Claude Code then prompts on every call, even in auto or bypassPermissions, and allow rules don't skip it. In dontAsk the call is denied.

Here's a project file for a repository that uses a hypothetical issue-tracker server named tracker:

JSON
{
  "permissions": {
    "allow": ["mcp__tracker__search_issues", "mcp__tracker__get_issue"],
    "ask": ["mcp__tracker__create_issue", "mcp__tracker__add_comment"],
    "deny": ["mcp__tracker__delete_issue"]
  }
}

Search and read run without prompts, writes ask each time, and deletion is removed from Claude's context. The tool names are examples; check /mcp for the real ones.

Claude Code settings.json

Permission rules, the default mode and most other options live in a settings.json file. Claude Code reads it from four places, plus settings an organization delivers:

ScopeFileApplies to
User~/.claude/settings.jsonYou, in every project on this machine
Shared project.claude/settings.jsonEveryone who works in the repository, once committed
Project local.claude/settings.local.jsonYou, in this project only; kept out of git
Managedmanaged-settings.json, MDM policy or server-managed settingsEveryone your organization deploys it to

Installing Claude Code creates none of these. You write them yourself, or Claude Code writes ~/.claude/settings.json when you change certain /config options and .claude/settings.local.json when you choose "Yes, and don't ask again" on a prompt. The file-based managed settings live at /Library/Application Support/ClaudeCode/managed-settings.json on macOS, /etc/claude-code/managed-settings.json on Linux and WSL, and C:\Program Files\ClaudeCode\managed-settings.json on Windows.

MCP servers aren't configured in settings.json. Claude Code keeps them in ~/.claude.json (user and local scope) or a project .mcp.json; settings.json holds the rules for their tools.

Precedence

For single values, the highest level that sets a key usually wins. For a few security-sensitive keys, Claude Code honors a stricter value from a lower level even over managed settings. The order is:

  1. Managed settings
  2. Command line: a file or JSON string passed with --settings, for that session only
  3. Project local (.claude/settings.local.json)
  4. Shared project (.claude/settings.json)
  5. User (~/.claude/settings.json)

Permission lists behave differently from single values. permissions.allow and the other lists merge across files rather than replacing each other. Deny rules from any level beat allow rules from any level, so a user-level deny blocks a project-level allow and the reverse is also true. Nothing below managed settings can override a managed deny.

Settings files are strict JSON: a // comment or a trailing comma is an error that Claude Code reports at the next start. Run /status after saving to check which files loaded.

A complete permissions example

This is a shared project file for a repository that uses a GitHub server named github and a hypothetical issue tracker named tracker:

JSON
{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "defaultMode": "acceptEdits",
    "allow": [
      "Bash(npm run lint)",
      "Bash(npm run test *)",
      "mcp__github__get_*",
      "mcp__github__search_*",
      "mcp__tracker__search_issues",
      "mcp__tracker__get_issue"
    ],
    "ask": [
      "Bash(git push *)",
      "mcp__github__create_pull_request",
      "mcp__tracker__create_issue",
      "mcp__tracker__add_comment"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "mcp__github__delete_*",
      "mcp__tracker__delete_issue"
    ]
  }
}

What each part does:

  • $schema points to the published JSON schema, which gives autocomplete and validation in editors that support it. The schema can lag behind new releases.
  • defaultMode starts terminal sessions in acceptEdits. Terminal sessions honor project defaults except auto and bypassPermissions; the VS Code extension uses its own starting-mode precedence and doesn't read project settings for it.
  • allow lets lint, tests and GitHub's read tools run without prompts. mcp__github__get_* is valid because the server name before the wildcard is literal.
  • ask prompts for pushes and for MCP tools that create or comment.
  • deny blocks matching .env and .env.* paths through the built-in file tools and the file commands Claude Code recognizes in Bash, such as cat, and removes the matching delete tools from Claude's context, because they are named without parentheses. Read rules don't cover a script or other subprocess that opens files itself; that needs the OS-level sandbox.

The MCP tool names are examples. Run /mcp to see the real ones before writing rules. Remember that the allow entries in a committed project file wait until each developer trusts the folder, while ask and deny apply at once.

--allowedTools and --disallowedTools

The CLI flags add rules for one session without touching any file:

Shell
claude --allowedTools "mcp__tracker__search_issues" "Bash(npm test)" \
  --disallowedTools "mcp__tracker__delete_issue"
  • --allowedTools (or --allowed-tools) lists tools that run without prompting, using the same rule syntax as permissions.allow.
  • --disallowedTools (or --disallowed-tools) adds deny rules. A bare name removes the tool from context, and "mcp__*" removes every MCP tool.
  • --tools is different: it restricts which built-in tools exist at all, such as --tools "Bash,Edit,Read", and doesn't affect MCP tools.

The flags sit inside the same deny-first logic. A managed deny can't be overridden by --allowedTools, and --disallowedTools can add restrictions beyond managed settings. They are also the only place to match an MCP tool by parameter: Claude Code skips any mcp__ rule with parentheses in a settings file, but the docs allow such a deny rule through --disallowedTools.

For a scripted run, pair them with dontAsk. Calls that would require approval are denied unless pre-approved; actions that need no approval still run: claude -p "run the test suite" --permission-mode dontAsk --allowedTools "Bash(npm test)" "Read".

Trust and local approvals

Two more behaviors matter for teams:

  • Workspace trust gates project allows. permissions.allow rules in a committed .claude/settings.json apply only after each developer accepts the trust dialog for that folder. Deny and ask rules apply immediately, since they only restrict.
  • "Don't ask again" writes locally. Choosing "Yes, and don't ask again" in the CLI saves an allow rule to .claude/settings.local.json. That local allow doesn't outrank an ask rule from the project or managed settings.

Managed settings and server allowlists

Organizations can deploy managed settings through MDM, a managed-settings.json file in a system directory, or server-managed settings from the claude.ai admin console. Managed keys can't be overridden by users, apart from a short list of keys where Claude Code honors a stricter value from elsewhere.

For permissions, allowManagedPermissionRulesOnly makes managed settings the only source of permission rules. For MCP servers, managed MCP configuration offers several patterns: a fixed set of servers through managed-mcp.json, servers provided to everyone through managedMcpServers, or filtering with allowedMcpServers and deniedMcpServers. Adding allowManagedMcpServersOnly: true stops users broadening the server allowlist. Entries should match by serverUrl or serverCommand; the docs say plainly that serverName entries aren't a security control.

The MCP allowlist guide compares this with Cursor, Codex and VS Code.

Where Claude Code permissions fall short for teams

These are limits of local enforcement, not flaws in Claude Code.

Rules live on each machine. Even with a committed project file, each developer's user and local files add their own allows, and each machine resolves its own effective list. Managed settings close much of that gap, but they need someone to deploy and maintain them, and they only cover Claude Code.

Other apps don't share the rules. A developer who also uses Cursor or Codex against the same MCP server has a separate set of rules there, in a different format.

There's no central record of tool calls by default. Claude Code can export tool events through OpenTelemetry if you configure it, which is a real option for organizations that already run a collector. Without that, there's no shared answer to "which agent called which tool on Tuesday?"

Credentials stay in local config. Claude Code stores MCP server configuration in ~/.claude.json or a project .mcp.json. A static header token in that config sits on the developer's disk, and a deny rule in Claude Code does nothing to stop the same token being used elsewhere.

Approval is local to whoever is at the keyboard. An ask rule prompts the person running the session. For a solo developer that's right. For a team that wants a second person to approve a production change, it isn't.

Combine Claude Code rules with a gateway

A practical split is to let Claude Code govern local behavior and let a gateway govern access to remote MCP servers.

Claude Code handles the local side: shell commands, file edits, web fetches, the permission mode, and any MCP servers that must run locally over stdio.

The gateway handles the remote side: which upstream servers an agent can reach, which tools it may call, which ones need someone else's approval, the upstream credentials, and the shared activity log.

With Toolstile, you add one remote server to Claude Code:

Shell
claude mcp add --transport http toolstile 'https://mcp.toolstile.com/mcp'

Then run /mcp, sign in, and choose the workspace. The Claude Code session becomes an agent in that workspace with no access until you grant it. In Toolstile's Access page you set Allow, Require approval or Block per tool for that agent. A block wins over Require approval, which wins over Allow or full access. Claude Code only receives the tools you allowed, and Toolstile checks every call again before forwarding it.

On the Claude Code side, all of those tools sit under the server name you chose, so rules look like mcp__toolstile__*. The tool names Toolstile exposes are generated identifiers rather than the upstream names, which makes per-tool rules awkward to write in Claude Code. Write per-tool decisions in Toolstile and keep Claude Code's rules at the server level. Two reasonable choices:

JSON
{
  "permissions": {
    "ask": ["mcp__toolstile"]
  }
}

This makes Claude Code confirm every gateway call locally as well, which suits sensitive repositories. If Toolstile's grants are already narrow and risky tools require approval there, you can use "allow": ["mcp__toolstile__*"] instead, so routine reads don't prompt twice.

When Claude calls a tool that requires approval in Toolstile, the call returns an approval-required result with a review link instead of running. In a team workspace, an owner or admin reviews the exact arguments and approves once; in a personal workspace, the connection owner does. Claude then retries the identical call. Changed arguments need a new approval, and a call waiting for approval doesn't count toward the workspace's operations.

The two layers are independent. Loosening local rules doesn't reach a tool Toolstile blocks, and revoking the agent in Toolstile cuts off its MCP access without touching anyone's laptop.

Quick checklist

  • Pick a default mode deliberately. Disable bypassPermissions (and auto, if you don't use it) in managed settings for company machines, and keep --dangerously-skip-permissions to containers.
  • Commit a project .claude/settings.json with deny and ask rules for risky MCP tools. Remember that allow rules there wait for workspace trust.
  • Use /permissions to see which file a rule came from before debugging a prompt.
  • Match MCP servers by URL or command in managed allowlists, never by name alone.
  • Keep upstream tokens out of .mcp.json and other files that end up in repositories.
  • Put per-tool access for shared remote servers in a gateway, where it applies to every app and is logged in one place.
  • Require approval at the gateway for tools that change shared state, so someone other than the requester can review them.

To set up the gateway side, follow getting started and the Claude Code client steps. Permissions covers Toolstile's rules in full, and what an MCP gateway is explains the model behind it.