How do you restrict which MCP servers a coding agent can use?
Check each MCP tool call against an allowlist of approved servers before the call runs. Claude Code, GitHub Copilot, Cursor, and OpenAI Codex fire a hook – a call to a command or HTTP endpoint that you configure – before every tool call, and an MCP call is a tool call. A policy at that hook reads the server name and the tool name and denies any server that isn't on your list. The agent's own process then refuses the call.
The Model Context Protocol (MCP) connects an agent to external servers that expose tools. An allowlist, a list of the only values that a rule permits, suits MCP well. The set of servers that your organization has reviewed is small and changes slowly, and anything outside it is unreviewed code with access to the developer's session. A deny list of known-bad servers can't keep up with servers that anyone can publish.
Arcjet coding agent hooks enforce that allowlist with one policy for all four agents. Arcjet reads the server name from each agent's own MCP naming, denies an unlisted server before the call runs, and records the decision in the Arcjet Console. The list lives in a published policy, so you change it in one place and the change takes effect in real time, with no redeploy to developer machines. For the wider set of coding agent controls, see coding agent security.
How does a hook identify an MCP call?
Each agent names MCP tools in its own way. The hook payload carries the following MCP tool names:
- Claude Code and Copilot: any
mcp__<server>__<tool>name. - Copilot CLI: any MCP tool.
- Codex: any
mcp__<server>__<tool>or@<server>/<tool>name. - Cursor: any namespaced MCP name.
For every one of these, Arcjet sets tool_kind to mcp, mcp_server to the MCP server name, and mcp_tool to the MCP tool name. A rule that compares tool_kind and mcp_server works the same way on every agent, so you don't maintain per-vendor rules.
What does an MCP allowlist policy look like?
Arcjet policies are written in Rego, the policy language of Open Policy Agent, and the Arcjet Console includes starter policies that you can publish or adapt. This one runs with Execute on set to Tool call in the Arcjet Console. The coding-agent.mcp-allowlist starter policy denies MCP servers outside a list written into the policy:
deny contains "unlisted-mcp-server" if { input.values.tool_kind == "mcp" not input.values.mcp_server in {"arcjet", "github", "sentry"}}Replace the three names with the servers that your organization has approved. A built-in tool, such as Bash or Read, isn't an MCP call, so mcp_server is empty and this rule doesn't fire on it. Other starter policies, such as destructive-command and credential-access, cover built-in tools. For those, see block dangerous commands and stop coding agents reading secrets.
When the rule fires, Arcjet denies the call before the MCP server receives it, and the agent receives the rule ID, unlisted-mcp-server, and nothing else.
Why write the list into the policy?
The allowlist has to come from somewhere that the agent can't change. A list that the agent supplied, or that arrived in the hook payload, is a list that the agent could widen. When the list is written into the Rego, the only way to change it is to edit and publish the policy. That happens in the Arcjet Console, not in the developer's session.
The hook configuration doesn't name the policy either. Arcjet runs every policy set to Tool call on each tool call, so publishing the allowlist is what turns it on. When the list changes, you don't redeploy hook configuration to every laptop, and the change takes effect in real time once you publish it.
How do you restrict individual MCP tools?
Some servers are safe to read from but not to write through. Rego gives you fine-grained control here: add a condition on mcp_tool for one server. The following rule adapts the per-server pattern in the Arcjet policy examples to the coding agent inputs. It allows only tools whose names start with get_ on the github server:
deny contains "unapproved-github-tool" if { input.values.tool_kind == "mcp" input.values.mcp_server == "github" not startswith(input.values.mcp_tool, "get_")}Before you write a prefix rule, check the tool names that your servers expose, and add stored tests for an allowed and a denied tool. Put the rule in the same policy as the server allowlist or in its own policy. Arcjet runs every policy set to Tool call in one round trip and applies the most restrictive decision.
What do you pair with an MCP allowlist?
The hook policy decides whether each MCP call runs. It compares the server name that the agent reports for the call, and it doesn't change which servers are configured. Pair it with the following controls:
- Vendor MCP settings. Use each agent's own managed configuration to limit which servers developers can install. The Arcjet allowlist is the backstop that still applies if a server gets configured anyway.
- Configuration change events. Arcjet records Claude Code's
ConfigChangeevent, which fires when settings change and is a tamper signal worth reviewing. - Elicitation events. Arcjet records Claude Code's
ElicitationandElicitationResultevents, so you can see when an MCP server asked the developer for input and what they answered. - Hook lockdown. Install the hooks through managed settings, the configuration that an administrator deploys to developer machines and that a developer can't remove without administrator access. For per-agent steps, see secure Claude Code, secure GitHub Copilot, secure Cursor, and secure OpenAI Codex.
- Your other tool-call policies. No supported agent offers a hook point where a tool's result could be withheld, so an approved server's response reaches the model. The destructive-command and credential-access starter policies apply to whatever the agent does next, as the following section describes.
What about a malicious MCP server on the allowlist?
An allowlist controls which servers the agent can call, not what an approved server sends back. A poisoned MCP response is one of the most common routes for a prompt injection into a coding agent, and it reaches the model after the tool has run.
Arcjet covers the next action. If an injected response tells the agent to read ~/.ssh/id_rsa or run a piped installer, that next step is a tool call, and Arcjet decides it against your other policies before it runs. Arcjet threat intelligence also scores the hosts that a tool call is about to contact, through the coding-agent.destination-threat starter policy. For more information, see rogue MCP server detection and detect malicious URLs from AI agents. For a comparison of MCP security tools, see MCP server security platforms.
How do you roll out an MCP allowlist?
Start by finding out which servers developers use. In Arcjet, every new rule starts in dry run, a mode in which Arcjet records what the rule would have denied without denying it, so you see the effect before it goes live. Publish the allowlist with a short list, open Activity in the Arcjet Console, and read which MCP calls it would have denied. Add the servers that you approve, and then set the rule live. Every session and decision stays in the Arcjet Console, and you can export decisions to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 (Enterprise plan) for detection and alerting on unlisted servers.
Tell developers how to request a new server. A denial names the rule ID, so a developer who sees unlisted-mcp-server knows that the server isn't approved rather than broken.
How does Arcjet enforce an MCP allowlist across coding agents?
With Arcjet, a coding agent that calls an unapproved MCP server gets a denial before the call runs, on Claude Code, Copilot, Cursor, and Codex alike. The following capabilities apply:
- One allowlist over normalized
mcp_serverandmcp_toolinputs, decided from the hooks that the agents already fire, with no code change. - A list held in a published policy that the developer's session can't change, updated in the Arcjet Console in real time.
- Hooks installed through managed settings, so developers can't remove them without administrator access.
- Starter policies for destructive commands, credential access, and destination threats, which apply to whatever an approved server's response asks the agent to do next.
The same policy engine protects custom agents that you build, so coding agents and custom agents share one approach.
To start, publish coding-agent.mcp-allowlist in dry run with the servers that you already know about, and review the rest on the Activity page in the Arcjet Console. For the full input contract, see the coding agent policies documentation.
Frequently asked questions
How do I allow only approved MCP servers in Claude Code or Cursor?
Deny MCP calls to any server outside an allowlist at the hook that the agent fires before each tool call. Arcjet sets tool_kind to mcp and mcp_server to the server name for every agent, so one rule covers Claude Code, GitHub Copilot, Cursor, and OpenAI Codex.
Where should the MCP allowlist live?
Write the MCP allowlist into the policy itself. A list that the agent or the hook payload supplied is a list that the agent could widen. When the list is in the Rego, only a published policy change can alter it.
Can I allow an MCP server but block some of its tools?
Yes. Add a rule that matches one mcp_server and denies mcp_tool names outside a pattern, such as allowing only read tools on that server.
Does an MCP allowlist protect against a poisoned MCP response?
Not on its own. An allowlist controls which servers the agent can call, and no supported agent lets a hook withhold a tool result. Arcjet decides whatever the agent tries next against your other tool-call policies, such as the destructive-command, credential-access, and destination-threat starter policies, before that call runs.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Start from the MCP allowlist starter policy and replace the example servers with the ones you approve.