Skip to main content

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

  1. Your server exposes a /register endpoint that returns the configured clientId.
  2. The MCP client runs PKCE authorization against the upstream using that clientId.
  3. At token exchange, your server injects the clientId and clientSecret before forwarding to the upstream.
  4. 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 jwksVerifiermust 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,
    };
  },
});

Accessing user info in tools

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