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.
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
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:
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 so a caller only ever sees the tools they may use:
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.