Skip to main content
Version: Dev

Manage MCP Access with Policies

AISIX Cloud OnlyAvailable with AISIX Cloud

Granting MCP tools on each caller API key works well for a small number of callers. At larger scale, registering a new MCP server or tool can require updating hundreds of keys. MCP access policies move the shared part of the grant to the environment or team level, where it applies to every key those layers cover.

This guide explains how the layers combine, how to configure the environment and team grants, and how to inspect a key's effective access.

How the Layers Combine

Up to three layers apply to a caller API key. They all carry the same two fields — allow and deny — and none of them overrides another:

  1. Environment policy: applies to every key in the environment.
  2. Team policy: applies to the keys bound to one team, in every environment of the organization.
  3. Key mcp_access block: the key's own layer.

A key's effective access is resolved per request:

effective = (every present layer's allow, intersected) − (every present layer's deny, unioned)

Three rules keep the model predictable:

  • Allow intersects. A tool is available only when every layer that is present allows it, so any layer can narrow the result and none can widen it. A team policy cannot grant a tool the environment policy withholds, and neither can a key.
  • Deny always wins. A deny pattern on any layer removes the tool, whatever the other layers allow.
  • A missing layer imposes no constraint, but no layer at all grants nothing. A key with no mcp_access block, a team with no policy, and a disabled policy each simply drop out of the intersection. When none of the three is configured, the key has no MCP tool access — access is granted explicitly, never by the absence of configuration.

Because allow is required on every layer, the two edge cases are always spelled out rather than implied:

allowMeaning
[]This layer allows nothing, so every key it covers loses MCP access.
["*"]This layer narrows nothing. Use it for a layer that only subtracts through deny.

All patterns use the <server>__<tool> naming and single-* glob matching described in Control Tool Access.

Prerequisites

The examples on this page use the AISIX Cloud Admin API. Prepare the control-plane URL, environment ID, and write-scoped admin token for your organization. Install curl and jq to run the commands.

For On-Premises, follow the AISIX Cloud Quickstart. To request Hybrid Cloud access, contact API7. Then set:

# AISIX_CP includes /api and has no trailing slash
# The local On-Premises quickstart uses http://localhost:8080/api
export AISIX_CP="YOUR_AISIX_CLOUD_ADMIN_API_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"

Set the Environment Policy

Save the environment layer:

curl -sS -X PUT "$AISIX_CP/environments/$ENV_ID/mcp_policy" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"allow": ["github__*"],
"deny": ["github__delete_repository"]
}'

❶ The environment allows every github tool and nothing else. Send ["*"] only when every tool on every current and future server should be reachable, and [] to block MCP access for the whole environment.

❷ Deny patterns apply to every key in the environment, however the team and key layers are configured.

The dashboard provides the same editor under Environment → MCP Access.

Give a Key Its Own Layer

A key that adds no layer of its own takes whatever the environment and team layers leave:

API_KEY_RESPONSE=$(curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/api_keys" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"display_name": "inheriting-caller",
"allowed_models": []
}')

export API_KEY_ID=$(printf '%s\n' "$API_KEY_RESPONSE" | jq -er '.api_key.id')
export AISIX_API_KEY=$(printf '%s\n' "$API_KEY_RESPONSE" | jq -er '.plaintext')
printf '%s\n' "$API_KEY_RESPONSE" | jq

API_KEY_ID and AISIX_API_KEY refer to the same caller. AISIX Cloud returns the plaintext only in this create response, so retain both variables for setup and verification.

Add an mcp_access block when the key should reach less than its environment and team allow:

"mcp_access": {
"allow": ["github__*"],
"deny": ["github__delete_repository"]
}

The block is the key's whole allow side, so "allow": [] leaves the key no MCP access at all, and "allow": ["*"] narrows nothing — useful when the key only means to subtract through its own deny. Send "mcp_access": null on an update to remove the key's layer, returning it to whatever the other layers leave.

Verify Effective Access

Inspect the key to see which layers constrain it and where each pattern came from:

curl -sS "$AISIX_CP/environments/$ENV_ID/api_keys/$API_KEY_ID/effective_permissions" \
-H "Authorization: Bearer $AISIX_TOKEN" | jq

The key above is constrained by the environment layer alone:

{
"effective_permissions": {
"mcp": {
"layers": [
{
"source": "env_policy",
"policy_id": "a5065729-3049-407a-90d4-357a6ab214c2"
}
],
"all_tools": false,
"allow": [
{
"pattern": "github__*",
"source": "env_policy"
}
],
"deny": [
{
"pattern": "github__delete_repository",
"source": "env_policy"
}
]
}
}
}

An empty layers array means nothing is configured anywhere, which is why such a key has no MCP tool access. The dashboard shows the same view from the key's row on the API Keys page.

Grant a Team Policy

A team policy is configured once per team and applies to caller API keys explicitly bound to that team, in every environment of the organization. SCIM directory sync updates the team's member roster when identity provider group membership changes, but it does not bind or rebind caller API keys. A caller key receives the team policy only after it is bound to the team.

Copy the Team ID from the Teams page, then export it for the request:

export TEAM_ID="YOUR_TEAM_ID"

curl -sS -X PUT "$AISIX_CP/teams/$TEAM_ID/entitlements" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mcp": { "allow": ["postgres__*"] }
}'

The team layer intersects with the environment layer rather than replacing it: team-bound keys reach postgres__* only if the environment policy allows it too. Send "mcp": null to remove the layer, leaving those keys with the environment layer and any key-level mcp_access block that remains.

Operational Notes

  • A policy can carry "enabled": false. A disabled policy is not a layer at all: it neither grants nor denies, and drops out of the intersection.
  • Deleting the environment policy leaves keys that have no team policy and no mcp_access block with no layer at all, and therefore no MCP access. The resolution is fail-closed, never fail-open.
  • Unauthorized tools are filtered from tools/list and rejected on tools/call before any upstream routing, with the same neutral error.
  • Every policy write and entitlement change is recorded in the audit log.

Next Steps

If the upstream server is not registered yet, use the registration workflow in Set Up MCP Gateway. Before verifying everything__echo, make sure every access layer that applies to the caller allows it. The setup workflow updates the key-level grant; update any applicable environment or team policy as well. Then add rate limits and budgets, guardrails, or observability to the tool-call path.