Zuplo

API Key Authentication for MCP Servers

A static key in a request header is the most common way to authenticate service-to-service calls to a remote MCP server.

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

How it works

The agent sends Authorization: Bearer <key> on every request to the MCP server. The server checks the key and resolves it to a caller before running a tool. Agent SDKs take the header when the client is constructed, so the first request on the wire is an authenticated initialize — there is no unauthenticated probe, 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
MCP spec
Outside the spec's OAuth flow, which the spec makes optional
Works with
Supported by every agent SDK; hosted connectors are more limited
Effort
An afternoon

The exchange

Each step in the exchange, in order.

  • Your agent Cloud Run, Lambda, CI
  • MCP server validates the key
  1. The key is configured when the client is constructed, so the first message on the wire is already authenticated: no 401, no .well-known lookup, no browser step.

    Your agent to MCP server

    POST /mcp 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 initialize result — tools listed
  4. There is no session credential to establish. Every subsequent request carries the same static key.

    Your agent to MCP server

    POST /mcp tools/call, same Authorization header
  5. MCP server to Your agent

    200 OK tool result
The agent sends its configured API key on the very first request. The MCP server validates the key, resolves it to a caller, and answers. Every later request repeats the same header — there is 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.
  • You need something that works across every agent SDK without 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.
  • The server will be listed in the ChatGPT or Claude connector directories, both of which require the spec OAuth flow.
  • Calls are made on behalf of a named person, or the audit trail has to 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. A static key therefore sits outside the spec rather than in conflict with it. The spec provides no way to advertise that a server expects a key, which is why each caller has to be configured directly.

Zuplo

What lands on you next

A static key answers exactly one question: is this caller allowed in. Every question asked of you after it ships is a different question.

Which tools can this key call?

mcp-capability-filter-inbound

An exact allow-list of tools, prompts and resources per route. A call to a hidden tool is refused with MethodNotFound before it reaches your server.

Who ran refund_order at 3am?

capability_invocation

Every tool call emits an event carrying the caller, the capability, the upstream, 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

Agents authenticate to the gateway, which strips that credential and injects the upstream key — so the secret your agents hold and the one your server accepts stop being the same secret.

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

mcp-okta-oauth-inbound

No — the inbound method is a policy, and there is a first-class one for eleven named identity providers plus any OIDC provider. Exactly one per project, and the provider'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.

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 spec compliance.

Do agent SDKs run the OAuth discovery flow automatically?

Not when a header is configured. Supplying headers means no auth provider is wired up, 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, and it has to if both audiences matter. Validate the static credential first and dispatch when it succeeds; fall through to a 401 with WWW-Authenticate only when nothing usable was presented. Reversing that order pushes callers holding a valid key into a discovery flow.

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. A server that accepts static keys should either omit that document or ensure a valid key dispatches before any 401 is written.

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.

Should an API key be used 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.