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 🙏

© 2026 – Pkg Stats / Ryan Hefner

mcp-auth-test-server

v1.0.3

Published

Three servers that demonstrate MCP authentication with GitHub OIDC and Entra ID:

Downloads

104

Readme

MCP Auth Test Server

Three servers that demonstrate MCP authentication with GitHub OIDC and Entra ID:

| Server | Transport | Port | Command | | ----------------------------------------------------- | --------- | ---- | ---------------------- | | Local MCP for use w/stand alone service (default) | stdio | — | npm start | | Stand alone authentication exchange | HTTP | 3124 | npm run start:oidc | | Remote MCP Server w/auth built-in | HTTP | 3125 | npm run start:remote |

All three servers share the same echo tool and support the same token exchange flows.


Quick Start

npm install
cp .env.example .env   # configure environment variables
npm run build
npm start              # starts the local stdio MCP server

Servers

Local MCP Server (src/mcp-local/server.ts)

A stdio-based MCP server intended for local tool use. Reads an Entra ID access token from the MCP_ACCESS_TOKEN environment variable, verifies it using the same JWKS-based validation as the remote server, then exposes the echo tool over stdio.

MCP_ACCESS_TOKEN=<token> npm start

Requires AZURE_TENANT_ID and AZURE_CLIENT_ID to verify the token.

Remote MCP Server (src/mcp/server.ts)

An HTTP-based MCP server secured with OAuth 2.0. Uses the MCP TypeScript SDK's auth router for dynamic client registration, authorization code flow, and bearer auth on the /mcp endpoint. Also supports GitHub OIDC token exchange for machine-to-machine access.

npm run start:remote

OIDC Token Exchange Server (src/oidc-stand-alone/server.ts)

A standalone HTTP token exchange server. Accepts GitHub OIDC tokens and returns real Entra ID access tokens. Does not serve MCP — used as an upstream token provider.

npm run start:oidc

Supported Grant Types

All three grant types are supported by both the remote MCP server and the OIDC standalone server via a shared handler architecture. Each grant type extracts a GitHub OIDC JWT from the request, verifies it, and exchanges it for an Entra ID token.

JWT Bearer (urn:ietf:params:oauth:grant-type:jwt-bearer)

RFC 7523. The JWT is sent as the assertion parameter.

POST /token
grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<github-oidc-jwt>
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=<target-audience>
&scope=<optional>
&resource=<optional>

Token Exchange (urn:ietf:params:oauth:grant-type:token-exchange)

RFC 8693. The JWT is sent as subject_token.

POST /token
grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<github-oidc-jwt>
&subject_token_type=urn:ietf:params:oauth:token-type:id_token
&requested_token_type=urn:ietf:params:oauth:token-type:access_token
&audience=<target-audience>
&client_id=<optional>
&scope=<optional>
&resource=<optional>

Client Credentials (client_credentials)

OAuth 2.0 client credentials with a JWT assertion (used by Azure AD Workload Identity Federation).

POST /token
grant_type=client_credentials
&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&client_assertion=<github-oidc-jwt>
&client_id=<client-id>
&scope=<optional>

The remote MCP server also supports client_credentials with client_id + client_secret for dynamically registered clients.

Token Exchange Flow

All three grant types follow the same flow after extracting the JWT:

GitHub OIDC JWT
      │
      ▼
Verify signature + claims (JWKS)
Extract actor identity
      │
      ├── Actor in sub + stored credentials? ──▶ User-mapped flow
      │     Refresh stored Entra token              (delegated token)
      │
      └── Otherwise ──▶ Workload Identity Federation
            GitHub JWT as client assertion           (app-level token)
      │
      ▼
{ access_token, issued_token_type, token_type, expires_in }

User-mapped flow: Returns a user-delegated Entra ID token. Requires a one-time interactive login via GET /map-user?github_login=<username> to link a GitHub user to their Entra ID account.

Workload Identity Federation: Returns an app-level Entra ID token using the GitHub OIDC JWT as a federated client assertion. Requires a federated identity credential on the Entra ID app registration.


Endpoints

Remote MCP Server (port 3125)

| Endpoint | Description | | --------------------------------------------- | -------------------------------------------------------------------------------------------------- | | GET /.well-known/oauth-authorization-server | OAuth 2.0 server metadata | | GET /.well-known/oauth-protected-resource | Protected resource metadata (RFC 9728) | | POST /register | Dynamic client registration | | GET /authorize | OAuth authorization (auto-approves in demo mode) | | POST /token | Token endpoint (authorization_code, refresh_token, jwt-bearer, token-exchange, client_credentials) | | POST /revoke | Token revocation | | GET /map-user?github_login=<username> | Initiate Entra ID login to link a GitHub user | | GET /callback | Entra ID OAuth redirect callback | | POST /mcp | MCP Streamable HTTP endpoint (requires Bearer token) | | GET /mcp | SSE stream for server-to-client notifications | | DELETE /mcp | Close an MCP session |

OIDC Token Exchange Server (port 3124)

| Endpoint | Description | | --------------------------------------- | --------------------------------------------------------------- | | GET /health | Health check | | GET /map-user?github_login=<username> | Initiate Entra ID login to link a GitHub user | | GET /callback | Entra ID OAuth redirect callback | | POST /token | Token endpoint (jwt-bearer, token-exchange, client_credentials) |


Tools

| Tool | Description | Input | | ------ | -------------------------------- | ------------------------- | | echo | Echoes back the provided message | { "message": "string" } |

Available on both the local (stdio) and remote (HTTP) MCP servers.


Configuration

Required Environment Variables

| Variable | Description | | --------------------- | ------------------------------------------------------------------- | | GITHUB_OIDC_ISSUER | GitHub OIDC issuer URL(s), comma-separated | | ENTRA_AUDIENCE | Resource identifier / audience for Entra token requests | | AZURE_TENANT_ID | Entra ID tenant ID | | AZURE_CLIENT_ID | Entra ID app registration client ID | | AZURE_CLIENT_SECRET | Entra ID app registration client secret | | REDIRECT_URI | OAuth callback URL for the OIDC standalone server | | MCP_REDIRECT_URI | OAuth callback URL for the remote MCP server | | ENCRYPTION_KEY | 32-byte hex key for AES-256-GCM encryption of stored refresh tokens |

Optional Environment Variables

| Variable | Description | Default | | ------------------------- | --------------------------------------- | ----------------------------------------------- | | SKIP_ENTRA_EXCHANGE | Skip Entra ID exchange (see below) | false | | FAIL_FIRST_MCP_AUTH | Reject first /mcp request with 401 | false | | MCP_PORT | Remote MCP server port | 3125 | | PORT | OIDC standalone server port | 3124 | | HOST_URL | Public URL for the remote MCP server | http://localhost:<MCP_PORT> | | MCP_METADATA_ISSUER_URL | OAuth issuer URL in metadata | https://login.microsoftonline.com/common/v2.0 | | TOKEN_SCOPES | Space-separated Entra ID scopes | <ENTRA_AUDIENCE>/.default offline_access | | GITHUB_OIDC_JWKS_URL | Override GitHub OIDC JWKS endpoint | Derived from issuer | | MCP_ACCESS_TOKEN | Entra ID token for the local MCP server | — |

Testing Without Entra ID (SKIP_ENTRA_EXCHANGE)

Set SKIP_ENTRA_EXCHANGE=true to test the token exchange flow without an Entra ID dependency. In this mode:

  • GitHub OIDC JWTs are still cryptographically verified against the JWKS endpoint
  • The verified JWT is returned directly as the access_token (no Entra exchange)
  • Azure environment variables (AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET, REDIRECT_URI, ENCRYPTION_KEY) are not required
  • The /map-user and /callback endpoints return 404 (user mapping requires Entra)
  • The local MCP server verifies the access token as a GitHub OIDC JWT instead of an Entra JWT
  • expires_in is derived from the GitHub JWT's exp claim

Minimal configuration for skip mode:

GITHUB_OIDC_ISSUER=https://token.actions.githubusercontent.com
ENTRA_AUDIENCE=api://your-audience
SKIP_ENTRA_EXCHANGE=true

This is intended for the OIDC standalone server and local MCP server only. The remote MCP server's built-in OAuth flow (dynamic client registration, authorization code) works independently of this setting.

Testing Re-Authentication (FAIL_FIRST_MCP_AUTH)

Set FAIL_FIRST_MCP_AUTH=true to test that clients correctly handle a 401 on the /mcp endpoint and retry after re-authenticating. When enabled:

  • The first authenticated /mcp request is rejected with HTTP 401 and a WWW-Authenticate: Bearer resource_metadata="..." header
  • All subsequent requests proceed normally
  • The client must perform a new OIDC flow to obtain a fresh token and retry

This is useful for verifying that MCP clients implement the re-authentication loop correctly.

FAIL_FIRST_MCP_AUTH=true npm run start:remote

Generating an Encryption Key

node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Project Structure

src/
├── mcp-local/              # Local MCP server (stdio, default)
│   └── server.ts
├── mcp/                    # Remote MCP server (HTTP + OAuth)
│   ├── server.ts           # Express app, OAuth metadata, /mcp endpoints
│   ├── auth-provider.ts    # In-memory OAuth provider (authorization code, refresh, client_credentials)
│   └── token-exchange.ts   # Thin wrapper mounting shared routes with fallthrough
├── oidc-stand-alone/       # OIDC token exchange server (HTTP)
│   └── server.ts
└── shared/                 # Shared modules
    ├── auth/               # Token verification & identity providers
    │   ├── entra-auth.ts           # Entra ID OAuth client (code, refresh, WIF)
    │   ├── entra-token-verifier.ts # Entra ID JWT verification via JWKS
    │   ├── github-oidc.ts          # GitHub OIDC token verification & claims
    │   ├── id-token-parser.ts      # Decode Entra identity from id_tokens
    │   ├── jwt-verifier.ts         # Generic JWKS-backed JWT verifier
    │   └── token-store.ts          # Encrypted refresh token storage (AES-256-GCM)
    ├── grants/             # OAuth grant type handling
    │   ├── grant-handler.ts        # GrantHandler interface + error types
    │   ├── grant-exchange.ts       # Verify JWT → user-mapped or WIF → Entra token
    │   ├── jwt-bearer.ts           # JWT Bearer handler (RFC 7523)
    │   ├── token-exchange.ts       # Token Exchange handler (RFC 8693)
    │   ├── client-credentials.ts   # Client Credentials handler
    │   └── index.ts                # Barrel export
    ├── http/               # Express server infrastructure
    │   ├── middleware.ts           # Request/response logger + error handler
    │   └── token-exchange-routes.ts # Shared router factory (map-user, callback, POST /token)
    ├── config.ts           # Server configuration loader
    ├── constants.ts        # Grant type URIs, token types, defaults
    └── mcp-tools.ts        # MCP echo server/tool registration

Security Notes

  • Refresh tokens are encrypted at rest using AES-256-GCM
  • The data/tokens.json file and ENCRYPTION_KEY must be kept secure
  • Refresh tokens expire (typically ~90 days with sliding window) — users will need to re-login via /map-user when they expire
  • The local MCP server verifies the access token at startup; if the token expires mid-session the server must be restarted with a fresh token
  • In production, replace file-based token storage with a proper secrets store (e.g., Azure Key Vault)