- Choose a pattern that matches how much control you need
- Deploy a fixed server set with
managed-mcp.json, including how to disable MCP entirely - Provide servers through managed settings while users keep their own
- Control servers with allowlists and denylists
- Tell users what to expect when a restriction blocks a server
- Monitor which servers your organization actually uses
The Security page covers the MCP threat model and how to evaluate a server before approving it. Decide what to enforce covers MCP restrictions alongside the other administrative controls.
Choose a pattern
Claude Code supports a range of restriction levels. Each pattern uses one or more of the mechanisms covered below:managed-mcp.json for deploying a fixed set, the managedMcpServers managed setting for providing servers alongside the ones users add, and allowedMcpServers/deniedMcpServers for filtering what users configure.
Claude Code doesn’t have a built-in MCP server registry that users can browse and install from. For the approved-catalog pattern, share the approved list and its
claude mcp add commands somewhere your users will find them, such as an internal wiki, or distribute the servers as plugins through a managed plugin marketplace so users can browse and install them from /plugin.Exclusive control with managed-mcp.json
If you deploy amanaged-mcp.json file, Claude Code loads only the servers that file defines, the servers you provide through managedMcpServers, plus any in-process servers the app that started the session registers, such as the VS Code extension’s own server or the connectors the desktop app delivers. Users can’t add, modify, or use any other MCP servers, including plugin-provided servers and servers passed with the --mcp-config CLI flag. The file also suppresses the claude.ai connectors Claude Code fetches itself unless you allow them alongside the managed set.
Deploy managed-mcp.json
managed-mcp.json is a standalone file, so it cannot be delivered through server-managed settings. To deliver servers through managed settings instead, without exclusive control, use managedMcpServers.
Any process that can write to a system path with administrator privileges can deploy the file. Across a fleet, that’s usually through device management tooling, such as Jamf or a configuration profile on macOS, Group Policy or Intune on Windows, or your fleet management of choice on Linux. Claude Code looks for the file at one of these paths:
The file uses the same format as a project
.mcp.json file:
Authenticate with per-user credentials
Any user on the machine can read this file, so don’t store API keys or other credentials inenv blocks. Pass per-user credentials with one of these instead:
${VAR}expansion to read secrets from each user’s environment.- OAuth or per-user headers so each user authenticates as themselves.
headersHelperto generate credentials at connection time.
Servers passed with --mcp-config or --strict-mcp-config
When a session receives servers through --mcp-config while managed-mcp.json is deployed, what the user sees differs between a workstation and a cloud session:
- On a workstation, Claude Code exits at startup with
You cannot dynamically configure MCP servers when an enterprise MCP config is present. - In cloud sessions on a host where the file is deployed, such as a self-hosted runner, Claude Code starts with the managed servers only and skips the claude.ai connectors and other servers the cloud host delivers through
--mcp-config. Nothing in the session tells the user which servers were left out. Claude Code names them in a warning on its stderr, which a self-hosted runner records at thedebuglog level.
--strict-mcp-config, Claude Code exits at startup on a workstation and in a cloud session alike, because that flag asks to replace the managed set.
How allowlists and denylists apply to the managed set
The denylist can further filter the servers inmanaged-mcp.json:
deniedMcpServersapplies to managed servers too, so a managed server that matches an entry won’t load.- A user’s own
deniedMcpServersmerges in from their settings, so users can block a managed server for themselves.
allowedMcpServers doesn’t apply to the servers in managed-mcp.json, with one exception: Claude Code still checks a server whose definition uses ${VAR} expansion against the allowlist, because that server’s effective configuration comes from each user’s environment rather than from the file alone. Before v2.1.259, every managed server had to pass the allowlist whenever one was set. See How a server is evaluated for which fields trigger the ${VAR} check and the full order of checks.
If you used allowedMcpServers to keep some of your own managed-mcp.json servers from loading, those servers start loading on each user’s first launch of v2.1.259 or later unless they use ${VAR} expansion, with no prompt or notice: only deniedMcpServers still subtracts from those servers. Add denylist entries for them, or deploy a separate managed-mcp.json per group, before your users upgrade.
Validate the configuration
To confirm the file is in effect, run two checks on a managed machine:claude mcp listshows only the servers inmanaged-mcp.json, plus any you provide throughmanagedMcpServers. If a user’s own servers still appear, the file isn’t being read; check the path and permissions.claude mcp add --transport http test https://example.com/mcpfails withCannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers. The URL doesn’t need to be a real server, since the policy check rejects the command before anything is contacted.
Disable MCP entirely
Deploy amanaged-mcp.json containing an empty server map to block every MCP server apart from in-process servers the app that started the session registers:
claude mcp add fails with the enterprise-policy error above. Servers users had previously configured stop loading the next time they start a session, with no warning that policy is the reason. Servers you provide through managedMcpServers still load under an empty map, so leave that key unset as well to disable MCP completely.
Allow claude.ai connectors alongside the managed set
By default, deployingmanaged-mcp.json suppresses the claude.ai connectors Claude Code fetches itself, including connectors an administrator configured for the organization in the claude.ai admin console. To load those connectors alongside the servers in managed-mcp.json, set "allowAllClaudeAiMcps": true in a managed settings source.
With the setting enabled, Claude Code loads the same claude.ai connectors it would load if managed-mcp.json weren’t deployed. Allowlists and denylists still apply to those connectors, so you can block specific ones with deniedMcpServers. The setting affects only the claude.ai connectors Claude Code fetches itself; plugin-provided servers stay suppressed.
Cloud sessions and the desktop app’s local and SSH sessions receive connectors another way, described in How connectors reach Claude Code. A managed-mcp.json on the host that runs a cloud session, such as a self-hosted runner host, suppresses that session’s connectors whether or not you set allowAllClaudeAiMcps. No managed-mcp.json reaches the connectors the desktop app delivers to its local and SSH sessions.
Claude Code reads allowAllClaudeAiMcps only from admin-controlled policy tiers: server-managed settings, an MDM-deployed plist or HKLM registry key, or a system managed-settings.json file. Placing it in user or project settings has no effect, so users cannot re-enable connectors that exclusive control suppressed.
Provide servers through managed settings
To give every user a set of remote MCP servers without taking exclusive control of MCP, list them undermanagedMcpServers in a managed settings source: server-managed settings, a Claude apps gateway policy, an MDM profile or registry policy, or managed-settings.json. Users keep the servers they add themselves and receive yours in addition. Requires Claude Code v2.1.259 or later. Earlier clients ignore the key.
The value is an object keyed by server name. Each entry has the same shape as an HTTP or SSE server in a project .mcp.json file, including the optional headers and oauth members described in Authenticate with remote MCP servers. This example provides a search server that each user signs in to with OAuth, and a records server that sends a header your organization issues:
headers out and let each user sign in with OAuth.
What an entry can contain
Claude Code loads an entry only when it passes every check below. It drops an entry that fails one, records a notice you can read with/status, and still loads the other entries:
typeishttporsse. As in.mcp.json,streamable-httpis accepted as an alias forhttp.urlis anhttps://URL. Claude Code refuses a plainhttp://URL, including one that points atlocalhost.- The entry has no
command,args,env, orheadersHelpermember, so a managed settings document never names a program to run on a user’s machine. - No value contains a
${VAR}reference. Claude Code doesn’t expand environment variables in these entries, so write literal values. - The server name contains only letters, numbers, hyphens, and underscores, and no key or value contains control or invisible formatting characters.
How provided servers load
These rules decide what loads when a provided server overlaps with another server definition or with another setting on this page:- A provided server takes precedence over a server with the same name in local, project, or user scope, and over a plugin server or claude.ai connector that points at the same URL.
- If you also deploy
managed-mcp.json, Claude Code loads its servers and the provided servers together, and the file’s entry takes precedence when both define a name. - Provided servers keep loading when
strictPluginOnlyCustomizationlocks themcpsurface. deniedMcpServersapplies to provided servers, including entries from a user’s own settings, so a user can block one for themselves. Provided servers need noallowedMcpServersentry.
managed-mcp.json, the per-run flags keep their meaning:
- A server a user passes with
--mcp-configunder the same name replaces the provided one for that run and is checked againstallowedMcpServers. --strict-mcp-configleaves provided servers out along with every other configured server.
managed-mcp.json deployed, both flags behave as Exclusive control with managed-mcp.json describes.
What users can see and change
Users can’t edit or remove a provided server:claude mcp removereports that the server is provided by the organization.- When you haven’t also deployed
managed-mcp.json, an entry a user adds under the same name is saved but not used while yours is present. - Users can still turn a provided server off for themselves in
/mcp, which lists provided servers under Managed MCPs.
claude mcp get and /mcp show a provided server’s URL as its host only, for example https://mcp.example.com/…, and claude mcp get shows its header names without their values.
Where managedMcpServers applies
Claude Code reads managedMcpServers from the managed source it selects under How Claude Code combines managed sources. When that source sets managedSourcesBehavior to "merge", Claude Code provides the servers from every admin source instead, and when two sources define the same name, the higher-ranked source’s entry applies whole. It never reads the key from the user-writable HKCU registry, from parent settings an embedding host supplies, or from user, project, or local settings files, where it drops the key with a warning.
Claude Code doesn’t read the key in the Claude Desktop app’s Code tab on a third-party deployment or in the app’s Cowork sessions, because Claude Desktop supplies and locks those sessions’ MCP servers itself. /status and claude doctor say so when your managed settings carry the key there.
When provided servers connect
WhenmanagedMcpServers arrives through server-managed settings, its timing follows Fetch and caching behavior:
- On a machine with cached settings, Claude Code withholds the cached copy of this key until the server confirms the settings for the session, and waits for that confirmation before it loads MCP servers. If the confirmation fails, the session continues without the provided servers and
/statussays they are withheld. - On a machine’s first launch, with nothing cached yet, an interactive session that starts before the settings arrive connects the provided servers as soon as they do, and a
claude -prun that has already started can finish without them.
- Add a server: Claude Code connects it when the updated settings arrive, without a restart.
- Change a server’s entry: those sessions reconnect to it with the new definition.
- Remove a server: a running interactive session disconnects it once it reads the changed settings. A non-interactive (
-p) run keeps it until it ends.
Policy-based control with allowlists and denylists
Allowlists and denylists filter which configured servers are allowed to load. They aren’t a registry: a server still has to be added by a user, a plugin, or your organization before either list applies to it. Servers your organization delivers throughmanagedMcpServers load without an allowlist entry, and How a server is evaluated covers managed-mcp.json servers. The denylist applies to every server regardless of where it came from, other than in-process type: "sdk" entries.
To deploy servers to users, use managed-mcp.json or managedMcpServers. Both lists also filter servers passed with the --mcp-config CLI flag, other than in-process type: "sdk" entries; --strict-mcp-config limits which configuration files load and doesn’t bypass either list.
To make the allowlist authoritative, set allowedMcpServers and allowManagedMcpServersOnly: true together in a managed settings source, such as server-managed settings or a deployed managed-settings.json file. Restrict the allowlist to managed settings only shows the configuration. Without allowManagedMcpServersOnly, allowlists from every settings scope merge, including a user’s own ~/.claude/settings.json, so a user can broaden what your allowlist permits. Denylists merge from every scope regardless.
allowManagedMcpServersOnly is separate from allowManagedPermissionRulesOnly, which locks down permission rules only. Setting that flag does not enforce the MCP allowlist.Match servers by URL, command, or name
allowedMcpServers and deniedMcpServers are lists of entries. Each entry is an object with a single key that identifies servers by their URL, their command, or their name:
Leaving
allowedMcpServers unset is different from setting it to an empty array:
See Invalid entries in managed settings for what happens when an entry fails schema validation.
The
serverName validation differs between the two lists:
- In
deniedMcpServers,serverNameaccepts any non-empty string, so you can block claude.ai connectors by their display name. For example,{ "serverName": "claude.ai Slack" }blocks the Slack connector. Prefer aserverUrlentry when you need the deny to be robust to renames, or when a connector name collides and gains a(N)suffix. - In
allowedMcpServers,serverNameis limited to letters, numbers, hyphens, and underscores. UseserverUrlto allowlist a claude.ai connector Claude Code fetches itself; for connectors a cloud host delivers to self-hosted sessions, use the entries listed under Connector traffic leaves your network instead.
disableClaudeAiConnectors.
How a server is evaluated
Before loading a server, including one frommanaged-mcp.json, Claude Code runs the three checks below in order. It runs them again when a user reconnects a server or turns a disabled one back on in /mcp. In-process type: "sdk" servers, which the app that started the session registers, skip all three.
-
Merge the lists. Allowlist and denylist entries from every settings scope combine into one allowlist and one denylist, with the managed scope’s lists coming from the managed source or sources Claude Code applies. When
allowManagedMcpServersOnlyistrue, only the managed allowlist is kept; the denylist always merges from every scope. - Check the denylist. A server that matches any denylist entry, by URL, command, or name, is blocked. Nothing overrides a denylist match.
-
Check the allowlist. If
allowedMcpServersisn’t set anywhere, every server that passed the denylist loads. If it is set, what the server must match depends on its type, shown in the table below. The organization’s own servers skip this check: everymanagedMcpServersentry, and anymanaged-mcp.jsonentry whose values use no${VAR}expansion. Built-in servers skip it too, such as Claude in Chrome, theideserver Claude Code connects to in a running VS Code or JetBrains IDE, and servers the CLI itself configures. Amanaged-mcp.jsonserver that uses${VAR}expansion in its command, arguments,env, URL, or headers is still checked, as is every server a user, a plugin,--mcp-config, or claude.ai adds.
Three matching rules apply inside those checks:
- Commands match exactly. Every argument, in order.
["npx", "-y", "server"]does not match["npx", "server"]or["npx", "-y", "server", "--flag"]. serverCommandandserverUrlvalues expand before matching. Both the policy entry and the server’s configured value go through${VAR}and${VAR:-default}expansion, so an entry written as["${HOME}/bin/server"]matches a server config that uses either the same reference or the expanded path. On Windows, reference an environment variable that is set there, such as${USERPROFILE}instead of${HOME}.serverNamevalues match literally and never expand. The two sides read different environments; How policy entries expand covers which, and how allowlist and denylist entries differ.- URLs support
*wildcards anywhere in the pattern, including the scheme. Hostname matching is case-insensitive and ignores a trailing FQDN dot, sohttps://Mcp.Example.com/*matcheshttps://mcp.example.com/api. Paths stay case-sensitive.
How policy entries expand
The server’s configured value expands from the live process environment, like the rest of.mcp.json. A policy entry expands from a pinned environment instead, so a variable set by a project or user settings file can’t change what an allowlist entry means. Because a policy entry still depends on the launching shell’s value for any variable it references, use literal URLs and commands for entries you rely on for enforcement.
Requires Claude Code v2.1.219 or later.
Example configuration
The configuration below sets up a hard allowlist with a denylist. The highlighted lines change how the rest of the list is evaluated, and the callouts after the block explain each one:- Line 3: the first
serverUrlentry. Once one exists, every remote server must match a URL pattern, so a user can’t get an unlisted remote server through by giving it an allowed name. - Line 5: the first
serverCommandentry. Same effect for stdio servers, so every local server must match a listed command exactly. - Line 11: a
serverNameentry in the denylist. Denylist entries always apply, so any server nameddangerous-serveris blocked regardless of its URL or command.
serverName entry in this allowlist would never match anything, since both transport types already have stricter entries.
The accordions below walk through how a server is evaluated against other allowlist and denylist combinations.
URL-only allowlist
URL-only allowlist
Command-only allowlist
Command-only allowlist
Mixed name and command allowlist
Mixed name and command allowlist
Name-only allowlist
Name-only allowlist
Allowlist with denylist override
Allowlist with denylist override
Restrict the allowlist to managed settings only
To make the managed allowlist the only one that applies, setallowManagedMcpServersOnly in the managed settings file:
allowManagedMcpServersOnly is true, allowlists from user, project, and local settings are ignored. The denylist still merges from every settings scope, so users can always block servers for themselves.
How restrictions appear to users
For what users see at startup whenmanaged-mcp.json is deployed and the session also has --mcp-config servers, see Exclusive control with managed-mcp.json. Use this table to recognize the other reports and to tell users what to expect before you roll out a change:
When a server silently disappears, the user gets no signal that policy is the reason, so tell affected users which servers are blocked when you roll out a new restriction.
Monitor MCP usage
When OpenTelemetry export is configured, Claude Code can record which MCP servers and tools users invoke. SetOTEL_LOG_TOOL_DETAILS=1 to include MCP server and tool names in tool events, then aggregate them in your collector to see which servers your users actually connect to. See Monitoring to set up the exporter and for the full event schema.
Configuration summary
Every file and setting this page covers, what it controls, and how to deliver it:Related resources
- Decide what to enforce: MCP restrictions alongside permission rules, sandboxing, and the other admin controls
- Connect Claude Code to tools via MCP: the full MCP reference, including transports, scopes, and authentication
- Settings: the settings hierarchy and how managed settings take precedence
- Server-managed settings: deliver
allowedMcpServersanddeniedMcpServersfrom the Claude.ai admin console - Security: the threat model these controls defend against
- Claude Enterprise Administrator Guide: SSO, SCIM, seat management, and rollout playbook