
# Transport Auth

> Bearer tokens, API keys, or your own validator — on every HTTP method.

Auth is off by default because many MCP endpoints sit behind a gateway that already authenticates. Turn it on and every request to the route — `POST`, `GET`, and `DELETE` — must present a credential.

```ts
defineMcpHandler({
  name: "my-server",
  version: "1.0.0",
  auth: {
    tokens: [process.env.MCP_TOKEN!],
  },
  tools: [/* ... */],
});
```

With no `schemes` given, both are accepted: `Authorization: Bearer <token>` and `x-api-key: <token>`.

## API Key Only

```ts
defineMcpHandler({
  name: "my-server",
  version: "1.0.0",
  auth: {
    schemes: ["api-key"],
    header: "x-mcp-token", // default: x-api-key
    tokens: [process.env.MCP_TOKEN!],
  },
});
```

## Custom Validator

For dynamic credentials — a JWT, a per-tenant key, a lookup — validate yourself. The callback receives the parsed credentials and the event, and returns a boolean:

```ts
defineMcpHandler({
  name: "my-server",
  version: "1.0.0",
  auth: {
    schemes: ["bearer"],
    validate: async (auth, event) => {
      const claims = await verifyJwt(auth.token);
      if (!claims) return false;
      event.context.tenant = claims.tenant; // pass it down to your handlers
      return true;
    },
  },
});
```

Stashing the verified identity on `event.context` is the idiomatic way to get it to your tool handlers — auth runs before any of them.

Enabling auth requires at least one of `tokens` or `validate`; a config with neither throws at startup rather than accepting everything.

## Responses

Missing or invalid credentials get `401` with a `www-authenticate` header. No JSON-RPC body is produced — the request never reached the protocol layer.

Token comparison is length-capped, and tokens are trimmed. Invalid header names in `header` are rejected at startup.

## What Auth Does Not Do

> [!IMPORTANT]
> Auth answers "may this caller talk to this endpoint" — nothing more. It does not scope _what_ they may reach. A valid token still gets every tool you declared and every resource they can name. Per-operation authorization belongs in your handlers, or in [dynamic options](/guide/basics/handler#dynamic-options) so a caller only ever sees the tools they may use:

```ts
defineMcpHandler((event) => ({
  name: "my-server",
  version: "1.0.0",
  auth: { validate: verifyToken },
  tools: toolsFor(event.context.tenant), // resolved per request, after auth
}));
```

Also note that on the legacy era a session id is **not** a credential replacement: send auth on every request, including the ones that carry `Mcp-Session-Id`.

:read-more{to="/guide/legacy/sessions" title="Sessions"}
