Zuplo
On this page

API key authentication for MCP servers

A static key in a request header authenticates service-to-service calls to a remote MCP server, with no browser step and no token to mint.

Method
API key
Platform
Any platform
MCP specification
2026-07-28

How it works

The agent sends Authorization: Bearer API_KEY on every request to the MCP server, where API_KEY is the key you issued it. The server checks the key and resolves it to a caller before it runs a tool. Agent SDKs take the header when you construct the client, so the first request on the wire is already authenticated: no 401, no .well-known lookup, and no browser step.

Best for
Service-to-service calls where you control both the agent and the MCP server
Specification
Outside the specification's OAuth flow, which the specification makes OPTIONAL
Works with
Every agent SDK on this page; hosted connectors are more limited
Effort
An afternoon

The exchange

  • Your agent Cloud Run, Lambda, CI
  • MCP server validates the key
  1. You configure the key when you construct the client, so the first message on the wire is already authenticated.

    Your agent to MCP server

    POST /mcp (carries the credential) tools/list, Authorization: Bearer sk_live_…
  2. Inside MCP server

    Look the key up and resolve it to a caller
  3. MCP server to Your agent

    200 OK tools listed
  4. There is no session credential to establish, so every later request carries the same static key.

    Your agent to MCP server

    POST /mcp (carries the credential) tools/call, same Authorization header
  5. MCP server to Your agent

    200 OK tool result
The agent sends its configured API key on the first request, the MCP server validates the key and resolves it to a caller, and every later request repeats the same header, with no 401, no discovery request, and no token exchange anywhere in the flow.

Connect your agent

Pick your language and SDK.

Language
SDK
TypeScriptagent.ts
import { query } from "@anthropic-ai/claude-agent-sdk";

// `type: "http"` is streamable HTTP; `"sse"` is the older transport.
for await (const message of query({
  prompt: "Search for open orders.",
  options: {
    mcpServers: {
      example: {
        type: "http",
        url: "https://mcp.example.com/mcp",
        headers: { Authorization: `Bearer ${process.env.MCP_API_KEY}` },
      },
    },
    // Auto-approves these tools. Anything unlisted stops for a
    // permission prompt, which a headless run never gets past.
    allowedTools: ["mcp__example__search"],
  },
})) {
  console.log(message);
}

When to use something else

Use this when

  • Both ends belong to you: an agent calling your own MCP server, a scheduled job, a CI step.
  • One credential can represent the whole integration, because no end user's identity needs to reach the API behind the server.
  • The callers are agent SDKs: they all accept a header, and none can complete a browser step.

Use something else when

  • The agent and the MCP server run in the same cloud, where workload identity removes the secret entirely.
  • You plan to list the server in the ChatGPT or Claude connector directories, both of which require the specification's OAuth flow.
  • The agent calls on behalf of a named person, or the audit trail must identify a user rather than a caller.

The specification makes authorization OPTIONAL and says HTTP transports SHOULD, rather than MUST, conform to its OAuth flow, so a static key sits outside the specification rather than in conflict with it. What you give up is discovery: nothing in MCP advertises that a server expects a key, so you configure each caller directly.

Zuplo

A static key settles one thing: whether a caller is allowed in. It says nothing about which tools that caller can reach, how often it can call them, or who ran what, and none of those needs a change to your server.

  1. Step 1.

    Move the key upstream

    Your server keeps the key it already accepts. The gateway holds it now and presents it on every forwarded call, so the key stops living in five agents' config files.

    "url": "https://api.example.com/mcp"
  2. Step 2.

    Add the policies

    A list on the route. Callers authenticate to the gateway with OAuth — every MCP route requires one of these policies — and the upstream key is attached after that.

    "inbound": [
      "mcp-oauth-inbound",
      "mcp-capability-filter-inbound",
      "set-upstream-api-key-inbound"
    ]
  3. Step 3.

    Deploy

    Your MCP server keeps the URL, the authentication, and the code it has right now. It never restarted.

    zuplo deploy

Why can the reporting agent's key call delete_records?

mcp-capability-filter-inbound

Because a key is all-or-nothing until something narrows it. Allowlist the tools on the route: everything unlisted is refused with MethodNotFound before it reaches your server.

Who ran refund_order at 3 AM?

capability_invocation

One query, not a log grep: every tool call emits an event carrying the caller, the tool, the outcome, and the latency. Tokens and request bodies are never logged.

That key is in five agents' env vars. How do we rotate it?

set-upstream-api-key-inbound

Split them. Agents hold a gateway credential and the gateway injects the upstream key, so rotating either side is one change in one place.

Sales wants this in Claude with our Okta login. Do we rewrite the server?

mcp-okta-oauth-inbound

No — you point the route's sign-in at Okta, one of eleven named identity providers plus any OIDC one. Every MCP route carries exactly one of these policies, and Okta's token never reaches the MCP client.

All of these attach to one MCP route's policies.inbound — the same policy engine on the way in and on the way out. Your MCP server keeps the code and the authentication it has today.

Common questions

Does MCP require OAuth, or can I use an API key?

An API key is permitted. The specification makes authorization optional and says HTTP transports SHOULD, rather than MUST, conform to its OAuth flow. The real limits are hosted-connector support and directory listing, not specification compliance.

Do agent SDKs run the OAuth discovery flow automatically?

Not when a header is configured. Supplying headers means the SDK sets up no auth provider, so the credential goes out on the first request and no 401 is ever exchanged. Discovery applies to clients that connect without a credential.

Why do so many MCP servers accept a fixed token instead of implementing OAuth?

The server usually sits in front of an API that already authenticates with a token. Running an authorization server to reach the same result adds work without adding security when both ends belong to the same team.

Can one endpoint accept both static keys and OAuth?

Yes. Validate the static credential first and dispatch when it succeeds; write a 401 with WWW-Authenticate only when nothing usable was presented, because reversing that order pushes callers holding a valid key into a discovery flow. Ordering only helps for clients that send the request at all: some probe /.well-known first and start OAuth whenever they find discovery metadata there, whatever header they hold — Cursor is the staff-confirmed case. If those clients matter, serve the OAuth metadata from a separate route.

Why would a client ignore the header it was given and attempt OAuth?

Some clients read /.well-known/oauth-protected-resource on connect and treat its presence as proof that OAuth is required. If your server accepts static keys, either omit that document or dispatch a valid key before writing any 401.

Is a header name other than Authorization acceptable?

In your own agents, yes: every SDK shown here sets arbitrary headers. The Anthropic Messages API connector exposes only authorization_token, so a server that insists on X-API-Key cannot be reached through it. Authorization: Bearer is the portable choice.

Is an API key the right choice when the agent and MCP server share a cloud?

Usually not. On Cloud Run, Lambda, or a shared Kubernetes cluster the platform can issue a short-lived identity token instead, which removes the stored secret and makes the caller's identity verifiable rather than looked up.

How does a server move from API keys to OAuth?

Accept both during the transition by validating the bearer token as either a key or a JWT, then withdraw key acceptance one caller at a time. Handling this at a gateway makes it a single change rather than one per server.

One policy engine for APIs, AI, and MCP

Put your MCP servers behind a gateway that speaks every identity provider, filters tools per role, and logs every call.