> ## Documentation Index
> Fetch the complete documentation index at: https://docs-mcp.phake.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Authentication

> Protect your MCP server with one of five built-in authentication strategies.

`@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.

```ini theme={null}
# .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

| Strategy  | Description                                                     | Required env vars                                                                                                                                       |
| --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `oauth`   | Full OAuth 2.1 PKCE flow; links provider tokens to MCP sessions | `OAUTH_CLIENT_ID`, `OAUTH_CLIENT_SECRET`, `OAUTH_SCOPES`, `OAUTH_REDIRECT_URI`, `PROVIDER_CLIENT_ID`, `PROVIDER_CLIENT_SECRET`, `PROVIDER_ACCOUNTS_URL` |
| `bearer`  | Static Bearer token checked on every request                    | `BEARER_TOKEN`                                                                                                                                          |
| `api_key` | Static API key sent in a request header                         | `API_KEY`, `API_KEY_HEADER` (optional, default: `x-api-key`)                                                                                            |
| `custom`  | Arbitrary custom request headers forwarded to tool context      | `CUSTOM_HEADERS`                                                                                                                                        |
| `none`    | No 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.

```ini theme={null}
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/provider-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:

```typescript theme={null}
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:

| Path                                      | Description                 |
| ----------------------------------------- | --------------------------- |
| `/.well-known/oauth-authorization-server` | OAuth discovery document    |
| `/.well-known/oauth-protected-resource`   | Protected resource metadata |
| `/authorize`                              | Authorization request       |
| `/token`                                  | Token exchange              |
| `/oauth/provider-callback`                | Provider callback           |
| `/oauth/callback`                         | OAuth callback              |
| `/revoke`                                 | Token revocation            |
| `/register`                               | Dynamic 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.

```ini theme={null}
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:

```json theme={null}
{
  "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.

```ini theme={null}
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).

```typescript theme={null}
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:

```ini theme={null}
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`:

```typescript theme={null}
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.

<Warning>
  Do not use `none` in production deployments that are publicly accessible. Any client can call your tools without restrictions.
</Warning>

```ini theme={null}
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`:

```typescript theme={null}
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:

| Strategy  | `resolvedHeaders` content                             |
| --------- | ----------------------------------------------------- |
| `oauth`   | `{ Authorization: "Bearer <provider_access_token>" }` |
| `bearer`  | `{ Authorization: "Bearer <BEARER_TOKEN>" }`          |
| `api_key` | `{ "<API_KEY_HEADER>": "<API_KEY>" }`                 |
| `custom`  | All key/value pairs from `CUSTOM_HEADERS`             |
| `none`    | `{}`                                                  |

```typescript theme={null}
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();
},
```
