FastMCP provides flexible authentication options to secure your MCP server, from simple API keys to full OAuth 2.1 flows 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
Google
GitHub
Azure
Custom Provider
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" ,
});
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
AND Logic
OR Logic
Custom Logic
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
Function Description Example requireAuthRequires any authenticated user canAccess: requireAuthrequireScopes(...scopes)Requires specific OAuth scopes canAccess: requireScopes("read:user")requireRole(...roles)Requires specific role canAccess: requireRole("admin")requireAll(...checks)Combines checks with AND logic canAccess: requireAll(requireAuth, requireRole("admin"))requireAny(...checks)Combines checks with OR logic canAccess: requireAny(requireRole("admin"), requireRole("moderator"))getAuthSession(session)Type-safe session extraction const { 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