Skip to main content
FastMCP provides flexible authentication options to secure your MCP server, from simple API keys to full OAuth 2.1 flows with pre-configured providers.

OAuth with Pre-configured Providers

The simplest way to add OAuth is using the auth option with a pre-configured provider:
import { FastMCP, getAuthSession, GoogleProvider, requireAuth } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  }),
  name: "My Server",
  version: "1.0.0",
});

server.addTool({
  canAccess: requireAuth,
  description: "Get user profile",
  execute: async (_args, { session }) => {
    const { accessToken } = getAuthSession(session);
    const response = await fetch(
      "https://www.googleapis.com/oauth2/v2/userinfo",
      {
        headers: { Authorization: `Bearer ${accessToken}` },
      },
    );
    return JSON.stringify(await response.json());
  },
  name: "get-profile",
});

Available Providers

import { GoogleProvider } from "fastmcp";

const server = new FastMCP({
  auth: new GoogleProvider({
    baseUrl: "https://your-server.com",
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    scopes: ["openid", "profile", "email"],
  }),
  name: "My Server",
  version: "1.0.0",
});

Tool Authorization

Control which tools are available to authenticated users using the canAccess property with built-in helper functions:

Require Authentication

import { requireAuth } from "fastmcp";

server.addTool({
  canAccess: requireAuth, // Only authenticated users
  name: "user-tool",
  execute: async () => "Authenticated!",
});

Require Specific Scopes

import { requireScopes } from "fastmcp";

server.addTool({
  canAccess: requireScopes("read:user", "write:data"),
  name: "scoped-tool",
  execute: async () => "Access granted!",
});

Require Specific Role

import { requireRole } from "fastmcp";

server.addTool({
  canAccess: requireRole("admin"),
  name: "admin-tool",
  execute: async () => "Welcome, admin!",
});

Combine Requirements

import { requireAll, requireAuth, requireRole } from "fastmcp";

server.addTool({
  canAccess: requireAll(requireAuth, requireRole("admin")),
  name: "admin-only",
  execute: async () => "Full access!",
});

Access Session Data

Use getAuthSession for type-safe access to the OAuth session in your tool execute functions:
import { getAuthSession, GoogleSession } from "fastmcp";

server.addTool({
  canAccess: requireAuth,
  name: "get-profile",
  execute: async (_args, { session }) => {
    // Type-safe destructuring (throws if not authenticated)
    const { accessToken } = getAuthSession(session);

    // Or with provider-specific typing:
    // const { accessToken } = getAuthSession<GoogleSession>(session);

    const response = await fetch("https://api.example.com/user", {
      headers: { Authorization: `Bearer ${accessToken}` },
    });
    return JSON.stringify(await response.json());
  },
});
You can also access session.accessToken directly, but you must handle the case where session is undefined. The getAuthSession helper throws a clear error if the session is not authenticated, making it safer when used with canAccess: requireAuth.

Custom Authentication

For non-OAuth scenarios like API keys or custom tokens, use the authenticate option:
const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  authenticate: (request) => {
    const apiKey = request.headers["x-api-key"];

    if (apiKey !== "123") {
      throw new Response(null, {
        status: 401,
        statusText: "Unauthorized",
      });
    }

    return { id: 1, role: "user" };
  },
});

server.addTool({
  name: "sayHello",
  execute: async (args, { session }) => {
    return `Hello, ${session.id}!`;
  },
});

OAuth Discovery Endpoints

FastMCP supports OAuth discovery endpoints for direct integration with OAuth providers, supporting both MCP Specification 2025-03-26 and MCP Specification 2025-06-18:
import { FastMCP, DiscoveryDocumentCache } from "fastmcp";
import { buildGetJwks } from "get-jwks";
import fastJwt from "fast-jwt";

const discoveryCache = new DiscoveryDocumentCache({
  ttl: 3600000, // Cache for 1 hour
});

const server = new FastMCP({
  name: "My Server",
  version: "1.0.0",
  oauth: {
    enabled: true,
    authorizationServer: {
      issuer: "https://auth.example.com",
      authorizationEndpoint: "https://auth.example.com/oauth/authorize",
      tokenEndpoint: "https://auth.example.com/oauth/token",
      jwksUri: "https://auth.example.com/.well-known/jwks.json",
      responseTypesSupported: ["code"],
    },
    protectedResource: {
      resource: "mcp://my-server",
      authorizationServers: ["https://auth.example.com"],
    },
  },
  authenticate: async (request) => {
    const authHeader = request.headers.authorization;

    if (!authHeader?.startsWith("Bearer ")) {
      throw new Response(null, {
        status: 401,
        statusText: "Missing or invalid authorization header",
      });
    }

    const token = authHeader.slice(7);

    try {
      const config = (await discoveryCache.get(
        "https://auth.example.com/.well-known/openid-configuration"
      )) as {
        jwks_uri: string;
        issuer: string;
      };

      const getJwks = buildGetJwks({
        jwksUrl: config.jwks_uri,
        cache: true,
        rateLimit: true,
      });

      const verify = fastJwt.createVerifier({
        key: async (token) => {
          const { header } = fastJwt.decode(token, { complete: true });
          const jwk = await getJwks.getJwk({
            kid: header.kid,
            alg: header.alg,
          });
          return jwk;
        },
        algorithms: ["RS256", "ES256"],
        issuer: config.issuer,
        audience: "mcp://my-server",
      });

      const payload = await verify(token);

      return {
        userId: payload.sub,
        scope: payload.scope,
        email: payload.email,
      };
    } catch (error) {
      throw new Response(null, {
        status: 401,
        statusText: "Invalid OAuth token",
      });
    }
  },
});
This configuration automatically exposes OAuth discovery endpoints:
  • /.well-known/oauth-authorization-server - Authorization server metadata (RFC 8414)
  • /.well-known/oauth-protected-resource - Protected resource metadata (RFC 9728)
  • /.well-known/oauth-protected-resource<endpoint> - Protected resource metadata at sub-path

Helper Functions Reference

FunctionDescriptionExample
requireAuthRequires any authenticated usercanAccess: requireAuth
requireScopes(...scopes)Requires specific OAuth scopescanAccess: requireScopes("read:user")
requireRole(...roles)Requires specific rolecanAccess: requireRole("admin")
requireAll(...checks)Combines checks with AND logiccanAccess: requireAll(requireAuth, requireRole("admin"))
requireAny(...checks)Combines checks with OR logiccanAccess: requireAny(requireRole("admin"), requireRole("moderator"))
getAuthSession(session)Type-safe session extractionconst { accessToken } = getAuthSession(session)

Next Steps

OAuth Proxy

Learn about the built-in OAuth Proxy with DCR, PKCE, and token swap

Custom Routes

Add authenticated custom HTTP routes to your server

Build docs developers (and LLMs) love