Control agent access
MCP allowlist: how to approve which servers and tools AI clients can use
Claude Code, GitHub Copilot and Cursor each have an MCP allowlist that blocks any server you haven't approved. They differ in where the list lives, how a server is matched, and whether you can approve less than a whole server.
New to MCP? What is an MCP server? covers the basics first.
An MCP allowlist is a list of approved MCP servers that an AI client checks before it loads one. Anything not on the list is blocked. Claude Code, GitHub Copilot (including VS Code) and Cursor all have one now, and all three match on the server's URL or the command that starts it.
What none of them do on their own is follow the server's tools, the credential it holds, or the agent calling it. I'll set up the Claude Code version for real, show the Copilot and Cursor equivalents, and then cover the gaps.
Prerequisites
- Claude Code. I ran these steps on 2.1.322.
- A scratch directory to try the settings in before you deploy them anywhere.
- For enforcement across a team: a way to put a file in a system directory on every machine (Jamf, Intune, Group Policy or your own tooling), or Claude Code's server-managed settings.
- For Copilot: an enterprise owner account. For Cursor: an Enterprise plan.
Step 1: Write a server allowlist for Claude Code
Claude Code reads two keys, allowedMcpServers and deniedMcpServers. Each entry matches a server one of three ways: serverUrl for remote servers (with * wildcards), serverCommand for local ones (the exact command and arguments), or serverName.
To try it, put this in .claude/settings.json in the scratch directory:
{
"allowedMcpServers": [
{ "serverUrl": "https://mcp.deepwiki.com/*" }
],
"deniedMcpServers": [
{ "serverUrl": "https://*.untrusted.example.com/*" }
]
}Then add three servers:
claude mcp add --transport http deepwiki https://mcp.deepwiki.com/mcp --scope project
claude mcp add --transport http other https://mcp.example.com/mcp --scope project
claude mcp add --transport http bad https://mcp.untrusted.example.com/mcp --scope projectAdded HTTP MCP server deepwiki with URL: https://mcp.deepwiki.com/mcp to project config
Cannot add MCP server "other": not allowed by enterprise policy
Cannot add MCP server "bad": server is explicitly blocked by enterprise policyThe list also filters servers that were already configured. With that file in place, claude mcp list showed only deepwiki. Every server in my user config had gone from the list without a message, and claude -p printed Warning: claude.ai MCP servers blocked by enterprise policy with my claude.ai connectors named after it. Tell people before you roll a list out.
Match on serverUrl or serverCommand. Anthropic's managed MCP docs warn that "a serverName entry, in either list, is not a security control", because the name is whatever the user typed after claude mcp add. An unset allowedMcpServers allows everything, and an empty array allows nothing.
Step 2: Stop users from widening the list
In a project or user settings file, the list is advisory. Allowlists from every scope merge, so I added this to .claude/settings.local.json in the same directory:
{
"allowedMcpServers": [
{ "serverUrl": "https://mcp.example.com/*" }
]
}and the other server that was refused a minute earlier went straight in. To make the list hold, deploy it as a managed setting with the lock on:
{
"allowManagedMcpServersOnly": true,
"allowedMcpServers": [
{ "serverUrl": "https://mcp.deepwiki.com/*" }
]
}The file goes in /Library/Application Support/ClaudeCode/managed-settings.json on macOS, /etc/claude-code/managed-settings.json on Linux and WSL, and C:\Program Files\ClaudeCode\managed-settings.json on Windows. With the lock on, only the managed allowlist counts. Denylists still merge from every scope, so a user can always block more for themselves, and a deny beats an allow.
If you'd rather hand everyone a fixed set and nothing else, managed-mcp.json in the same directory takes exclusive control. Users then can't add servers at all.
Step 3: Block single tools with permission rules
A server allowlist approves the whole server. To take one tool away, Claude Code uses a permission rule named mcp__<server>__<tool>. Deny rules run before ask and allow rules, and a deny that names a whole tool removes it from Claude's context.
DeepWiki's own docs list its question tool as ask_question, so I denied that first:
{
"permissions": {
"deny": ["mcp__deepwiki__ask_question"]
}
}Then I asked Claude, in print mode, to list its DeepWiki tools:
mcp__deepwiki__ask_wiki_question
mcp__deepwiki__read_wiki_contents
mcp__deepwiki__read_wiki_structureThe rule matched nothing. The live server calls the tool ask_wiki_question, and a rule for a name that doesn't exist fails without a word. After I changed the rule to mcp__deepwiki__ask_wiki_question, the same prompt returned only the two read tools.
So write tool rules from the names a client actually loads, and check them again when a server updates. Rules can also live in managed settings, and allowManagedPermissionRulesOnly ignores everyone else's. That flag is separate from allowManagedMcpServersOnly, so set both.
Step 4: Set the same policy for GitHub Copilot and VS Code
GitHub reuses Claude Code's key names. An enterprise owner writes a managed-settings.json, usually kept in the enterprise's .github-private repository or pushed with MDM, using allowedMcpServers and deniedMcpServers with serverUrl and serverCommand entries. GitHub's allowlist page gives the order: built-in servers are always allowed, a deny match blocks, and anything that misses the allowlist is blocked. A malformed list is treated as empty, which blocks everything but the built-ins.
There is also an older route: set an MCP registry URL and choose Registry only under AI controls. GitHub's enforcement reference says that one is "based only on server name/ID matching, which can be bypassed by editing configuration files", and recommends managed-settings.json instead.
For VS Code on machines you manage, the same controls exist as device policies: ChatMCP, ChatAllowedMcpServers, ChatDeniedMcpServers and ChatAllowManagedMcpServersOnly (VS Code policy reference). Neither GitHub page describes a per-tool rule.
Step 5: Set the allowlist for Cursor
In Cursor it's an Enterprise feature, in the team dashboard under MCP Configuration. Each entry is a URL or a command string, with * wildcards, and anything that doesn't match is blocked. Each entry also has a Tools field. Cursor's enterprise docs say you can "restrict which tools can run by listing them in that server's Tools field", and an empty field allows all of them.
Don't confuse it with mcpAllowlist in ~/.cursor/permissions.json. That one takes server:tool entries and decides which tools run without asking you first. A tool missing from it still runs after a prompt.
Where client allowlists stop
| Claude Code | GitHub Copilot | Cursor | |
|---|---|---|---|
| Enforced in | Claude Code on each machine | Each Copilot client | Each Cursor client |
| Matches servers by | URL, command or name | URL, command or name | URL or command string |
| Blocks single tools | Yes, with permission rules | Not in the docs | Yes, in each entry's Tools field |
| Set by | An admin, as a file or server-managed settings | An enterprise owner | A team admin, Enterprise plan |
Three gaps are left, whichever client you use:
- The list is per client. A developer with Claude Code, Copilot and Cursor installed needs three policies kept in step, and a client that isn't one of these reads none of them.
- The key stays where it was. An approved server still carries its API key in someone's config file. Keep credentials out of AI agents covers why that matters.
- It applies to the whole machine. Every agent on a laptop gets the same list. You can narrow one run further, with Claude Code's
--disallowedToolsflag for example, but that is set by whoever starts the agent. The upstream API still sees the one key, so its logs can't tell you which agent made a call.
Finding servers nobody approved is a separate job, covered in shadow MCP.
Step 6: Grant tools per agent at a gateway
A gateway enforces the list where the call happens, so it covers every client at once. In Statio, you add the approved servers in the dashboard, grant a person or an agent a whole server, and deny single tools on top. The upstream key is stored encrypted in Statio and attached at call time, so it never sits on a laptop. Each agent has its own token, and a call to a tool it wasn't granted gets a 403 and lands in the log beside the calls that went through. MCP permissions compares this with the other ways to do it.
The client allowlist is still worth having next to it. Allow only the gateway's URL, and a server someone adds by hand never loads.
Conclusion
You now have a Claude Code allowlist that users can't widen, a tool-level deny that you checked against the tool's real name, and the matching settings for Copilot and Cursor.
- Least privilege for AI agents covers the other things to narrow besides tools.
- Shadow MCP covers finding the servers that were installed before the list existed.
- Give an AI agent read-only GitHub access applies the same idea to one server.