Skip to main content
@phake/mcp supports five authentication strategies. Set AUTH_STRATEGY in your environment to select one, or let the framework infer it from which env vars are present.
# .dev.vars or wrangler secret
AUTH_STRATEGY=bearer
If AUTH_STRATEGY is not set, the framework infers the strategy:
  1. api_key — if API_KEY is present
  2. bearer — if BEARER_TOKEN is present
  3. none — otherwise

Strategies at a glance

StrategyDescriptionRequired env vars
oauthFull OAuth 2.1 PKCE flow; links provider tokens to MCP sessionsOAUTH_CLIENT_ID, OAUTH_CLIENT_SECRET, OAUTH_SCOPES, OAUTH_REDIRECT_URI, PROVIDER_CLIENT_ID, PROVIDER_CLIENT_SECRET, PROVIDER_ACCOUNTS_URL
bearerStatic Bearer token checked on every requestBEARER_TOKEN
api_keyStatic API key sent in a request headerAPI_KEY, API_KEY_HEADER (optional, default: x-api-key)
customArbitrary custom request headers forwarded to tool contextCUSTOM_HEADERS
noneNo authentication — all requests are accepted

oauth — OAuth 2.1 PKCE

Use oauth when you want users to authenticate with a third-party provider (Google, GitHub, Spotify, etc.). The server runs a full OAuth 2.1 authorization code flow with PKCE and maintains a mapping between its own RS tokens and the provider’s access tokens. When to use it: when your tools need to call external APIs on behalf of individual users, or when you need multi-user isolation with per-session tokens.
AUTH_STRATEGY=oauth

OAUTH_CLIENT_ID=your-mcp-client-id
OAUTH_CLIENT_SECRET=your-mcp-client-secret
OAUTH_SCOPES=openid profile email
OAUTH_REDIRECT_URI=https://your-server.com/oauth/callback

PROVIDER_CLIENT_ID=your-provider-client-id
PROVIDER_CLIENT_SECRET=your-provider-client-secret
PROVIDER_ACCOUNTS_URL=https://provider.example.com/accounts
Once a user completes the flow, context.providerToken in tool handlers is set to the provider’s access token. Use context.resolvedHeaders to forward it:
handler: async (_args, context) => {
  const response = await fetch("https://api.provider.com/me", {
    headers: context.resolvedHeaders,
    // => { Authorization: "Bearer <provider_access_token>" }
  });
  return await response.json();
},
The OAuth endpoints exposed by the server are:
PathDescription
/.well-known/oauth-authorization-serverOAuth discovery document
/.well-known/oauth-protected-resourceProtected resource metadata
/authorizeAuthorization request
/tokenToken exchange
/oauth/callbackOAuth callback
/oauth/provider-callbackProvider callback
/revokeToken revocation
/registerDynamic client registration

bearer — Static Bearer token

Use bearer for server-to-server integrations or personal deployments where a single shared secret is sufficient. Every incoming request must include Authorization: Bearer <token>. When to use it: simple scenarios where you control all clients and a single token is enough.
AUTH_STRATEGY=bearer
BEARER_TOKEN=super-secret-token
In your tools, context.providerToken is set to the value of BEARER_TOKEN and context.resolvedHeaders contains { Authorization: "Bearer super-secret-token" }. Configure clients to send the header:
{
  "mcpServers": {
    "my-api": {
      "url": "https://your-server.com/mcp",
      "headers": {
        "Authorization": "Bearer super-secret-token"
      }
    }
  }
}

api_key — Static API key

Use api_key to authenticate with a key sent in a custom header instead of the Authorization header. The header name defaults to x-api-key but is configurable. When to use it: APIs that expect a non-standard auth header (e.g., x-api-key, Api-Key), or when you want to avoid the Authorization header for routing reasons.
AUTH_STRATEGY=api_key
API_KEY=my-api-key-value
API_KEY_HEADER=x-api-key    # optional, this is the default
context.resolvedHeaders will contain { "x-api-key": "my-api-key-value" } (or whichever header name you configure).
handler: async (_args, context) => {
  const response = await fetch("https://api.example.com/data", {
    headers: context.resolvedHeaders,
    // => { "x-api-key": "my-api-key-value" }
  });
  return await response.json();
},

custom — Custom headers

Use custom when the upstream API you’re wrapping expects arbitrary headers that don’t fit the bearer or API key patterns — for example, multiple headers or non-standard schemes. When to use it: wrapping APIs that use proprietary auth headers, or when you need to forward multiple headers to every tool call. Provide headers as a comma-separated key:value string:
AUTH_STRATEGY=custom
CUSTOM_HEADERS=X-App-Id:my-app,X-App-Secret:my-secret
All headers in CUSTOM_HEADERS are available via context.resolvedHeaders:
handler: async (_args, context) => {
  const response = await fetch("https://api.example.com/data", {
    headers: context.resolvedHeaders,
    // => { "X-App-Id": "my-app", "X-App-Secret": "my-secret" }
  });
  return await response.json();
},

none — No authentication

Use none to accept all requests without checking credentials. Suitable for local development or fully public tools.
Do not use none in production deployments that are publicly accessible. Any client can call your tools without restrictions.
AUTH_STRATEGY=none

assertProviderToken

When a tool has requiresAuth: true the dispatcher already rejects unauthenticated calls before your handler runs. If you need a compile-time guarantee that context.providerToken is typed as string (not string | undefined), call assertProviderToken:
import { assertProviderToken } from "@phake/mcp";

const myTool = defineTool({
  name: "get_data",
  description: "Fetch data for the current user",
  inputSchema: z.object({}),
  requiresAuth: true,
  handler: async (_args, context) => {
    assertProviderToken(context); // throws if token is missing
    // After this line, context.providerToken is typed as string
    const token = context.providerToken;
    const response = await fetch("https://api.example.com/data", {
      headers: { Authorization: `Bearer ${token}` },
    });
    return await response.json();
  },
});
assertProviderToken throws Error("Authentication required") if the token is absent. In practice this only happens in tools without requiresAuth: true, since the dispatcher already guards authenticated tools.

Using context.resolvedHeaders

Prefer context.resolvedHeaders over constructing headers manually. The framework builds the correct header shape for the active strategy automatically:
StrategyresolvedHeaders content
oauth{ Authorization: "Bearer <provider_access_token>" }
bearer{ Authorization: "Bearer <BEARER_TOKEN>" }
api_key{ "<API_KEY_HEADER>": "<API_KEY>" }
customAll key/value pairs from CUSTOM_HEADERS
none{}
handler: async (_args, context) => {
  // Works correctly regardless of which strategy is active
  const response = await fetch("https://api.example.com/resource", {
    headers: context.resolvedHeaders,
  });
  return await response.json();
},