Documentation Index
Fetch the complete documentation index at: https://mcp-use.com/docs/llms.txt
Use this file to discover all available pages before exploring further.
Use oauthProxy when your identity provider doesn’t support Dynamic Client Registration (DCR). You register a single application in the provider’s dashboard, hand the clientId / clientSecret to the MCP server, and the server mediates the OAuth flow so every MCP client can authenticate without registering itself upstream.
If your provider supports DCR (Auth0, Better Auth, Keycloak, WorkOS, Supabase, or any provider that advertises a registration_endpoint), prefer a remote auth provider instead — clients register directly with the upstream and the server only verifies tokens.
When to use OAuth Proxy
Reach for oauthProxy when all of the following are true:
- The provider requires you to register an app in a dashboard and returns a fixed
clientId / clientSecret.
- The provider does not expose a
registration_endpoint in its OAuth metadata.
- You’re fine holding the client secret on the server and mediating token exchange.
Common proxy targets: Google, GitHub, Okta, Azure AD (Microsoft Entra ID), Auth0 Regular Web Apps (non-DCR), and most enterprise SSO deployments.
How it works
- Your server exposes a
/register endpoint that returns the configured clientId.
- The MCP client runs PKCE authorization against the upstream using that
clientId.
- At token exchange, your server injects the
clientId and clientSecret before forwarding to the upstream.
- On each
/mcp/* request, your server verifies the bearer token via the verifyToken function you provided.
Your server holds the client credentials and mediates every token exchange. Tokens are passed through — the proxy does not mint its own.
Quick start (Google)
import { MCPServer, oauthProxy, jwksVerifier } from "mcp-use/server";
const server = new MCPServer({
name: "my-server",
version: "1.0.0",
oauth: oauthProxy({
authEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint: "https://oauth2.googleapis.com/token",
issuer: "https://accounts.google.com",
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["openid", "email", "profile"],
extraAuthorizeParams: { access_type: "offline" },
verifyToken: jwksVerifier({
jwksUrl: "https://www.googleapis.com/oauth2/v3/certs",
issuer: "https://accounts.google.com",
audience: process.env.GOOGLE_CLIENT_ID!,
}),
}),
});
await server.listen(3000);
Configuration options
oauthProxy({
// Required: upstream OAuth endpoints
authEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint: "https://oauth2.googleapis.com/token",
issuer: "https://accounts.google.com",
// Required: pre-registered client credentials
clientId: process.env.CLIENT_ID!,
clientSecret: process.env.CLIENT_SECRET, // optional for public clients
// Required: token verifier — resolves to { payload } or throws
verifyToken: jwksVerifier({
jwksUrl: "https://www.googleapis.com/oauth2/v3/certs",
issuer: "https://accounts.google.com",
audience: process.env.CLIENT_ID!,
}),
// Optional: scopes to request (default: ["openid", "email", "profile"])
scopes: ["openid", "email", "profile"],
// Optional: grant types (default: ["authorization_code", "refresh_token"])
grantTypes: ["authorization_code", "refresh_token"],
// Optional: extra authorize-request params (e.g. audience, access_type, prompt)
extraAuthorizeParams: { access_type: "offline", prompt: "consent" },
// Optional: custom user info extractor
// Default pulls sub/email/name/picture and parses `scope` into scopes[].
getUserInfo(payload) {
return {
userId: payload.sub as string,
email: payload.email as string | undefined,
name: payload.name as string | undefined,
};
},
});
jwksVerifier helper
For standard JWT + JWKS providers, jwksVerifier handles signature verification, issuer checking, and optional audience validation against a remote JWKS endpoint:
verifyToken: jwksVerifier({
jwksUrl: "https://www.googleapis.com/oauth2/v3/certs",
issuer: "https://accounts.google.com",
audience: process.env.GOOGLE_CLIENT_ID!, // optional
});
For non-JWT providers (e.g. GitHub opaque tokens), write a custom verifyToken function — see the GitHub example below.
Any verifyToken function — including the one returned by jwksVerifier — must resolve to { payload: Record<string, unknown> } or throw on an invalid token. The proxy surfaces payload to getUserInfo and to ctx.auth.
Provider examples
Google
oauth: oauthProxy({
authEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
tokenEndpoint: "https://oauth2.googleapis.com/token",
issuer: "https://accounts.google.com",
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scopes: ["openid", "email", "profile"],
extraAuthorizeParams: { access_type: "offline" },
verifyToken: jwksVerifier({
jwksUrl: "https://www.googleapis.com/oauth2/v3/certs",
issuer: "https://accounts.google.com",
audience: process.env.GOOGLE_CLIENT_ID!,
}),
});
Okta
const oktaDomain = process.env.OKTA_DOMAIN!;
oauth: oauthProxy({
authEndpoint: `${oktaDomain}/oauth2/default/v1/authorize`,
tokenEndpoint: `${oktaDomain}/oauth2/default/v1/token`,
issuer: `${oktaDomain}/oauth2/default`,
clientId: process.env.OKTA_CLIENT_ID!,
clientSecret: process.env.OKTA_CLIENT_SECRET,
scopes: ["openid", "email", "profile"],
verifyToken: jwksVerifier({
jwksUrl: `${oktaDomain}/oauth2/default/v1/keys`,
issuer: `${oktaDomain}/oauth2/default`,
}),
});
Azure AD (Microsoft Entra ID)
const tenantId = process.env.AZURE_TENANT_ID!;
const base = `https://login.microsoftonline.com/${tenantId}/v2.0`;
oauth: oauthProxy({
authEndpoint: `${base}/oauth2/v2.0/authorize`,
tokenEndpoint: `${base}/oauth2/v2.0/token`,
issuer: base,
clientId: process.env.AZURE_CLIENT_ID!,
clientSecret: process.env.AZURE_CLIENT_SECRET,
scopes: ["openid", "profile", "email"],
verifyToken: jwksVerifier({
jwksUrl: "https://login.microsoftonline.com/common/discovery/v2.0/keys",
issuer: base,
audience: process.env.AZURE_CLIENT_ID!,
}),
});
Auth0 Regular Web App (non-DCR)
Use the proxy when your Auth0 tenant doesn’t have the Early Access DCR feature enabled:
const domain = process.env.AUTH0_DOMAIN!;
const audience = process.env.AUTH0_AUDIENCE!;
oauth: oauthProxy({
authEndpoint: `https://${domain}/authorize`,
tokenEndpoint: `https://${domain}/oauth/token`,
issuer: `https://${domain}/`,
clientId: process.env.AUTH0_CLIENT_ID!,
clientSecret: process.env.AUTH0_CLIENT_SECRET,
scopes: ["openid", "email", "profile"],
extraAuthorizeParams: { audience },
verifyToken: jwksVerifier({
jwksUrl: `https://${domain}/.well-known/jwks.json`,
issuer: `https://${domain}/`,
audience,
}),
});
See the runnable auth0-proxy example for a full working setup.
GitHub (opaque tokens)
GitHub issues non-JWT opaque tokens, so jwksVerifier doesn’t apply. Write a custom verifyToken that validates by calling the GitHub API:
oauth: oauthProxy({
authEndpoint: "https://github.com/login/oauth/authorize",
tokenEndpoint: "https://github.com/login/oauth/access_token",
issuer: "https://github.com",
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
scopes: ["read:user", "user:email"],
async verifyToken(token) {
const res = await fetch("https://api.github.com/user", {
headers: {
Authorization: `Bearer ${token}`,
"User-Agent": "my-mcp-server",
},
});
if (!res.ok) throw new Error("Invalid GitHub token");
const user = await res.json();
return { payload: { sub: String(user.id), ...user } };
},
getUserInfo(payload) {
return {
userId: payload.sub as string,
username: payload.login as string | undefined,
name: payload.name as string | undefined,
email: payload.email as string | undefined,
picture: payload.avatar_url as string | undefined,
};
},
});
server.tool(
{
name: "get-user-info",
description: "Get authenticated user info",
},
async (_args, ctx) => ({
userId: ctx.auth.user.userId,
email: ctx.auth.user.email,
name: ctx.auth.user.name,
scopes: ctx.auth.scopes,
}),
);
The upstream access token is available as ctx.auth.accessToken — use it to call provider APIs on behalf of the user:
server.tool(
{
name: "get-google-profile",
description: "Fetch the full profile from Google's userinfo endpoint",
},
async (_args, ctx) => {
const res = await fetch("https://openidconnect.googleapis.com/v1/userinfo", {
headers: { Authorization: `Bearer ${ctx.auth.accessToken}` },
});
return { content: [{ type: "text", text: JSON.stringify(await res.json()) }] };
},
);
Resources
Next Steps