Skip to main content

Client Authentication

An MCP client reaches AISIX at one of two entries: the aggregated /mcp endpoint, which serves every registered server's tools under <server>__<tool> names, and /mcp/{server}, which serves one server's tools under their original names. This guide covers how a caller proves who it is at those entries.

Client authentication is independent of upstream authentication: the credential a client sends to AISIX identifies the caller to AISIX, and the credential AISIX sends to an upstream MCP server is held gateway-side and never forwarded.

Three modes are available, and an environment can combine them:

ModeThe client sendsUse it for
Gateway API keyAuthorization: Bearer <api key>The default. Machine-to-machine callers and agents you issue keys to.
OAuth sign-inAn access token from your identity providerStandard MCP clients that discover the sign-in flow themselves.
AnonymousNothingClients on trusted networks that cannot present a credential.

Whatever the mode, the caller ends up as an API key principal: its tool grant, rate limits, budget, guardrails and usage attribution all apply. That is why anonymous callers stay as governable as authenticated ones.

Prerequisites

Before starting, prepare the following:

  • For AISIX Cloud, an environment and a write-scoped admin token. For On-Premises, follow the AISIX Cloud Quickstart. To request Hybrid Cloud access, contact API7.
  • For an open-source AISIX gateway, a gateway that loads a declarative resources file. Set Up MCP Gateway provides a working MCP server entry and the validation and reload workflow.
  • cURL for the AISIX Cloud examples.

Gateway API Key

This is the default and needs no configuration. A client sends its key on every request:

curl -sS -X POST "$AISIX_GATEWAY/mcp" \
-H "Authorization: Bearer $AISIX_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

The key's grant decides which tools the caller sees and can call. See Control Tool Access for scoping a key to specific tools, whole servers, or every tool, and MCP Access Policies for granting at the environment or team level.

x-api-key: <api key> is accepted as an alternative header.

OAuth Sign-In

Standard MCP clients such as desktop assistants can sign a user in instead of asking them to paste a key. They do it by reading the WWW-Authenticate header on a 401, fetching the protected resource metadata it points at, and running the OAuth flow against the authorization server named there.

AISIX publishes that metadata once an environment has both:

  • a canonical MCP resource URL — the public URL clients use to reach this environment's /mcp endpoint; and
  • at least one enabled OIDC trust provider, which is the authorization server tokens must come from.

With both configured, GET /.well-known/oauth-protected-resource (and its /.well-known/oauth-protected-resource/mcp sibling) returns the resource identity, the issuers tokens may come from, and the scopes they must carry. Without them the routes return 404 and 401 responses carry no challenge, exactly as before the feature existed.

Access tokens must include the resource URL in their audience claim. This is the most common configuration mistake: a token minted for a different audience is rejected at the gateway even though sign-in succeeded.

AISIX Cloud

Set the resource URL on the environment:

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mcp_resource_url": "https://gateway.example.com/mcp"
}'

The URL must be an absolute http or https URL whose path is exactly /mcp, with no query or fragment, and no embedded credentials — it is published on an unauthenticated endpoint. Send null to clear it and turn discovery off.

In the dashboard, the same setting lives on the environment's OIDC Providers page, which also warns when an enabled provider's audiences do not include the URL.

Open-Source AISIX Gateway

Add the settings row and a trust provider to resources.yaml:

resources.yaml
_format_version: "1"

mcp_auth_settings:
- resource_url: https://gateway.example.com/mcp

oidc_providers:
- name: corp-sso
issuer: https://sso.example.com/realms/agents
audiences:
- https://gateway.example.com/mcp
required_scopes:
- mcp:tools

At most one mcp_auth_settings entry may exist. A second one is rejected at load, and if a duplicate ever reaches a running gateway the discovery surface stays off rather than picking one.

Anonymous Access

Anonymous access lets clients that present no credential reach entries you open for them. It exists for fleets migrating from a gateway that never required a credential, where changing every client is not practical.

An anonymous request still runs as a principal you choose — an API key in the environment — so tool grants, rate limits, budgets, guardrails and usage attribution keep applying. Nothing is skipped; the credential check is what is replaced.

caution

Anyone who can reach the gateway from the allowed networks can call the permitted tools without a credential, and the usage counts against the environment. Treat the source network allowlist as the access control it is, and keep the principal's tool grant as narrow as the clients actually need.

What Anonymous Is Not

It is not a downgrade path. A request that presents a credential is authenticated normally, and an invalid, expired, disabled or malformed one is rejected with 401. Only a request that presents nothing at all takes the anonymous path. A wrong authentication scheme or an empty header value counts as presenting something, so a client that tries to authenticate and gets it wrong fails rather than quietly succeeding with a different identity.

It is not visible to callers who are not allowed in. Every refusal — source outside the allowlist, a server not offered anonymously, the principal deleted or disabled, anonymous access off — answers the same 401 the entry gives without anonymous access at all. Callers cannot tell those apart, nor tell a registered MCP server from one that does not exist. Operators see the reason on the gateway's aisix_auth_decisions_total metric.

Configuration

Anonymous access is configured per environment:

FieldMeaning
api_key_idThe API key anonymous traffic runs as.
source_cidrsClient source networks allowed in. Required and non-empty.
serversMCP servers anonymous callers may reach. Required and non-empty.
aggregate_entryWhether the aggregated /mcp endpoint also serves anonymous callers. Off by default.
enabledSet to false to close anonymous access while keeping the configuration. Defaults to true.

Two of these deserve more than a one-line description.

The Server List Is a Ceiling

servers is not only the list of /mcp/{server} entries to open — it is the limit on what the principal can reach anywhere, including through the aggregated endpoint. Without that, a principal whose own tool grant is wider than the list could name <server>__<tool> on the aggregated endpoint and reach a server whose per-server entry is closed.

So the effective grant of an anonymous caller is the listed servers' tools intersected with the principal's own grant. Both tools/list and tools/call follow it, which is why an anonymous caller never sees a tool it could not call.

A newly registered MCP server is never anonymous by default. Reaching anonymous callers is always a name added to this list.

The Principal Needs Its Own Grant

The principal must carry an explicit MCP grant. A key set to inherit the environment-default access policy is rejected: its tools would then follow that policy wherever it goes, so setting the policy to grant everything would hand every registered tool to anonymous callers without anyone revisiting this setting. A key that narrows what it inherits is accepted, as is a key with an explicit tool allowlist.

Configure in AISIX Cloud

Set the block on the environment:

curl -sS -X PATCH "$AISIX_CP/environments/$ENV_ID" \
-H "Authorization: Bearer $AISIX_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"mcp_anonymous": {
"api_key_id": "'"$ANON_KEY_ID"'",
"source_cidrs": ["10.0.0.0/8"],
"servers": ["docs"],
"aggregate_entry": false
}
}'

❶ The principal anonymous traffic runs as. It must belong to this environment and carry its own MCP grant.

❷ Matched against the source address AISIX resolves through its real-IP configuration, never a header the client supplies. Use 10.0.0.1/32 for a single address.

❸ Approved servers exposed to this environment. Anonymous callers reach /mcp/docs and nothing else.

❹ Leave the aggregated endpoint on gateway credentials. See Anonymous Access and OAuth Sign-In before turning it on.

Send "mcp_anonymous": null to turn anonymous access off. The change reaches running gateways without a restart.

In the dashboard, the same settings live on the environment's OIDC Providers page, where enabling anonymous access requires an explicit risk acknowledgement.

Configure in an Open-Source Gateway

Add the block to the mcp_auth_settings entry in resources.yaml:

resources.yaml
_format_version: "1"

mcp_auth_settings:
- anonymous:
api_key_id: 6fbea7f2-88a7-4cbb-8dca-a0ad785d07c5
source_cidrs:
- 10.0.0.0/8
servers:
- docs
aggregate_entry: false

api_key_id is the id of an api_keys entry in the same configuration. The resource_url field for OAuth discovery lives on this same entry; the two settings are independent, and either can be configured without the other.

Anonymous Access and OAuth Sign-In

An environment can run both, and for most deployments the natural split is: existing clients that cannot present a credential use the per-server entries anonymously, while standard MCP clients sign in through the aggregated /mcp.

Turning on aggregate_entry in an environment that also publishes OAuth discovery changes that. A no-credential request to /mcp then succeeds instead of returning the 401 that carries the discovery hint, so OAuth-capable clients never start the sign-in flow and stay on the anonymous grant. The per-server entries are unaffected.

Verify

Confirm the mode you configured behaves as intended.

An anonymous call to a listed entry succeeds with no credential:

curl -sS -X POST "$AISIX_GATEWAY/mcp/docs" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

The response lists the tools the principal's grant allows on that server, under their original names.

A bad credential is still rejected, rather than served anonymously:

curl -sS -o /dev/null -w '%{http_code}\n' -X POST "$AISIX_GATEWAY/mcp/docs" \
-H "Authorization: Bearer not-a-real-key" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'

The gateway returns 401.

Observability

Anonymous traffic is attributable. Usage events carry the principal's API key id like any other request, plus auth_type: anonymous, which distinguishes traffic that inherited the principal from an entry from traffic that presented that key's own credential. Authentication decisions, including refusals and their reasons, are counted on aisix_auth_decisions_total.

Limitations

Anonymous access is designed for trusted networks. Two capacity protections that authenticated deployments can rely on are not yet in place for it:

  • Per-source-IP rate limiting is not available. A single anonymous client can consume the principal's whole quota, since all anonymous traffic shares one principal.
  • The initialize, ping and tools/list methods are not metered. Only tools/call passes the rate-limit and budget gate.

The mandatory source network allowlist is what keeps this bounded. Do not expose anonymous entries to untrusted networks.

Next Steps