Skip to content

heroku/oauth-provider-adapters-for-mcp

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

42 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

OAuth Provider Adapter Library for MCP Servers

An OAuth/OIDC adapter framework designed to make adding identity providers to Remote MCP servers simple, consistent, and testable.

This project provides a base adapter contract, structured logging, and a standards-compliant OIDC provider implementation (discovery, PKCE S256, code exchange, refresh) with normalized errors and configuration validation.

Benefits

  • Normalize provider integrations behind a single contract
  • Enforce consistent error handling and structured, PII-safe logging
  • Support OIDC discovery + PKCE, authorization code exchange, and token refresh
  • Make adapters independently testable with clear types and acceptance criteria

Code Quality and Testing

This project uses pnpm for package management and includes comprehensive code quality tools to maintain high standards.

Available Scripts

A list of useful scripts when developing against the codebase:

# Run the full Mocha test suite with c8 coverage reporting
pnpm test

# Check code quality with ESLint
pnpm lint

# Automatically fix linting issues and format code with Prettier
pnpm format

# Run TypeScript type checking on *.ts files
pnpm type-check

# All code quality checks and tests
pnpm check

# Run the continuous integration checks (linting, type checking, and tests):
pnpm ci

Implementing OIDC in a Remote MCP Server (with Auth0 Example)

This guide shows how to integrate oauth-provider-adapters-for-mcp into a remote MCP server and implement an OIDC provider using both discovery and static metadata. It uses remote MCP with Auth0 from Cloudflare as an example.

Prerequisites

  • Node.js ≥ 20
  • An OIDC provider (for example, Auth0 Tenant)
  • A remote MCP server (for example, Cloudflare Workers or Node server)
  • MCP Remote Auth Proxy - An MCP auth proxy within your Heroku app that enables you to use a remote MCP server

Install

  • npm:

    npm install @heroku/oauth-provider-adapters-for-mcp
  • pnpm:

    pnpm add @heroku/oauth-provider-adapters-for-mcp
  • yarn:

    yarn add @heroku/oauth-provider-adapters-for-mcp

Choose a Configuration Mode

You can configure the OIDCProviderAdapter with:

  • Discovery: Provide an issuer (recommended)
  • Static metadata: Provide metadata (useful in restricted environments)

You must provide exactly one of issuer or metadata.

Quickstart with Auth0 (Discovery)

Auth0 issuer pattern: https://<your-tenant>.auth0.com

import { OIDCProviderAdapter } from '@heroku/oauth-provider-adapters-for-mcp';

const adapter = new OIDCProviderAdapter({
  clientId: process.env.IDENTITY_CLIENT_ID!,
  clientSecret: process.env.IDENTITY_CLIENT_SECRET,
  issuer: `https://${process.env.AUTH0_TENANT}.auth0.com`,
  scopes: ['openid', 'profile', 'email', 'offline_access'],
  // Redirect URI must match your app registration
  redirectUri: process.env.IDENTITY_REDIRECT_URI,
  // Auth0 often requires audience for API access (optional)
  customParameters: process.env.AUTH0_AUDIENCE
    ? { audience: process.env.AUTH0_AUDIENCE }
    : undefined,
});

await adapter.initialize();

// Begin login flow (inside your authorize endpoint)
const state = crypto.randomUUID();
const authUrl = await adapter.generateAuthUrl(
  state,
  process.env.IDENTITY_REDIRECT_URI!
);
// Redirect user to authUrl

// Handle OAuth callback
// Retrieve the `code` from the OAuth callback query parameters (for example, `req.query.code` or `event.queryStringParameters.code`)
// Retrieve the PKCE `code_verifier` you previously stored for this interaction (for example, from a secure session, database, or in-memory store keyed by `state`)
const tokens = await adapter.exchangeCode(
  code,
  codeVerifier,
  process.env.IDENTITY_REDIRECT_URI!
);
// tokens: { accessToken, refreshToken?, idToken?, expiresIn?, scope? }

// Later: refresh token
const refreshed = await adapter.refreshToken(tokens.refreshToken!);

Environment variables commonly used with Auth0:

IDENTITY_CLIENT_ID=<auth0 client id>
IDENTITY_CLIENT_SECRET=<auth0 client secret>
AUTH0_TENANT=<tenant subdomain>
AUTH0_AUDIENCE=<optional resource API identifier>
IDENTITY_REDIRECT_URI=https://<your-remote-mcp-host>/oauth/callback

For most use cases, you can simplify configuration by using the fromEnvironmentAsync convenience helper. It reduces boilerplate and helps ensure your adapter is configured consistently across environments. It's especially useful in production or CI/CD setups, where secrets and configuration are injected via environment variables, and helps prevent accidental misconfiguration. This helper automatically reads all required OIDC configurations from the supported environment variables.

Supported environment variables:

  • IDENTITY_CLIENT_ID -> clientId
  • IDENTITY_CLIENT_SECRET -> clientSecret
  • IDENTITY_SERVER_URL -> issuer (for OIDC discovery)
  • IDENTITY_SERVER_METADATA_FILE -> metadata (static metadata file, skips discovery)
  • IDENTITY_REDIRECT_URI -> redirectUri
  • IDENTITY_SCOPE -> scopes (split by spaces and commas)

You can still override or extend the configuration by passing additional options, such as customParameters for provider-specific needs (for example, Auth0's audience).

Using Static Metadata Example (No Discovery)

Fetch your provider’s metadata from /.well-known/openid-configuration, then embed a subset:

import { OIDCProviderAdapter } from '@heroku/oauth-provider-adapters-for-mcp';

const adapter = new OIDCProviderAdapter({
  clientId: process.env.IDENTITY_CLIENT_ID!,
  clientSecret: process.env.IDENTITY_CLIENT_SECRET,
  scopes: ['openid', 'profile', 'email', 'offline_access'],
  redirectUri: process.env.IDENTITY_REDIRECT_URI,
  metadata: {
    issuer: `https://${process.env.AUTH0_TENANT}.auth0.com`,
    authorization_endpoint: `https://${process.env.AUTH0_TENANT}.auth0.com/authorize`,
    token_endpoint: `https://${process.env.AUTH0_TENANT}.auth0.com/oauth/token`,
    jwks_uri: `https://${process.env.AUTH0_TENANT}.auth0.com/.well-known/jwks.json`,
    response_types_supported: ['code'],
    grant_types_supported: ['authorization_code', 'refresh_token'],
    code_challenge_methods_supported: ['S256'],
    subject_types_supported: ['public'],
    id_token_signing_alg_values_supported: ['RS256'],
  },
  customParameters: process.env.AUTH0_AUDIENCE
    ? { audience: process.env.AUTH0_AUDIENCE }
    : undefined,
});

await adapter.initialize();

PKCE State Storage

OIDCProviderAdapter requires storing the PKCE verifier securely between the authorize and callback steps. In development, an in-memory mock is used. In production, you must provide a durable storageHook:

interface PKCEStorageHook {
  storePKCEState(
    interactionId: string,
    state: string,
    codeVerifier: string,
    expiresAt: number
  ): Promise<void>;
  retrievePKCEState(
    interactionId: string,
    state: string
  ): Promise<string | null>;
  cleanupExpiredState(beforeTimestamp: number): Promise<void>;
}

Examples: Heroku Key-Value Store, Redis, or your database.

Connecting to a Remote MCP Server

At minimum, your server needs routes that:

  • Start the auth flow and redirect to await adapter.generateAuthUrl(...)
  • Handle the callback, verify state, load code_verifier, and call adapter.exchangeCode(...)
  • Optionally expose a refresh path that calls adapter.refreshToken(...)

Error Handling and Logging

Errors are normalized to:

type OAuthError = {
  statusCode: number;
  error: string;
  error_description?: string;
  endpoint?: string;
  issuer?: string;
};

Logger Injection

The adapter performs PII-safe, structured logging and retries with backoff for discovery. In addition, we support logger injection with LogTransport so logs integrate with your observability stack. Here's an example of how to demo logging capabilities using the winston logger used in mcp-remote-auth-proxy.

  1. Import required types:
import {
  fromEnvironmentAsync,
  DefaultLogger,
  LogLevel,
} from '@heroku/oauth-provider-adapters-for-mcp';
import winstonLogger from './winstonLogger.js';
  1. Create a LogTransport wrapper:
// Create a LogTransport that wraps Winston
const winstonTransport = {
  log: (message) => {
    // Winston child logger preserves request context and Splunk formatting
    const contextLogger = winstonLogger.child({ component: 'oidc-adapter' });
    contextLogger.info(message);
  },
  error: (message) => {
    const contextLogger = winstonLogger.child({ component: 'oidc-adapter' });
    contextLogger.error(message);
  },
};
  1. Create DefaultLogger with the Winston transport:
// Create DefaultLogger that uses Winston as transport
const adapterLogger = new DefaultLogger(
  { component: 'oidc-adapter' }, // Base context
  {
    level: LogLevel.Info,
    redactPaths: [], // DefaultLogger already has OAuth redaction built-in
  },
  winstonTransport // Use Winston as the transport
);
  1. Pass the logger to the adapter:
const oidcAdapter = await fromEnvironmentAsync({
  env: adapterEnv,
  storageHook,
  defaultScopes: IDENTITY_SCOPE_parsed,
  logger: adapterLogger,
});

Development Workflow

For the best development experience:

  1. Before starting work: Ensure dependencies are installed with pnpm install.
  2. During development: Run pnpm type-check periodically to catch type errors early.
  3. Before committing: Run pnpm check to ensure all quality standards are met.
  4. Fix issues quickly: Use pnpm format to auto-fix formatting and linting issues.

Testing Requirements

Tests are located in src/**/*.test.ts and run against the compiled JavaScript in dist/cjs/. The test suite includes:

  • Unit tests for all public APIs
  • Mock-based testing for external dependencies
  • Coverage reporting with c8 (HTML and text-summary)

Tests automatically run in silent mode (LOG_LEVEL=silent) to keep output clean.

Build Outputs

The library produces dual builds for maximum compatibility:

  • CommonJS (dist/cjs/): Use for Node.js and older bundlers
  • ES Modules (dist/esm/): Use for modern bundlers and tree-shaking support

Both outputs include TypeScript declaration files (.d.ts) for type information.

License

Apache-2.0. See LICENSE for details.

Contributing

We welcome issues and PRs. Please follow conventional commits, keep changes under 200 lines per commit, and ensure tests and type checks pass. See CONTRIBUTING.md for details.

About

An OAuth provider framework that enables remote MCP servers to handle OAuth connections with minimal friction.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

No packages published