MCP registries and gateways: allowlisting servers for an organization
An organization allowlists MCP servers in four layers: vet each server against the Official MCP Registry and the vendor’s repository, record approved servers in one reviewed catalog, enforce the catalog in each agent’s managed configuration, and route calls through a gateway where audit logs are needed. The Official MCP Registry is a preview index, not a vetted list.
Your security team approved GitHub and Sentry MCP in the spring. Six months later, claude mcp list on a random laptop shows eleven servers, including a figma entry that points at a package from an unrelated publisher, and a Codex user has a Postgres server nobody reviewed. Nobody broke a rule, because there was no rule a tool could enforce, and nobody can say which servers ran against production data last week.
This page is for the tech lead who owns the approved list and the CTO who needs to show auditors that it holds. It assumes you have read securing MCP servers, which covers scanning and read-only modes for a single server. The per-tool policy files that carry this list sit in enforcing one policy across every coding agent.
What an organization MCP allowlist gives you
Section titled “What an organization MCP allowlist gives you”- A repeatable vetting routine that uses the Official MCP Registry for identity and the vendor’s repository for trust, with a prompt that runs it.
- A decision table for choosing between a fixed server set, an approved catalog and a denylist.
- One catalog file that renders into Claude Code
allowedMcpServers, Codexrequirements.tomland a Cursor review checklist. - A gateway route for teams that need one entry point, secret scanning and call logs.
- Mechanical checks that prove the allowlist holds on every machine, and a list of what breaks during rollout.
What is the Official MCP Registry, and what does it not do?
Section titled “What is the Official MCP Registry, and what does it not do?”The Official MCP Registry is a public index of MCP server metadata: name, version, packages and remote URLs. Its own README calls it “an app store for MCP servers”. It launched in preview on 2025-09-08 and its API has been frozen at v0.1 since 2025-10-24; general availability has not been announced (checked 2026-09-26). On 2026-09-26 it listed 36,176 servers at their latest version, counted through the registry API by this site’s research.
Three properties decide how you use it:
| Property | What it means for your allowlist |
|---|---|
Namespace verification only. io.github.<user>/… names require the publisher to authenticate as that GitHub user; com.example/… names require DNS or HTTP proof of the domain. | A name like io.github.getsentry/sentry-mcp proves who published the entry. It does not prove the server is safe or the one you meant. |
| Minimal moderation. The registry’s moderation policy says it does not remove “low-quality or buggy servers” or “servers with security vulnerabilities”, and that “consumers should assume minimal-to-no moderation”. | Registry presence is never an approval. Several */github, */figma and */sentry entries come from publishers unrelated to those vendors. |
| Versions can lag packages. On 2026-09-26 Serena’s registry entry was 1.5.3 while PyPI had 1.7.0, and Next.js DevTools was 0.3.6 against npm 0.4.0. | Pin versions from the package registry you install from, not from the MCP Registry. |
The registry expects aggregators downstream of it: services that copy its data “on a regular but infrequent basis (e.g., once per hour)” and add value such as ratings or security scanning. An aggregator that also serves the registry’s OpenAPI is a subregistry. That is the model for a private registry: your own filtered, reviewed copy, not a live dependency on the public index.
Which allowlist pattern fits your organization?
Section titled “Which allowlist pattern fits your organization?”Choose the pattern before you write any file. The stricter patterns cost developers flexibility, so match them to risk, not to habit.
| Pattern | What developers can do | Choose it when | Claude Code mechanism |
|---|---|---|---|
| Fixed set | Use exactly the servers you deploy; add nothing | Regulated code, contractors, CI runners | managed-mcp.json |
| Approved catalog | Add any server on the list; everything else is blocked | Most product teams | allowedMcpServers + allowManagedMcpServersOnly: true |
| Provided plus own | Get your remote servers automatically and keep their own | Early rollout, low-risk repositories | managedMcpServers |
| Denylist only | Add anything except known-bad servers | Only as a stopgap while you build the catalog | deniedMcpServers |
The rest of this page builds the approved catalog, because it is the pattern most organizations end up on and the other three are subsets of its steps.
How do you vet a server against the registry?
Section titled “How do you vet a server against the registry?”-
Search the registry, then keep only the vendor’s namespace. The registry API returns every entry that matches the product name, with its packages and remote URLs; the
selectline keeps the vendor’s namespace:Terminal window # terminal; needs curl and jqcurl -s "https://registry.modelcontextprotocol.io/v0.1/servers?search=sentry&version=latest" \| jq '.servers[]| select(.server.name | startswith("io.github.getsentry/"))| {name: .server.name, version: .server.version,repo: .server.repository.url,packages: [.server.packages[]?.identifier],remotes: [.server.remotes[]?.url]}'On 2026-09-26 the vendor’s entry was
io.github.getsentry/sentry-mcp0.42.0, with the npm package@sentry/mcp-serverand the remotehttps://mcp.sentry.dev/mcp. Remove theselectline to see every other result forsentry: each one is a candidate for your denylist, not your allowlist. -
Confirm the identity outside the registry. The
repository.urlmust belong to the vendor’s GitHub organization, and the vendor’s own README must name the same package or URL. Check the package itself withnpm view <package> name version repository.url deprecatedorhttps://pypi.org/pypi/<package>/json. -
Prefer the remote URL when the vendor offers one. A vendor-hosted HTTP server with OAuth runs no code on the laptop, and URL patterns are easier to enforce than exact commands. Record the read-only variant where one exists, such as GitHub’s
https://api.githubcopilot.com/mcp/readonly. -
Scan and classify. Run the scan from securing MCP servers and mark the server read-only or write-capable. Write-capable servers that touch production need a named security sign-off.
-
Record the decision in the catalog (next section), with the evidence: registry name, repository, pinned version, transport, read-only switch and approver.
Build the approved catalog as your private registry
Section titled “Build the approved catalog as your private registry”For most organizations, the private registry is a reviewed file in a policy repository, not a server. It is the single source for every tool’s configuration, and a pull request is the approval record.
# mcp-catalog/servers.yaml — one entry per approved server- name: github registry_name: io.github.github/github-mcp-server repository: https://github.com/github/github-mcp-server transport: http url: https://api.githubcopilot.com/mcp/readonly version: remote # vendor-hosted; nothing to pin read_only: true evidence: "registry entry + vendor README checked 2026-09-26" approver: "@acme/platform-security"- name: sentry registry_name: io.github.getsentry/sentry-mcp repository: https://github.com/getsentry/sentry-mcp transport: http url: https://mcp.sentry.dev/mcp version: 0.42.0 # registry entry and npm @sentry/mcp-server, 2026-09-26 read_only: false # write tools listed in the approving pull request evidence: "registry entry + npm view @sentry/mcp-server, 2026-09-26" approver: "@acme/platform-security"- name: playwright registry_name: io.github.microsoft/playwright-mcp repository: https://github.com/microsoft/playwright-mcp transport: stdio command: [npx, "@playwright/mcp@0.0.82"] version: 0.0.82 read_only: true evidence: "npm view @playwright/mcp version, 2026-09-26" approver: "@acme/frontend-leads"Give developers the exact install command next to each entry. Each line below matches the Claude Code allowlist and the Codex identity in the next section: the URLs fit their patterns, and the commands match argument by argument.
# terminal; Claude Codeclaude mcp add --transport http github https://api.githubcopilot.com/mcp/readonlyclaude mcp add --transport http sentry https://mcp.sentry.dev/mcpclaude mcp add playwright -- npx @playwright/mcp@0.0.82
# terminal; Codexcodex mcp add github --url https://api.githubcopilot.com/mcp/readonlycodex mcp add sentry --url https://mcp.sentry.dev/mcpcodex mcp add playwright -- npx @playwright/mcp@0.0.82In Codex, the requirement key must match the name the developer uses, so name is part of the contract, not a label.
Two other catalog forms are worth knowing:
- A plugin marketplace. Claude Code has no built-in MCP registry that users browse. Anthropic’s managed-MCP documentation suggests distributing approved servers as plugins in a team plugin marketplace so developers install them from
/plugin. - A subregistry. If you run many internal servers, host an aggregator that copies the public registry hourly with
updated_since, filters it to your approved names and adds your own servers. Publishing your own servers to the public registry is covered in building your own MCP server.
Enforce the catalog in each agent
Section titled “Enforce the catalog in each agent”Every tool enforces the list at a different point, with different matching rules. Render all three files from servers.yaml, and gate them in CI with the parity check from enforcing one policy across every coding agent.
Put the allowlist in a managed settings source: server-managed settings from the claude.ai admin console, a deployed managed-settings.json, an MDM profile or a registry policy. allowManagedMcpServersOnly: true stops users from widening the list in their own settings; denylists still merge from every scope.
{ "allowManagedMcpServersOnly": true, "allowedMcpServers": [ { "serverUrl": "https://api.githubcopilot.com/mcp/readonly" }, { "serverUrl": "https://mcp.sentry.dev/*" }, { "serverCommand": ["npx", "@playwright/mcp@0.0.82"] } ], "deniedMcpServers": [ { "serverUrl": "https://*.untrusted.example.com/*" } ]}The matching rules decide whether the list is real:
- Once the list has one
serverUrlentry, every remote server must match a URL pattern; once it has oneserverCommandentry, every stdio server must match a command exactly, argument by argument.["npx", "-y", "server"]does not match["npx", "server"]. - A
serverNameentry is not a security control, because users choose the name. - A denylist match always wins.
- To push remote servers to everyone without taking exclusive control, list them under
managedMcpServers(v2.1.259 or later, per the Claude Code managed-MCP documentation and changelog, checked 2026-09-26). Entries must behttps://HTTP or SSE servers with nocommand, and they load without an allowlist entry. - For a fixed set, deploy
managed-mcp.jsonto/Library/Application Support/ClaudeCode/(macOS),/etc/claude-code/(Linux and WSL) orC:\Program Files\ClaudeCode\(Windows). It cannot be delivered through server-managed settings.
Codex reads admin constraints from requirements.toml. The system file is /etc/codex/requirements.toml on Unix and %ProgramData%\OpenAI\Codex\requirements.toml on Windows; Codex also reads requirement layers from MDM and enterprise-managed sources. Each approved server is a table keyed by its name, with an identity:
[mcp_servers.github.identity]url = "https://api.githubcopilot.com/mcp/readonly"
[mcp_servers.sentry.identity]url = { match = "prefix", value = "https://mcp.sentry.dev/" }
[mcp_servers.playwright.identity]command = { executable = "npx", args = [{ match = "exact", value = "@playwright/mcp@0.0.82" }] }Behaviour, read from the Codex source at the rust-v0.157.1 tag and main on 2026-09-26:
- Once
mcp_serversis present, Codex disables every configured server whose name has no entry, or whose command or URL does not match that entry’s identity. A login or connection attempt then fails with “the MCP server is disabled by managed requirements”. - A plain
url = "…"is an exact string match. Use{ match = "prefix", … }or{ match = "regex", expression = … }for anything looser; a regex must match the whole value. - A plain
command = "npx"pins only the executable and ignores arguments, so it approves everynpxserver. Use theexecutableplusargsform, which requires the same number of arguments in the same order. - Plugins carry their own table:
[plugins."<plugin>@<marketplace>".mcp_servers.<name>.identity].
Cursor’s organization-level MCP controls could not be verified on 2026-09-26, because cursor.com was unreachable from the environment this page was checked in. The Cursor SDK 1.0.32 type definitions list team and mdm among its settings sources, so team-level and MDM-delivered settings exist; confirm in your admin dashboard whether they can restrict MCP servers to a list, and record the answer with the date.
Until then, enforce the catalog around Cursor rather than inside it:
- Commit a project-scope
.cursor/mcp.jsonthat contains only catalog servers, and put it underCODEOWNERS. - Point that file at a gateway profile (next section) so one reviewed entry carries the whole catalog.
- Run the canary check in the verification section on a Cursor machine each release.
When do you need an MCP gateway?
Section titled “When do you need an MCP gateway?”A gateway is a process that sits between the agent and the MCP servers: the agent connects to one entry, and the gateway starts or proxies the servers behind it. Add one when you need at least one of these:
- One entry point for several tools. One profile serves Claude Code, Cursor and other clients, so the tool-level allowlist shrinks to one gateway entry.
- Isolation. Stdio servers run in containers instead of in the developer’s session.
- Central controls. Secret scanning on arguments and responses, signature checks on server images, and call logs in one place.
Docker MCP Gateway is the verified option on this site. As of 2026-09-26, docker/mcp-gateway had 1.6k GitHub stars and its catalog repository docker/mcp-registry had 558 stars and 1.5k forks from server submissions (GitHub). Its gateway run defaults matter for audit:
| Flag | Default | What it does |
|---|---|---|
--log-calls | on | Logs each tool call with the tool name and argument shape only; raw argument values are not logged |
--block-secrets | on | Scans tool-call arguments and text responses for secret-like values before and after execution |
--verify-signatures | on for Docker MCP images | Requires images referenced by digest and verifies them before pull or run |
--block-network | off | Restricts the servers’ network access further |
# terminal; vendor lines from the docker/mcp-gateway READMEdocker mcp catalog pull mcp/docker-mcp-catalogdocker mcp profile create --name dev-tools \ --server catalog://mcp/docker-mcp-catalog/githubdocker mcp client connect claude-code --profile dev-tools --globalThen allowlist the gateway command itself, exactly as the client runs it, so no other stdio server gets through:
{ "serverCommand": ["docker", "mcp", "gateway", "run", "--profile", "dev-tools"] }This entry is an example. Read the command that docker mcp client connect actually wrote (claude mcp list, then claude mcp get <name>) and copy that into the allowlist, because the entry must match argument by argument.
# terminal; assembled from the README's manual client entrycodex mcp add MCP_DOCKER -- docker mcp gateway run --profile dev-toolsWhether docker mcp client connect supports Codex was not verified, so add the entry by hand. The matching requirement is:
[mcp_servers.MCP_DOCKER.identity]command = { executable = "docker", args = [ { match = "exact", value = "mcp" }, { match = "exact", value = "gateway" }, { match = "exact", value = "run" }, { match = "exact", value = "--profile" }, { match = "exact", value = "dev-tools" },] }# terminal; vendor line from the docker/mcp-gateway READMEdocker mcp profile create --name dev-tools \ --server catalog://mcp/docker-mcp-catalog/github --connect cursorCommit the resulting gateway entry in .cursor/mcp.json and review changes to it like code.
Outside Docker Desktop, run docker mcp feature enable profiles first. Profiles can be pushed to and pulled from an OCI registry, which is how one reviewed profile reaches every machine.
Two other gateway routes appear in the research for this site but were not tested here: the LiteLLM proxy lists an MCP gateway alongside its model gateway, and a Claude apps gateway policy is one of the managed sources Claude Code reads managedMcpServers from. Verify either against its own documentation before you rely on it.
A gateway also changes context cost: one entry can expose every tool in its profile. Keep profiles per team, measure with /context in Claude Code before and after you connect it, and use the techniques in reducing MCP token cost when the tool list grows.
Audit which servers actually run
Section titled “Audit which servers actually run”Enforcement says what may run; audit says what did. Collect both signals in one place.
- Claude Code telemetry. With OpenTelemetry export configured, set
OTEL_LOG_TOOL_DETAILS=1so tool events include MCP server and tool names. Aggregate them by server to see real usage per team. - Gateway logs. Ship the gateway’s call log to the same collector and alert on calls to tools outside the profile you expect.
- Upstream audit logs. For write-capable servers, the service’s own audit log is the record of what changed.
How do you prove the allowlist holds on every machine?
Section titled “How do you prove the allowlist holds on every machine?”Nobody reads every tool call. Check the configuration mechanically on a canary machine per platform, then on a sample of the fleet each release.
-
Negative test in Claude Code. With
managed-mcp.jsondeployed,claude mcp add --transport http test https://example.com/mcpmust fail withCannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers. Under an allowlist, adding an unlisted server must fail withnot allowed by enterprise policy. The URL does not need to exist; the policy check rejects the command first. -
Inventory check.
claude mcp listshows only catalog servers. If a user’s own server still appears undermanaged-mcp.json, Claude Code is not reading the file: check the path and its directory permissions./statusshowsmanagedMcpServersentries it dropped and why. -
Name-mismatch test in Codex. Register an approved URL under a wrong name and confirm Codex disables it. This proves the requirements file is loaded, not only that the right servers happen to be configured.
-
Gateway check. Call one tool through the gateway and confirm the call appears in the collector with the team and tool name.
-
Sign-off. The tech lead owns the catalog and approves additions; the security owner signs off every write-capable server with production reach; the platform team owns deployment and the checks above. Record each release’s results in the policy repository.
The measure to report upward is enforcement coverage: the share of sampled machines where the negative tests pass, per tool. Anything below 100% is a deployment bug, not a policy question.
What breaks when you roll out an MCP allowlist?
Section titled “What breaks when you roll out an MCP allowlist?”Developers lose a server with no message. Claude Code removes blocked servers from /mcp silently. Recovery: publish the catalog and the removal list before rollout, and point people at the negative-test message so they recognise policy.
Codex disables an approved server. The name differs from the requirement key, or the arguments differ (npx -y … against an identity without -y). Recovery: publish the exact codex mcp add line per catalog entry and render requirements from the same file.
CI jobs fail at startup. A job that passes --mcp-config on a machine with managed-mcp.json exits with You cannot dynamically configure MCP servers when an enterprise MCP config is present. Recovery: give CI runners their own managed file that contains the servers the job needs.
A managed server from settings never loads. managedMcpServers rejects http:// URLs, command entries and ${VAR} references, and Claude Code before v2.1.259 ignores the key. Recovery: read /status, fix the entry, and set a minimum client version in your rollout.
Users widened the list anyway. allowManagedMcpServersOnly is missing, so user-level allowlists merged in. Recovery: add the flag. With more than one managed source, reading the lock across sources needs v2.1.273 or later, according to the Claude Code managed-MCP documentation (checked 2026-09-26).
A gateway profile change shipped without review. Someone added a server to the shared profile, and every client picked it up. Recovery: keep profiles in the policy repository, push them to the OCI registry from CI only, and treat a profile diff like an allowlist diff.