npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

mcp-oidc-provider

v2.0.2

Published

OIDC provider for MCP servers with pluggable identity providers

Downloads

889

Readme

mcp-oidc-provider

NPM Version

OIDC provider for MCP (Model Context Protocol) servers with support for any OIDC-compliant identity provider.

Implementing a remote hosted MCP server requires implementing MCP Authorization Protocol. In theory, this is straightforward because modern applications either implement OAuth specs themselves or use an OAuth-compliant IdP like Auth0, Clerk, Okta, or Keycloak. Long story short, using your own IdP as-is imposes many limitations.

This package takes care of those limitations for you so you can focus on implementing your tools, resources, and prompts instead of spending hours investigating why your implementation doesn't work with Cursor or logs you out from Claude every few hours.

This package allows you to run either in standalone mode or integrate it into your MCP implementation. It works with any OIDC-compliant identity provider like Auth0, Clerk, Okta, Keycloak, Azure AD, Google, and more.

It uses different packages under the hood to glue everything together:

| Package | Purpose | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | | oidc-provider | Core OIDC/OAuth 2.0 server implementation. Handles authorization, token issuance, JWKS, client registration, and all OAuth flows | | openid-client | OAuth 2.0/OIDC client library. Used by OidcClient to communicate with upstream identity providers via OIDC Discovery | | jose | JWT signing/verification and JWKS generation. Used for access tokens and ID tokens | | keyv | Universal key-value storage abstraction. Used for sessions, tokens, grants, and OIDC adapter data | | express | Web framework for the Express adapter. Provides routing, middleware, and HTTP handling | | express-session | Session management for Express. Stores login state during OAuth flows |

Further Reading

To better understand the full auth flow when building an MCP server, challenges encountered, and solutions we implemented, take a look at the following detailed blog posts.

Installation

npm install mcp-oidc-provider keyv openid-client

JWKS (Signing Keys)

JWKS (JSON Web Key Set) contains the keys used to sign access tokens.

For development: No configuration needed. The package auto-generates keys on startup. Note that tokens become invalid after server restart.

For production: Generate and persist signing keys:

npx mcp-oidc-provider --pretty

Store the output securely and provide it via the jwks option or JWKS environment variable. This ensures:

  • Tokens remain valid across server restarts
  • All instances in a distributed deployment use the same keys

Quick Start

Option 1: Standalone OIDC Server

That is useful if you already have your MCP implementation in a different stack than express js. You can have the implementation in nextjs, then you can run this server standalone and proxy the Auth requests to it using the MCP SDK's ProxyOAuthServerProvider. See the standalone-oidc example.

Both servers must share the same persistent Keyv store (e.g., Tigris, Redis) so the MCP server can look up tokens issued by the OIDC server.

auth.ts - OIDC Server (port 4001)

import { Keyv } from 'keyv';
import { KeyvTigris } from '@tigrisdata/keyv-tigris';
import { createOidcServer, OidcClient } from 'mcp-oidc-provider/oidc';
import { type JWKS } from 'mcp-oidc-provider';

const OIDC_PORT = 4001;
const OIDC_BASE_URL = process.env.OIDC_BASE_URL ?? `http://localhost:${OIDC_PORT}`;

// Use a persistent store so both servers can access the same data
const store = new Keyv({ store: new KeyvTigris() });

// Parse JWKS from environment variable (required for production)
const jwks: JWKS | undefined = process.env.JWKS ? JSON.parse(process.env.JWKS) : undefined;

const oidcServer = createOidcServer({
  idpClient: new OidcClient({
    issuer: 'https://your-tenant.auth0.com', // or any OIDC issuer
    clientId: process.env.OIDC_CLIENT_ID!,
    clientSecret: process.env.OIDC_CLIENT_SECRET!,
    redirectUri: `${OIDC_BASE_URL}/oauth/callback`,
  }),
  store,
  secret: process.env.SESSION_SECRET!,
  port: OIDC_PORT,
  baseUrl: OIDC_BASE_URL,
  jwks,
});

await oidcServer.start();

mcp.ts - MCP Server (port 3001)

import express from 'express';
import { Keyv } from 'keyv';
import { KeyvTigris } from '@tigrisdata/keyv-tigris';
import { mcpAuthRouter } from '@modelcontextprotocol/sdk/server/auth/router.js';
import { ProxyOAuthServerProvider } from '@modelcontextprotocol/sdk/server/auth/providers/proxyProvider.js';
import { requireBearerAuth } from '@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js';
import { createMcpAuthProvider } from 'mcp-oidc-provider/mcp';

const OIDC_BASE_URL = process.env.OIDC_BASE_URL ?? 'http://localhost:4001';
const MCP_PORT = 3001;
const MCP_BASE_URL = process.env.MCP_BASE_URL ?? `http://localhost:${MCP_PORT}`;

// Same persistent store as the OIDC server
const store = new Keyv({ store: new KeyvTigris() });

const mcpApp = express();

// Get config for ProxyOAuthServerProvider
const { proxyOAuthServerProviderConfig, mcpRoutes, resourceMetadataUrl } = createMcpAuthProvider({
  oidcBaseUrl: OIDC_BASE_URL,
  store,
  mcpServerBaseUrl: MCP_BASE_URL,
});

// Create auth provider
const authProvider = new ProxyOAuthServerProvider(proxyOAuthServerProviderConfig);

// Mount routes (includes CORS, health check, and protected resource metadata)
mcpApp.use(mcpRoutes);

// Install MCP auth router
mcpApp.use(
  mcpAuthRouter({
    provider: authProvider,
    issuerUrl: new URL(OIDC_BASE_URL),
    baseUrl: new URL(MCP_BASE_URL),
  })
);

// Protected MCP endpoint
mcpApp.use(express.json());
mcpApp.post(
  '/mcp',
  requireBearerAuth({ verifier: authProvider, resourceMetadataUrl }),
  async (req, res) => {
    // Your MCP handler here
  }
);

mcpApp.listen(MCP_PORT);

Option 2: MCP Server with Integrated OIDC

For simpler deployments where OIDC and MCP run in the same Express app. See the mcp-integrated-oidc example.

import { Keyv } from 'keyv';
import { setupMcpExpress } from 'mcp-oidc-provider/mcp';
import { OidcClient } from 'mcp-oidc-provider/oidc';
import { type JWKS } from 'mcp-oidc-provider';

// Parse JWKS from environment variable (required for production)
const jwks: JWKS | undefined = process.env.JWKS ? JSON.parse(process.env.JWKS) : undefined;

const { app, handleMcpRequest } = setupMcpExpress({
  idpClient: new OidcClient({
    issuer: 'https://your-tenant.auth0.com', // or any OIDC issuer
    clientId: process.env.OIDC_CLIENT_ID!,
    clientSecret: process.env.OIDC_CLIENT_SECRET!,
    redirectUri: `${process.env.BASE_URL}/oauth/callback`,
  }),
  store: new Keyv(),
  baseUrl: process.env.BASE_URL!,
  secret: process.env.SESSION_SECRET!,
  jwks,
});

// Handle MCP requests - user is available via req.user
handleMcpRequest(async (req, res) => {
  console.log('Authenticated user:', req.user);
  // Your MCP server logic here
});

app.listen(3000);

Configuration

OidcClientConfig

The OidcClient works with any OIDC-compliant identity provider. It uses OIDC Discovery to automatically configure endpoints.

| Option | Type | Required | Description | | ---------------------- | -------------------------------------------------------------- | -------- | ----------------------------------------------------------------- | | issuer | string | Yes | OIDC issuer URL (e.g., https://your-tenant.auth0.com) | | clientId | string | Yes | OAuth client ID | | clientSecret | string | Yes | OAuth client secret | | redirectUri | string | Yes | OAuth callback URL | | scopes | string | No | OAuth scopes (default: openid email profile) | | additionalAuthParams | Record<string, string> | No | Additional authorization parameters (e.g., { audience: '...' }) | | extractCustomData | (claims: UserClaims) => Record<string, unknown> \| undefined | No | Extract provider-specific data from ID token claims |

Provider Examples

// Auth0
new OidcClient({
  issuer: 'https://your-tenant.auth0.com',
  clientId: process.env.AUTH0_CLIENT_ID!,
  clientSecret: process.env.AUTH0_CLIENT_SECRET!,
  redirectUri: 'https://your-app.com/oauth/callback',
  scopes: 'openid email profile offline_access',
  additionalAuthParams: { audience: 'https://your-api.com' },
});

// Clerk (note: doesn't support offline_access)
new OidcClient({
  issuer: 'https://your-app.clerk.accounts.dev',
  clientId: process.env.CLERK_CLIENT_ID!,
  clientSecret: process.env.CLERK_CLIENT_SECRET!,
  redirectUri: 'https://your-app.com/oauth/callback',
  extractCustomData: (claims) => {
    if (claims['org_id']) {
      return {
        organization: {
          id: claims['org_id'],
          slug: claims['org_slug'],
          role: claims['org_role'],
        },
      };
    }
  },
});

// Okta
new OidcClient({
  issuer: 'https://your-domain.okta.com',
  clientId: process.env.OKTA_CLIENT_ID!,
  clientSecret: process.env.OKTA_CLIENT_SECRET!,
  redirectUri: 'https://your-app.com/oauth/callback',
  extractCustomData: (claims) => {
    if (claims['groups']) {
      return { groups: claims['groups'] };
    }
  },
});

// Keycloak
new OidcClient({
  issuer: 'https://keycloak.example.com/realms/my-realm',
  clientId: process.env.KEYCLOAK_CLIENT_ID!,
  clientSecret: process.env.KEYCLOAK_CLIENT_SECRET!,
  redirectUri: 'https://your-app.com/oauth/callback',
});

// Microsoft Azure AD
new OidcClient({
  issuer: `https://login.microsoftonline.com/${tenantId}/v2.0`,
  clientId: process.env.AZURE_CLIENT_ID!,
  clientSecret: process.env.AZURE_CLIENT_SECRET!,
  redirectUri: 'https://your-app.com/oauth/callback',
});

// Google
new OidcClient({
  issuer: 'https://accounts.google.com',
  clientId: process.env.GOOGLE_CLIENT_ID!,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
  redirectUri: 'https://your-app.com/oauth/callback',
});

createOidcServer Options (from mcp-oidc-provider/oidc)

| Option | Type | Required | Description | | ----------------------- | ------------- | -------- | ---------------------------------------- | | idpClient | IOidcClient | Yes | OIDC client instance | | store | Keyv | Yes | Keyv instance for storage | | secret | string | Yes | Secret for signing cookies/sessions | | port | number | Yes | Port to listen on | | baseUrl | string | Yes | Base URL of the OIDC server | | jwks | JWKS | No | Custom JWKS for signing tokens | | isProduction | boolean | No | Production mode flag | | sessionMaxAge | number | No | Session max age in ms (default: 30 days) | | additionalCorsOrigins | string[] | No | Additional origins to allow for CORS | | onListen | function | No | Callback when server starts |

setupMcpExpress Options (from mcp-oidc-provider/mcp)

| Option | Type | Required | Description | | ----------------------- | ------------------ | -------- | ---------------------------------------- | | idpClient | IOidcClient | Yes | OIDC client instance | | store | Keyv | Yes | Keyv instance for storage | | baseUrl | string | Yes | Base URL of the server | | secret | string | Yes | Secret for signing cookies/sessions | | jwks | JWKS | No | Custom JWKS for signing tokens | | isProduction | boolean | No | Production mode flag | | sessionMaxAge | number | No | Session max age in ms (default: 30 days) | | additionalCorsOrigins | string[] | No | Additional origins to allow for CORS | | customMiddleware | RequestHandler[] | No | Custom middleware to run after CORS |

createMcpAuthProvider Options (from mcp-oidc-provider/mcp)

| Option | Type | Required | Description | | ------------------ | ------------------ | -------- | ----------------------------------------------------------- | | oidcBaseUrl | string | Yes | Base URL of the OIDC server (e.g., http://localhost:4001) | | store | Keyv | Yes | Same Keyv instance used by OIDC server | | mcpServerBaseUrl | string | Yes | Base URL of your MCP server | | mcpEndpointPath | string | No | MCP endpoint path (default: /mcp) | | scopesSupported | string[] | No | Supported OAuth scopes | | jwksCacheOptions | JwksCacheOptions | No | JWKS cache settings (default: 30s cooldown, 10min cache) |

Accessing IdP Tokens

When you need to call upstream APIs, use the getIdpTokens helper. It works with both authentication patterns:

import { getIdpTokens } from 'mcp-oidc-provider/mcp';

// Works with setupMcpExpress (req.user)
handleMcpRequest(async (req, res) => {
  const tokens = getIdpTokens(req.user);
  if (tokens) {
    const userInfo = await fetch('https://my-api.com/api/userInfo', {
      headers: { Authorization: `Bearer ${tokens.accessToken}` },
    });
  }
});

// Works with requireBearerAuth (req.auth) - standalone OIDC setup
app.post('/mcp', requireBearerAuth({ verifier: authProvider }), async (req, res) => {
  const tokens = getIdpTokens(req.auth);
  if (tokens) {
    // tokens.accessToken - IdP access token
    // tokens.idToken - IdP ID token
    // tokens.refreshToken - IdP refresh token
  }
});

The IdpTokenSet interface:

interface IdpTokenSet {
  accessToken: string; // Access token for calling IdP APIs
  idToken: string; // ID token containing user identity
  refreshToken: string; // Refresh token for obtaining new access tokens
  expiresAt?: number; // Unix timestamp when access token expires
}

You can also access tokens directly if preferred:

  • req.user.tokenSet (with setupMcpExpress)
  • req.auth.extra.idpTokens (with requireBearerAuth)

Custom Identity Provider

For advanced use cases, you can implement the IOidcClient interface directly:

import type { IOidcClient, AuthorizationParams, TokenSet, UserClaims } from 'mcp-oidc-provider';

class MyOidcClient implements IOidcClient {
  async createAuthorizationUrl(): Promise<AuthorizationParams> {
    // Generate authorization URL with PKCE, state, and nonce
  }

  async exchangeCode(
    callbackUrl: string,
    codeVerifier: string,
    expectedState: string,
    expectedNonce?: string
  ): Promise<TokenSet> {
    // Exchange authorization code for tokens
  }

  async refreshToken(refreshToken: string): Promise<TokenSet> {
    // Refresh access token
  }

  parseIdToken(idToken: string): UserClaims {
    // Decode ID token claims
  }

  // Optional: Extract custom data from claims
  extractCustomData?(claims: UserClaims): Record<string, unknown> | undefined {
    // Return any custom data you want stored in the user session
  }
}

Storage Backends

The package uses Keyv for storage abstraction. The store is used to persist:

  • OAuth Clients - Dynamically registered client applications (via DCR)
  • Authorization Codes - Short-lived codes exchanged for tokens
  • Access Tokens - Tokens used to authenticate API requests
  • Refresh Tokens - Long-lived tokens used to obtain new access tokens
  • User Sessions - Authenticated user information and IdP tokens
  • Interaction Sessions - OAuth flow state (PKCE, nonce, redirect URIs)
  • Grants - User consent records for client applications

You can use any Keyv-compatible backend. For production, use a persistent store like Tigris or Redis.

In-Memory (Development Only)

import { Keyv } from 'keyv';
const store = new Keyv();

Warning: In-memory storage loses all data on restart and is not shared across server instances. Do not use in production or distributed deployments.

Tigris (Recommended for Production)

import { Keyv } from 'keyv';
import { KeyvTigris } from 'keyv-tigris';

const store = new Keyv({
  store: new KeyvTigris(),
});

OIDC Endpoints

When using createOidcServer, the following endpoints are available:

| Endpoint | Description | | --------------------------------------- | --------------------------- | | GET /authorize | Authorization endpoint | | POST /token | Token endpoint | | POST /token/revocation | Token revocation endpoint | | POST /register | Dynamic Client Registration | | GET /jwks | JSON Web Key Set | | GET /.well-known/openid-configuration | OIDC Discovery | | GET /oauth/callback | IdP callback handler | | GET /health | Health check |

MCP Client Support

The provider automatically handles Dynamic Client Registration for MCP clients, including support for custom protocol URIs:

  • cursor:// - Cursor IDE
  • vscode:// - VS Code
  • windsurf:// - Windsurf

License

MIT