Enterprise MCP Policy
- Choose the right enterprise MCP restriction pattern — from a fixed managed server set to a soft allowlist or denylist-only approach
- Deploy and validate managed-mcp.json for exclusive server control, including how to disable MCP entirely across an organization
- Configure allowedMcpServers and deniedMcpServers with the correct entry type for each server, and understand why serverName is not a security control
Why individual developer choice is not always the right default
By default, any developer running Claude Code can connect any MCP server they choose. For many teams that is fine — developers are trusted, servers are reviewed, and the flexibility is valuable. But in regulated industries, security-sensitive environments, or large organizations where Claude Code runs on machines that also access production systems, unlimited MCP connectivity is a risk surface that needs management.
Claude Code provides a range of enterprise controls, from soft policies that developers can extend to hard enforcement where only pre-approved servers load. Choosing the right level requires understanding what each mechanism enforces and where it cannot be overridden.
Five patterns for different control requirements
managed-mcp.json: exclusive control
Deploying a managed-mcp.json file gives the organization exclusive control over every MCP server on that machine. When this file is present, users cannot add, modify, or use any server outside it. Plugin-provided servers are suppressed. Claude.ai connectors are suppressed unless you set allowAllClaudeAiMcps: true in a managed settings source.
The file lives at a platform-specific system path that requires administrator privileges to write:
Deploy it with MDM (Jamf, Intune), Group Policy, Ansible, or any fleet management tool that can write to system paths. The file cannot be delivered through server-managed settings — it requires direct filesystem access with administrator privileges.
Validate the deployment on a managed machine with two checks: claude mcp list should show only the servers in managed-mcp.json, and claude mcp add --transport http test https://example.com/mcp should fail immediately with "Cannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers."
To disable MCP entirely, deploy a managed-mcp.json with an empty server map:
{"mcpServers": {}}
allowedMcpServers and deniedMcpServers
Allowlists and denylists filter which configured servers are allowed to load. They are not a registry — a server must still be added by a user, plugin, or managed-mcp.json before the filter applies. Each entry in the lists identifies a server by URL pattern, command, or name:
- serverUrl: matches remote servers by URL, with * wildcards. Hostname matching is case-insensitive; path matching is case-sensitive.
- serverCommand: matches stdio servers by their exact command and argument list in order.
["npx", "-y", "server"]does not match["npx", "server"]— every argument must match exactly. - serverName: matches by the user-assigned label. This is not a security control. Any user can name any server anything, so a serverName entry does not enforce which server actually runs. Use serverUrl or serverCommand for security enforcement.
How a server is evaluated before loading
Before loading any server — including one from managed-mcp.json — Claude Code runs three checks in order:
- Merge the lists. Allowlist and denylist entries from all settings sources combine. When allowManagedMcpServersOnly is true, only the managed allowlist is kept; the denylist always merges from every source.
- Check the denylist. A server matching any denylist entry is blocked. Nothing overrides a denylist match — not the allowlist, not managed-mcp.json.
- Check the allowlist. If allowedMcpServers is unset, every server that passed the denylist loads. If set, remote servers must match a serverUrl entry, and stdio servers must match a serverCommand entry. A serverName match is only considered when no stricter entries exist for that transport type.
How restrictions appear to users
When a server is blocked at add time, users see an explicit error message explaining why. When a previously configured server is blocked by a new policy deployment, it silently disappears from /mcp and claude mcp list with no explanation. Users get no signal that policy is the reason their server is gone. Communicate which servers are blocked — and why — before rolling out a new restriction, so developers are not left debugging what appears to be a connection failure.
- managed-mcp.json gives organizations exclusive control over MCP servers — deploying it prevents users from adding any server outside the file, and plugin servers are suppressed unless explicitly included
- The file lives at a system path requiring administrator privileges and must be deployed by MDM or fleet management — it cannot be delivered through server-managed settings
- allowedMcpServers entries using serverUrl match by URL wildcard and serverCommand entries match by exact argument list — serverName is a label match and is not a security control because users can name any server anything
- The evaluation order is fixed: merge lists, then denylist, then allowlist — a denylist match always wins regardless of what the allowlist says
- When a previously configured server is blocked by a new policy, it silently disappears from /mcp with no explanation — communicate restrictions to affected developers before rolling out a change