MCP gateway vs policy enforcement: what's the difference?

An MCP gateway is a proxy on the path between an agent and its MCP servers. It routes, logs and can check credentials. Policy enforcement decides allow, deny or hold on each tool call, based on the caller, the tool and its arguments. A gateway can carry enforcement, but sitting in the path does not make it enforce anything.
Checked against the MCP specification revision 2026-07-28 and the Claude Code docs on code.claude.com, fetched 2026-10-29. The settings in this post are the ones those docs describe.
Placement versus decision
The two terms describe different things. A gateway describes where a component sits. Enforcement describes what that component decides. Each one alone leaves a gap. A gateway that sees every call but decides nothing is a logging point. A policy that nothing sits in front of only works if the client obeys it.
The MCP specification draws the same line. Its security overview says that MCP “itself cannot enforce these security principles at the protocol level”, and it asks implementors to build consent and authorization flows themselves. The protocol defines the messages and the rules a server must follow. It does not define a decision point. You place that yourself, and the rest of this post shows where each option can go. For the wider set of controls, see the MCP security pillar.
MCP gateway vs policy enforcement at a glance
| MCP gateway | Policy enforcement | |
|---|---|---|
| Main job | Route, log and authenticate MCP traffic | Decide allow, deny or hold for each tool call |
| Unit it judges | A connection or request | One tool call: server, tool name, arguments, caller |
| Where it runs | On the network path to HTTP MCP servers | In the client (rules, hooks) or on the server |
| Stdio servers | Out of reach, because they run as local processes | Covered by client rules and hooks |
| Can the developer switch it off? | Yes, by pointing the client somewhere else | Not when the policy sits in a managed settings source |
| What it leaves you | A record of what ran | A refusal before the call runs, with a reason |
Read the table by its last two rows. They decide whether you can rely on the control. A gateway that a developer can route around is useful for visibility. A rule that a developer can edit in their own settings is a default, not a control. Both have a place, but they answer different questions.
Where a gateway sees MCP traffic
An MCP client invokes a tool by sending a tools/call request, with the tool name and arguments in params. A gateway on that path can read the message before the server acts on it. That is the first condition for enforcement. The second is that the server is reachable only through the gateway, and the client’s configuration makes sure of that.
Local servers break the first condition. The Claude Code MCP docs say: “Stdio servers run as local processes on your machine.” There is no network hop to intercept. The MCP authorization spec adds that “Implementations using an STDIO transport SHOULD NOT follow this specification, and instead retrieve credentials from the environment”, so the HTTP authorization model does not reach them either.
Model gateways are a different path. Claude Code’s gateway page lists what a gateway provides: credentials, usage tracking, cost controls and audit logging, where the log covers “every model request”. That record shows what the model asked for. It does not, by itself, show that an MCP server ran the call.
Where policy enforcement runs
Enforcement has three possible homes: the client, the server, and a gateway between them. In the client, Claude Code’s permission rules and hooks decide before a call runs. A deny rule for one MCP tool uses the form mcp__<server>__<tool>, and a PreToolUse hook can return a deny decision with a reason.
On the server, the spec places access control. Its tools security section lists server duties: validate all tool inputs, implement proper access controls, rate limit tool invocations and sanitize tool outputs. HTTP servers also have to check that each access token was issued for them as the intended audience. Invalid or expired tokens get an HTTP 401.
A gateway is the third home. It gives you one checkpoint in front of HTTP servers. It does not remove the server’s duty. The spec says MCP servers “MUST NOT accept or transit any other tokens”, so a gateway in front of a server still leaves the token check with that server.
Steps: place the control on each path
- List every path an agent uses to reach a tool: local stdio servers, HTTP servers, direct API calls from code, and shell commands.
- Mark the paths that pass through a gateway. Every other path is covered only by client rules, hooks and the server’s own checks.
- Put the server allowlist in a managed source, with
allowManagedMcpServersOnlyset totrue. The example below is from the managed MCP docs. Replace the example URLs with your own servers. The restrict-servers post covers the match rules in full.
{
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{ "serverUrl": "https://api.githubcopilot.com/*" },
{ "serverUrl": "https://*.internal.example.com/*" }
]
}
- Add deny rules for individual tools that you must block on every server. Use the
mcp__<server>__<tool>form. The example below uses a placeholder tool name.
{
"permissions": {
"deny": [
"mcp__github__delete_repository"
]
}
}
- For each HTTP server, confirm it rejects an access token issued for a different audience with an HTTP 401. A gateway in front of the server does not replace this check.
- Log each decision in a store the developer cannot edit. The audit post covers the Claude Code signals to use.
- Test each rule from a managed test machine. Call one blocked server and one blocked tool, and confirm that both are refused.
Gotchas in the fine print
The docs’ fine print changes what a rule does.
- An unset allowlist is not an empty one. Leave
allowedMcpServersunset and every server is allowed. Set it to an empty array and no server is allowed, except those that skip the check. - An allowlist alone is a default. Without
allowManagedMcpServersOnly, allowlists from user and project settings merge in, so a developer can broaden what you allowed. - A
serverNameentry is a label, not a control. Any user can name a servergithub. Match onserverCommandorserverUrlto control what actually runs. - A deny rule matches the name a server is registered under. The same endpoint registered as
github2producesmcp__github2__...tools, which themcp__github__...rule does not match. Pin the server names inmanaged-mcp.jsonand deny each fixed server and tool pair, or enforce the operation at the gateway. - A failing hook does not block by default. A non-blocking error lets the action go ahead, unless the hook’s failure is set to block.
Enforcing this with AIControls
AIControls covers the model path and the identity layer. Its confirmed capabilities are: Claude Code through the gateway, in the CLI and in VS Code; per-user tokens; per-user and per-session cost and budget limits; PII held for approval, redacted or blocked; and identity provider integration for Cross App Access (XAA). Check those against your requirements first, and ask any vendor, including us, which layer each claim covers. The buyer’s guide lists the questions to ask. The product page is at AIControls.
Frequently asked questions
Is an MCP gateway the same thing as policy enforcement?
No. A gateway is where traffic passes: a proxy in front of MCP servers that can route, authenticate and log requests. Policy enforcement is the decision on each tool call. It can run in the gateway, in the client's permission rules and hooks, or on the server. A gateway that only logs gives you a record after the call has run. Enforcement has to refuse the call before it runs, from a layer the developer cannot switch off.
Do I still need a gateway if Claude Code has managed settings?
Not always. Managed settings can limit which MCP servers load and deny individual tools, and those rules apply on the developer's machine. A gateway adds one checkpoint for HTTP servers, central logs and a place for server-side checks. Stdio servers run as local processes, so a gateway in the network path does not see them. Decide per path: client rules cover local servers, and a gateway covers the HTTP traffic that is routed through it.
Does a deny rule still block a call when a PreToolUse hook returns allow?
Yes. Claude Code evaluates deny and ask rules whatever a PreToolUse hook returns, so a matching deny rule blocks the call. A hook that exits with code 2 blocks the call. An allow rule cannot carve an exception out of a deny rule. Confirm the order in your own settings with a test call before you rely on it.
What happens if my enforcement hook fails?
By default the action goes ahead. The hooks reference says that a non-blocking error, such as a hook that does not start or prints invalid JSON, lets the action continue. The exception is a hook whose failure is set to block. Test the failure path on purpose by stopping the script and checking that the call is refused. Only then count the hook as a control.
Can a developer turn off an MCP allowlist that comes from managed settings?
Not when allowManagedMcpServersOnly is true in a managed settings source. Without that flag, allowlists from user and project settings merge in, so a developer can broaden your list. Denylists merge from every scope, so a developer can always block more for themselves. Put the allowlist in a managed source, then check on a test machine that an unlisted server is refused.