Manage MCP Access with Policies
AISIX Cloud OnlyAvailable with AISIX CloudPer-key allowed_tools grants work well for a small number of callers. At larger scale, registering a new MCP server or tool can require updating hundreds of API keys. MCP access policies move the shared grant to the environment or team level, where each key can inherit, narrow, or reject it.
This guide explains how the policy layers combine, how to configure environment and team grants, how to migrate existing keys, and how to inspect a key's effective access.
How Policies Combine
Three layers participate, from broadest to narrowest:
- Environment default policy: the base grant for keys in the environment.
- Team entitlement: a team-level grant that replaces the environment default for the team's keys in every environment of the organization.
- Key
mcp_accessblock: how one key participates: inherit the grant, narrow it, or opt out.
A key's effective access is resolved per request:
base grant = team entitlement if the key's team has one, else the environment default
effective = (base grant ∩ key restriction) − every applicable deny pattern
Two rules keep the model predictable:
- A key can narrow the inherited grant, never widen it. A
restrictkey intersects its own patterns with the base grant; patterns outside the base add nothing. - Deny always wins. Deny patterns from the environment policy, the team entitlement, and the key are all subtracted. An environment-level deny holds even when a team entitlement replaces the environment's grant, and it also applies to keys that still use an explicit
allowed_toolslist.
Keys without an mcp_access block keep the exact per-key allowlist behavior: their allowed_tools list is the whole allow side, policies never widen them, and only policy deny patterns overlay them. Upgrading cannot silently grant access.
All patterns use the same <server>__<tool> naming and single-* glob matching as allowed_tools.
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:
export AISIX_CP="http://localhost:8080/api"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"
export ENV_ID="YOUR_ENVIRONMENT_ID"
Set the Environment Default Policy
Save the environment default:
curl -sS -X PUT "$AISIX_CP/environments/$ENV_ID/mcp_policy" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mode": "selected",
"allow": ["github__*"],
"deny": ["github__delete_repository"]
}'
❶ selected grants only tools matched by allow. Use none to grant no tools. Use all only when every tool on every current and future server should be available.
❷ Deny patterns apply to every key in the environment, including keys still using an explicit allowed_tools list.
The dashboard provides the same editor under Environment → MCP Access.
Choose a Key's Participation
A key's mcp_access block selects how it combines with the policies:
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": [],
"mcp_access": {
"mode": "inherit"
}
}')
export API_KEY_ID=$(echo "$API_KEY_RESPONSE" | jq -r '.api_key.id')
echo "$API_KEY_RESPONSE" | jq
❶ inherit uses the inherited grant unchanged. Use restrict to intersect the key's own allow patterns with the inherited grant and subtract its deny patterns. Use deny to grant no MCP tool access.
A restrict key that should reach only one server of a broader grant:
"mcp_access": {
"mode": "restrict",
"allow": ["github__*"],
"deny": ["github__delete_repository"]
}
Omit mcp_access entirely to keep the key on its explicit allowed_tools allowlist. Send "mcp_access": null on an update to return a policy-managed key to that behavior.
Verify Effective Access
Inspect the new key to verify which policy supplied each allow or deny pattern:
curl -sS "$AISIX_CP/environments/$ENV_ID/api_keys/$API_KEY_ID/effective_permissions" \
-H "Authorization: Bearer $AISIX_TOKEN" | jq
The key inherits the environment policy configured above:
{
"effective_permissions": {
"mcp": {
"key_mode": "inherit",
"base_source": "env_policy",
"all_tools": false,
"allow": [
{
"pattern": "github__*",
"source": "env_policy"
}
],
"deny": [
{
"pattern": "github__delete_repository",
"source": "env_policy"
}
]
}
}
}
key_mode: legacy marks a key still governed by its explicit allowed_tools. The dashboard shows the same view from the key's row on the API Keys page.
Grant a Team Entitlement
A team entitlement is configured once per team and applies to the team's caller API keys in every environment of the organization. When a team is synced from an identity provider group (SCIM directory sync), group membership changes propagate automatically without per-key edits.
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": { "mode": "selected", "allow": ["postgres__*"] }
}'
The entitlement replaces the environment default for member keys; it does not combine with it. Send "mcp": null to remove the entitlement. Member keys then fall back to each environment's default policy.
Migrate Existing Keys
Existing keys keep their explicit allowed_tools behavior until they are switched to inherit. A policy never widens them implicitly. The migration is a previewed, one-time batch:
Preview the impact first. The response counts affected keys and lists per-key pattern changes (up to 50 samples, changed keys first):
curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/mcp_policy/preview" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
{
"preview": {
"total_keys": 214,
"legacy_keys": 209,
"policy_managed_keys": 5,
"keys_gaining": 187,
"keys_losing": 12,
"keys_unchanged": 10,
"samples": [
{ "id": "…", "display_name": "ci-bot", "mode": "legacy", "gained": ["github__*"], "lost": [] }
]
}
}
Send a prospective policy in the body ({"policy": {…}}) to evaluate it before saving it. The comparison is pattern-level: it reports granted patterns, not an expansion into concrete tool names.
Then switch the keys:
curl -sS -X POST "$AISIX_CP/environments/$ENV_ID/mcp_policy/apply" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{}'
The response reports how many keys were switched:
{
"updated": 209
}
Every key still using an explicit allowlist switches to mcp_access: {"mode": "inherit"} (send key_ids to switch a subset). Each key's previous allowed_tools value is kept, so a key can be reverted individually by clearing its mcp_access. After the migration, registering a new MCP server or tool requires no key edits.
Operational Notes
- A policy can carry
"enabled": false; a disabled policy neither grants nor denies anything, and keys fall back to the next applicable layer. - Deleting the environment default while keys are in
inheritmode leaves those keys outside entitled teams with no MCP access. The resolution is fail-closed, never fail-open. - Enforcement is unchanged from per-key allowlists: unauthorized tools are filtered from
tools/listand rejected ontools/callbefore any upstream routing, with the same neutral error. - Every policy write, entitlement change, and batch migration is recorded in the audit log.
Next Steps
After verifying the policy, see MCP Gateway Overview to register and connect to an MCP server. Then add rate limits and budgets, guardrails, or observability to the tool-call path.