Control MCP server access for your organization changedmanaged-mcp
Nearest release: v2.1.293, published under an hour after upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 7 Oct 2026 17:15 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 20 Oct 2026 18:37 UTC.
Upstream edited
Recorded here
Lines+103added
Lines−105removed
From line
15
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits25to this page, all time
#### Environment variables in `serverCommand` and `serverUrl` entries
The whole hunk
from line 15, old and new numbered
/
from line 15
1515* [Tell users what to expect](#how-restrictions-appear-to-users) when a restriction blocks a server
1616* [Monitor which servers your organization actually uses](#monitor-mcp-usage)
1717
18<Note>
19 The [Security](/docs/en/security) page covers the MCP threat model and how to evaluate a server before approving it. [Decide what to enforce](/docs/en/admin-setup#decide-what-to-enforce) covers MCP restrictions alongside the other administrative controls.
20</Note>
18> [!NOTE]
19>
20> The [Security](/docs/en/security) page covers the MCP threat model and how to evaluate a server before approving it. [Decide what to enforce](/docs/en/admin-setup#decide-what-to-enforce) covers MCP restrictions alongside the other administrative controls.
2121
2222## Choose a pattern
2323
from line 34
3434| **Denylist only** | Block known-bad servers, allow everything else | `deniedMcpServers` |
3535| **No restrictions** | Users add anything | Don't deploy any managed MCP configuration |
3636
37<Note>
38 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](/docs/en/plugins/org#restrict-what-users-can-install) so users can browse and install them from `/plugin`.
39</Note>
37> [!NOTE]
38>
39> 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](/docs/en/plugins/org#restrict-what-users-can-install) so users can browse and install them from `/plugin`.
4040
4141## Exclusive control with managed-mcp.json
4242
from line 254
254254
255255Without `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.
256256
257<Note>
258 `allowManagedMcpServersOnly` is separate from `allowManagedPermissionRulesOnly`, which locks down [permission rules](/docs/en/permissions#managed-settings) only. Setting that flag does not enforce the MCP allowlist.
259</Note>
257> [!NOTE]
258>
259> `allowManagedMcpServersOnly` is separate from `allowManagedPermissionRulesOnly`, which locks down [permission rules](/docs/en/permissions#managed-settings) only. Setting that flag does not enforce the MCP allowlist.
260260
261261### Match servers by URL, command, or name
262262
from line 281
281281
282282A `serverName` entry matches the user-assigned label exactly, with no wildcards.
283283
284<Warning>
285 A `serverName` entry, in either list, is not a security control. The name is the label a user assigns when running `claude mcp add` or editing a config file, not the underlying server, so a user can call any server `github`. For claude.ai connectors the name is the display name returned by claude.ai, which can change. To enforce which servers actually run, add `serverCommand` or `serverUrl` entries.
286</Warning>
284> [!WARNING]
285>
286> A `serverName` entry, in either list, is not a security control. The name is the label a user assigns when running `claude mcp add` or editing a config file, not the underlying server, so a user can call any server `github`. For claude.ai connectors the name is the display name returned by claude.ai, which can change. To enforce which servers actually run, add `serverCommand` or `serverUrl` entries.
287287
288288The `serverName` validation differs between the two lists:
289289
from line 324
324324* `https://staging.example.com:*/*`: blocks `https` servers on that host on every port
325325* `*://staging.example.com:*/*`: blocks that host over any scheme and on any port
326326
327<h4 id="how-policy-entries-expand">
328 Environment variables in `serverCommand` and `serverUrl` entries
329</h4>
327#### Environment variables in `serverCommand` and `serverUrl` entries
330328
331329`serverCommand` and `serverUrl` values expand before matching. Both the policy entry and the server's configured value go through [`${VAR}` and `${VAR:-default}` expansion](/docs/en/mcp#environment-variable-expansion-in-mcp-json), so an entry written as `["${HOME}/bin/server"]` matches a server config that uses either the same reference or the expanded path. `serverName` values match literally and never expand.
332330
from line 399
401399
402400The accordions below walk through how a server is evaluated against other allowlist and denylist combinations.
403401
404<Accordion title="URL-only allowlist">
405 ```json theme={null}
406 {
407 "allowedMcpServers": [
408 { "serverUrl": "https://mcp.example.com/*" },
409 { "serverUrl": "https://*.internal.example.com/*" }
410 ]
411 }
412 ```
402> **URL-only allowlist**
403>
404> ```json theme={null}
405> {
406> "allowedMcpServers": [
407> { "serverUrl": "https://mcp.example.com/*" },
408> { "serverUrl": "https://*.internal.example.com/*" }
409> ]
410> }
411> ```
412>
413> | Server | Result |
414> | :- | :- |
415> | HTTP server at `https://mcp.example.com/api` | Allowed: matches URL pattern |
416> | HTTP server at `https://api.internal.example.com/mcp` | Allowed: matches wildcard subdomain |
417> | HTTP server at `https://external.example.com/mcp` | Blocked: doesn't match any URL pattern |
418> | Stdio server with any command | Blocked: no name or command entries to match |
413419
414 | Server | Result |
415 | :- | :- |
416 | HTTP server at `https://mcp.example.com/api` | Allowed: matches URL pattern |
417 | HTTP server at `https://api.internal.example.com/mcp` | Allowed: matches wildcard subdomain |
418 | HTTP server at `https://external.example.com/mcp` | Blocked: doesn't match any URL pattern |
419 | Stdio server with any command | Blocked: no name or command entries to match |
420</Accordion>
420> **Command-only allowlist**
421>
422> ```json theme={null}
423> {
424> "allowedMcpServers": [
425> { "serverCommand": ["npx", "-y", "approved-package"] }
426> ]
427> }
428> ```
429>
430> | Server | Result |
431> | :- | :- |
432> | Stdio server with `["npx", "-y", "approved-package"]` | Allowed: matches command |
433> | Stdio server with `["node", "server.js"]` | Blocked: doesn't match command |
434> | HTTP server named `my-api` | Blocked: no name entries to match |
421435
422<Accordion title="Command-only allowlist">
423 ```json theme={null}
424 {
425 "allowedMcpServers": [
426 { "serverCommand": ["npx", "-y", "approved-package"] }
427 ]
428 }
429 ```
436> **Mixed name and command allowlist**
437>
438> ```json theme={null}
439> {
440> "allowedMcpServers": [
441> { "serverName": "github" },
442> { "serverCommand": ["npx", "-y", "approved-package"] }
443> ]
444> }
445> ```
446>
447> | Server | Result |
448> | :- | :- |
449> | Stdio server named `local-tool` with `["npx", "-y", "approved-package"]` | Allowed: matches command |
450> | Stdio server named `local-tool` with `["node", "server.js"]` | Blocked: command entries exist but doesn't match |
451> | Stdio server named `github` with `["node", "server.js"]` | Blocked: stdio servers must match commands when command entries exist |
452> | HTTP server named `github` | Allowed: matches name |
453> | HTTP server named `other-api` | Blocked: name doesn't match |
430454
431 | Server | Result |
432 | :- | :- |
433 | Stdio server with `["npx", "-y", "approved-package"]` | Allowed: matches command |
434 | Stdio server with `["node", "server.js"]` | Blocked: doesn't match command |
435 | HTTP server named `my-api` | Blocked: no name entries to match |
436</Accordion>
455> **Name-only allowlist**
456>
457> ```json theme={null}
458> {
459> "allowedMcpServers": [
460> { "serverName": "github" },
461> { "serverName": "internal-tool" }
462> ]
463> }
464> ```
465>
466> | Server | Result |
467> | :- | :- |
468> | Stdio server named `github` with any command | Allowed: no command restrictions |
469> | Stdio server named `internal-tool` with any command | Allowed: no command restrictions |
470> | HTTP server named `github` | Allowed: matches name |
471> | Any server named `other` | Blocked: name doesn't match |
437472
438<Accordion title="Mixed name and command allowlist">
439 ```json theme={null}
440 {
441 "allowedMcpServers": [
442 { "serverName": "github" },
443 { "serverCommand": ["npx", "-y", "approved-package"] }
444 ]
445 }
446 ```
447
448 | Server | Result |
449 | :- | :- |
450 | Stdio server named `local-tool` with `["npx", "-y", "approved-package"]` | Allowed: matches command |
451 | Stdio server named `local-tool` with `["node", "server.js"]` | Blocked: command entries exist but doesn't match |
452 | Stdio server named `github` with `["node", "server.js"]` | Blocked: stdio servers must match commands when command entries exist |
453 | HTTP server named `github` | Allowed: matches name |
454 | HTTP server named `other-api` | Blocked: name doesn't match |
455</Accordion>
456
457<Accordion title="Name-only allowlist">
458 ```json theme={null}
459 {
460 "allowedMcpServers": [
461 { "serverName": "github" },
462 { "serverName": "internal-tool" }
463 ]
464 }
465 ```
466
467 | Server | Result |
468 | :- | :- |
469 | Stdio server named `github` with any command | Allowed: no command restrictions |
470 | Stdio server named `internal-tool` with any command | Allowed: no command restrictions |
471 | HTTP server named `github` | Allowed: matches name |
472 | Any server named `other` | Blocked: name doesn't match |
473</Accordion>
474
475<Accordion title="Allowlist with denylist override">
476 ```json theme={null}
477 {
478 "allowedMcpServers": [
479 { "serverUrl": "https://*.example.com/*" }
480 ],
481 "deniedMcpServers": [
482 { "serverUrl": "https://staging.example.com/*" }
483 ]
484 }
485 ```
486
487 | Server | Result |
488 | :- | :- |
489 | HTTP server at `https://mcp.example.com/api` | Allowed: matches allowlist URL pattern, no denylist match |
490 | HTTP server at `https://staging.example.com/api` | Blocked: matches both, but the denylist takes precedence |
491 | HTTP server at `https://staging.example.com:8443/api` | Allowed: matches allowlist URL pattern, [no denylist match on this port](#how-serverurl-entries-match) |
492 | HTTP server at `https://other.com/mcp` | Blocked: doesn't match the allowlist |
493</Accordion>
473> **Allowlist with denylist override**
474>
475> ```json theme={null}
476> {
477> "allowedMcpServers": [
478> { "serverUrl": "https://*.example.com/*" }
479> ],
480> "deniedMcpServers": [
481> { "serverUrl": "https://staging.example.com/*" }
482> ]
483> }
484> ```
485>
486> | Server | Result |
487> | :- | :- |
488> | HTTP server at `https://mcp.example.com/api` | Allowed: matches allowlist URL pattern, no denylist match |
489> | HTTP server at `https://staging.example.com/api` | Blocked: matches both, but the denylist takes precedence |
490> | HTTP server at `https://staging.example.com:8443/api` | Allowed: matches allowlist URL pattern, [no denylist match on this port](#how-serverurl-entries-match) |
491> | HTTP server at `https://other.com/mcp` | Blocked: doesn't match the allowlist |
494492
495493### Restrict the allowlist to managed settings only
496494
No line in this hunk matches that.