Get started
MCP Auth implements the authorization requirements of the latest MCP specification and works with any OAuth 2.0 / OpenID Connect provider that meets them.
How it works
The MCP TypeScript SDK v2 (@modelcontextprotocol/server) ships the entire HTTP layer of MCP authorization itself: requireBearerAuth, oauthMetadataResponse, and official framework adapters like @modelcontextprotocol/express. What it asks you to bring is provider integration: verifying the access tokens your OAuth 2.0 / OpenID Connect provider issues, and describing that provider in your server's metadata.
That is exactly what MCP Auth provides:
- A token verifier: the
MCPAuthinstance implements the SDK'sOAuthTokenVerifierinterface. It discovers your provider's metadata, fetches its JWKS, and verifies JWT access tokens (signature, issuer, audience, expiration, and the claims MCP servers need), with sensible caching throughout. - Your auth metadata:
mcpAuth.getAuthMetadataOptions()returns the SDK'sAuthMetadataOptions, ready to serve the OAuth discovery documents (RFC 9728 Protected Resource Metadata and RFC 8414 Authorization Server Metadata).
Choose a compatible OAuth 2.1 or OpenID Connect provider
MCP Auth works with any provider that meets the MCP specification's authorization requirements. In practice, two things matter:
- The provider supports standard metadata discovery (RFC 8414 or OpenID Connect Discovery), including a JWKS endpoint for token verification.
- The provider can issue JWT access tokens bound to your MCP server (RFC 8707); this is usually a matter of registering your server's identifier as a resource or audience.
Check the MCP-compatible provider list to see how popular providers score, and the Provider Guides for concrete configuration steps.
Install MCP Auth SDK
- pnpm
- npm
- yarn
pnpm add mcp-auth @modelcontextprotocol/servernpm install mcp-auth @modelcontextprotocol/serveryarn add mcp-auth @modelcontextprotocol/server@modelcontextprotocol/server v2 is a peer dependency of mcp-auth. The SDK is ESM only and requires Node.js >= 20, or any fetch-native runtime such as Cloudflare Workers, Deno, or Bun.
If your MCP server is built on the MCP TypeScript SDK v1 (@modelcontextprotocol/sdk), stay on the 0.2 line with npm install mcp-auth@0.2 and check out the v0.2 documentation. When you move to the v2 SDK, follow the migration guide.
Init MCP Auth
Declare your MCP server as a protected resource: give it a resource identifier (RFC 8707) and tell it which authorization server to trust:
import { MCPAuth } from 'mcp-auth';
const mcpAuth = new MCPAuth({
protectedResourceMetadata: {
// The resource identifier of this MCP server; also the expected `aud` claim of access tokens
resource: 'https://api.example.com/mcp',
// The authorization server trusted by this MCP server
authorizationServer: { issuer: 'https://auth.example.com/oidc', type: 'oidc' }, // or 'oauth'
// The scopes this MCP server understands
scopesSupported: ['read:notes'],
},
});
With this discovery config, the authorization server metadata is fetched lazily when first needed and cached afterwards, which is safe for edge runtimes where network calls are not allowed during module initialization. If you prefer to fetch and validate the metadata at startup so misconfigurations fail fast, use fetchServerConfig:
import { MCPAuth, fetchServerConfig } from 'mcp-auth';
const mcpAuth = new MCPAuth({
protectedResourceMetadata: {
resource: 'https://api.example.com/mcp',
authorizationServer: await fetchServerConfig('https://auth.example.com/oidc', { type: 'oidc' }),
scopesSupported: ['read:notes'],
},
});
One MCPAuth instance represents one protected resource trusting one authorization server. Everything in the declaration is published through the metadata endpoints, and the token verifier enforces what it declares: the aud claim of access tokens must match resource, and the iss claim must match the configured authorization server.
For other ways to provide the authorization server metadata (custom well-known URLs, data transpilation, or manual metadata), check Configure MCP Auth.
Serve the metadata and protect your MCP endpoint
Two steps remain, and each is one call into the MCP SDK:
- Serve the OAuth discovery documents so MCP clients can find your authorization server: feed
mcpAuth.getAuthMetadataOptions()to the SDK's metadata helpers. - Gate your MCP endpoint with the SDK's
requireBearerAuth:mcpAuth.getBearerAuthOptions()bundles the token verifier with the resource metadata URL (and your required scopes) into the SDK'sBearerAuthOptions.
- Fetch-native (Cloudflare Workers, Deno, Bun, Node.js)
- Express (Node.js)
import {
createMcpHandler,
oauthMetadataResponse,
requireBearerAuth,
} from '@modelcontextprotocol/server';
// `createMcpServer` builds your `McpServer` instance with tools (see the next section)
const handler = createMcpHandler(createMcpServer);
// Signature, issuer, audience, expiration, and scopes are all enforced by the gate
const gate = requireBearerAuth(mcpAuth.getBearerAuthOptions({ requiredScopes: ['read:notes'] }));
export default {
async fetch(request: Request): Promise<Response> {
// Serve the OAuth discovery documents
if (new URL(request.url).pathname.startsWith('/.well-known/')) {
const metadata = oauthMetadataResponse(request, await mcpAuth.getAuthMetadataOptions());
if (metadata) return metadata;
}
// Require a valid Bearer token for everything else
const auth = await gate(request);
if (auth instanceof Response) return auth;
return handler.fetch(request, { authInfo: auth });
},
};import {
createMcpExpressApp,
mcpAuthMetadataRouter,
requireBearerAuth,
} from '@modelcontextprotocol/express';
import { toNodeHandler } from '@modelcontextprotocol/node';
import { createMcpHandler } from '@modelcontextprotocol/server';
// `createMcpServer` builds your `McpServer` instance with tools (see the next section)
const mcpNodeHandler = toNodeHandler(createMcpHandler(createMcpServer));
const app = createMcpExpressApp();
// Serve the OAuth discovery documents
app.use(mcpAuthMetadataRouter(await mcpAuth.getAuthMetadataOptions()));
app.all(
'/mcp',
// Require a valid Bearer token; the verified auth info flows to the handler via `req.auth`
requireBearerAuth(mcpAuth.getBearerAuthOptions({ requiredScopes: ['read:notes'] })),
// `createMcpExpressApp` applies `express.json()`, which drains the request stream, so the
// parsed body is passed along explicitly
async (request, response) => mcpNodeHandler(request, response, request.body)
);
app.listen(3000);The metadata helpers serve the Protected Resource Metadata document at the path derived from your resource identifier (for example, https://api.example.com/mcp → /.well-known/oauth-protected-resource/mcp), and also mirror the authorization server metadata for clients that look it up on your MCP server. Requests without a valid token receive a 401 response with a WWW-Authenticate challenge pointing at the resource metadata, which is how MCP clients discover where to sign in.
Using Hono or another framework on a fetch-native runtime? The sample servers wrap the same two calls in Hono middlewares; the wiring is identical.
In OAuth 2.0, scopes are the primary mechanism for permission control. A valid token with the correct audience does NOT guarantee the user has permission to perform an action: authorization servers may issue tokens with an empty or limited scope.
Always use requiredScopes to enforce that the token contains the necessary permissions for each operation. Never assume a valid token implies full access.
For more details on Bearer auth, including per-tool scope enforcement and opaque token verification, check Configure Bearer auth.
Retrieve the auth info in your MCP implementation
Inside tool callbacks (and other MCP request handlers), use getAuthInfo to read the verified identity:
import { McpServer } from '@modelcontextprotocol/server';
import { getAuthInfo } from 'mcp-auth';
const createMcpServer = () => {
const server = new McpServer({ name: 'Notes', version: '1.0.0' });
server.registerTool('whoami', { description: 'Get the current user' }, (context) => {
// Pass `{ requiredScopes: [...] }` as the second argument for per-tool authorization
const { subject, claims } = getAuthInfo(context);
return { content: [{ type: 'text', text: JSON.stringify({ subject, claims }) }] };
});
return server;
};
getAuthInfo returns a McpAuthInfo object: the SDK's AuthInfo with guaranteed issuer, subject (the sub claim, typically the user ID), and the full verified JWT payload as claims.
Didn't hear about JWT (JSON Web Token) before? Don't worry, you can keep reading the documentation and we'll explain it when needed. You can also check Auth Wiki for a quick introduction.
Next steps
Continue reading to learn an end-to-end example of how to integrate MCP Auth with your MCP server, and how to handle the auth flow in MCP clients. Upgrading an existing server from mcp-auth 0.2? Start with the migration guide.