Custom

Wire any OAuth authorization server with custom() or jwtOAuthProvider().

Use a custom provider when your identity provider is not covered by a built-in plugin, you need token introspection instead of JWT verification, or claim mapping differs from the defaults.

JWT identity providers

Use jwtOAuthProvider() with a short spec:

import { defineConfig } from "bitmcp";
import { jwtOAuthProvider } from "bitmcp/oauth";

export default defineConfig({
  auth: jwtOAuthProvider({
    name: "my-provider",
    resolveIssuer: () => "https://auth.example.com",
    jwksUrl: (issuer) => new URL(`${issuer}/.well-known/jwks.json`),
    oauthMetadata: (issuer) => ({
      issuer,
      authorization_endpoint: `${issuer}/oauth/authorize`,
      token_endpoint: `${issuer}/oauth/token`,
      registration_endpoint: `${issuer}/oauth/register`,
      response_types_supported: ["code"],
      grant_types_supported: ["authorization_code", "refresh_token"],
      code_challenge_methods_supported: ["S256"],
    }),
    mapUser: (payload) => ({
      id: String(payload.sub),
      email: typeof payload.email === "string" ? payload.email : undefined,
    }),
    audience: "https://mcp.example.com/mcp",
  }),
});

Full control with custom()

Use custom() when verification logic is not a standard JWT flow:

import { custom, createJwtVerifier } from "bitmcp/oauth";

auth: custom({
  createTokenVerifier: (resource) =>
    createJwtVerifier({
      issuer: "https://auth.example.com",
      jwksUrl: new URL("https://auth.example.com/.well-known/jwks.json"),
      resource,
      audience: resource.href,
    }),
  oauthMetadata: {
    issuer: "https://auth.example.com",
    authorization_endpoint: "https://auth.example.com/oauth/authorize",
    token_endpoint: "https://auth.example.com/oauth/token",
    response_types_supported: ["code"],
  },
  mapUser: (authInfo) => ({
    id: String(authInfo.extra?.payload?.sub ?? authInfo.clientId),
  }),
});

Resource binding

Tokens must target your MCP server. In production set MCP_URL to the same URL you use as the OAuth resource (for example https://mcp.example.com/mcp). You can also pass resource on the provider options.

OAuth requires HTTP. app.stdio() throws when auth is set.

Options

custom(options: {
  createTokenVerifier: (resource: URL) => OAuthTokenVerifier;
  oauthMetadata: OAuthMetadata;
  mapUser: (authInfo: AuthInfo) => BitmcpUser;
  resource?: URL | string;
  requiredScopes?: string[];
  scopesSupported?: string[];
  resourceName?: string;
})
VariablePurpose
MCP_URLCanonical public MCP URL in production
BITMCP_STATE_KEYMRTR state when using confirm or ctx.ask

Options passed to a factory override environment variables.