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-sso

v0.2.3

Published

OAuth 2.1 for remote MCP servers: a spec-correct resource-server verifier plus a small AS-lite bridge with a pluggable upstream identity provider.

Readme

mcp-sso

OAuth in your MCP server, not an API key in your client's config.

npm CI OpenSSF Scorecard OpenSSF Best Practices license node runtime deps

Quickstart · Machine-to-machine · API-key gateway · Security · Alternatives · Status · Threat model · Live verification

The problem

Remote MCP servers need auth. The default is a static API key pasted into every client config — no expiry, no per-user identity, no revocation short of rotating the one shared secret. It's what leaks in a git add . or a support screenshot.

The MCP spec's answer is OAuth 2.1. But here's the catch: the clean self-onboarding — paste a URL and the client connects — depends on Dynamic Client Registration (DCR): the client registers itself at the identity provider automatically, no manual setup. The identity providers you actually have at work — Entra ID, Cloudflare Access, and most enterprise SSO — don't expose a DCR endpoint your client can call. So the paste-a-URL experience should just work… and against enterprise IdPs, it doesn't. You're left hand-rolling OAuth glue per deployment, or settling for manual registration that breaks the self-onboarding.

mcp-sso is the bridge. It speaks DCR, PKCE, and consent to the MCP client; your IdP stays the identity source of truth; it mints its own audience-bound tokens (each valid only for your server). Upstream IdP tokens never pass through.

sequenceDiagram
    participant C as MCP client
    participant B as mcp-sso bridge
    participant I as Your IdP (Entra / CF Access / OIDC)
    C->>B: register (DCR) + authorize (PKCE)
    B->>I: user signs in at the IdP
    I-->>B: verified identity (id_token / signed assertion)
    B-->>C: consent screen, then a bridge-minted token (audience-bound)
    C->>B: /mcp calls with the bridge token
    Note over B,I: upstream IdP tokens never pass through —<br/>the bridge mints its own

Quickstart

The fastest start needs no IdP and no keys to generate:

node examples/fastify-sqlite/index.ts
# → prints a one-time code to the console (console-pairing identity).
claude mcp add --transport http my-bridge http://localhost:3000/mcp
# → a browser opens to the consent page; approve; the tool is callable.

For a real identity provider (Cloudflare Access or Entra), set the env vars and point the client at a public https URL — see examples/fastify-sqlite/ and docs/live-verification.md for the exact setup (including the named Cloudflare tunnel the public URL needs).

What it works with

  • Identity providers: Cloudflare Access, Microsoft Entra ID (redirect flow + group→scope authorization), Google + generic OIDC sign-in, zero-setup console pairing. (GitHub preset — v0.3.)
  • Frameworks: fastify, express, hono — thin adapters; all logic is in the framework-free core.
  • Stores: node:sqlite (recommended, zero-ops), mysql2, in-memory — one shared conformance suite.
  • Grants: authorization code (PKCE S256), refresh-token rotation with theft detection, client_credentials (M2M).
  • Runtime dependency: jose only.

Machine-to-machine (client_credentials)

For headless callers — CI jobs, service agents, schedulers. Implements the official MCP extension io.modelcontextprotocol/oauth-client-credentials.

Machine clients are provisioned out-of-band — there's no HTTP endpoint for it; you run provisionMachineClient against the same ClientStore the bridge uses. ClientStore is a two-method port (save/find) you implement against your own database — the shipped /store/sqlite and /store/mysql adapters are StorePort-only (codes, refresh tokens, consent JTIs), not ClientStore. The secret is returned once and stored only as a SHA-256 hash.

import { provisionMachineClient, noopAudit } from "mcp-sso";

const { clientId, clientSecret } = await provisionMachineClient(
  { store: clientStore, catalog: config.scopeCatalog, clock: { nowMs: () => Date.now() }, audit: noopAudit },
  { name: "nightly-sync", allowedScopes: ["mcp:read"] }, // per-client scope ceiling, fixed at provisioning
);
// clientSecret (mcs_…) is returned ONCE — put it in your secret manager now; it cannot be retrieved again.
curl -s https://auth.example.com/oauth/token -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials -d scope=mcp:read
# → { "access_token": "…", "token_type": "Bearer", "expires_in": …, "scope": "mcp:read" }

Requires stored-DCR mode (dcr: { mode: "stored", store }) and clientCredentials: { enabled: true } in createBridgeConfig. No refresh token (the client already holds a durable credential). The mcc_… subject prefix and a gty: "client_credentials" marker jointly identify machine tokens — enforced at three points, detailed in docs/contracts.md §17.2. Rotate with rotateMachineClientSecret.

API-key gateway: SSO in front of a token-only backend

The common production shape: an internal MCP server that only accepts a static API key. Put mcp-sso in front — users authenticate through your real IdP, the gateway verifies its own short-lived tokens on /mcp, and the static key is injected server-side only. It never reaches an MCP client, a laptop, or a config file. Worked example: examples/api-key-gateway/; full pattern, topology, and Kubernetes notes in docs/gateway-deployment.md.

Security

  • Fail-closed everywhere — ambiguous config, a missing identity, an unknown audience, or a replayed token is a hard failure, never a degraded default.
  • jose is the only runtime dependency; every pin is ≥15 days old before we accept it; npm publishes run only through GitHub Actions with Sigstore provenance, never from a local machine.
  • Token handling: authorization codes and refresh tokens are hashed at rest and single-use (a replayed refresh token revokes its whole family); consent tokens are single-use. Access tokens are short-TTL ES256 bearer tokens — like any OAuth access token, a stolen one is valid until exp (no access-token introspection or revocation in v0.2; threat-model row 1). Separate signing keys for consent vs. access; timing-safe PKCE; redirect URIs matched against an explicit allowlist.
  • Published STRIDE threat model + a documented two-gate authorization model (IdP-side access control vs. mcp-sso's defense-in-depth allowlists): docs/threat-model.md, docs/authorization.md.

Alternatives

Does your identity provider already speak DCR/OAuth 2.1? Then you don't need a bridge — use mcp-auth (compatibility list). If it doesn't (Entra ID, Cloudflare Access, most enterprise SSO), that's exactly what mcp-sso is for.

| Project | Choose it if… | | --- | --- | | mcp-sso (this repo) | Your IdP doesn't speak DCR — Entra ID, Cloudflare Access, most enterprise SSO. | | mcp-auth | Your IdP already speaks DCR/OAuth 2.1; you just need resource-server wiring. | | mcp-oauth-server | You need device flow today (mcp-sso has client_credentials; device flow is v0.3). | | workers-oauth-provider | Your MCP server is a Cloudflare Worker. |

Status

mcp-sso is live-verified against real MCP clients — Claude Code, Codex CLI, claude.ai, ChatGPT, and the official MCP SDK on a real Cloudflare Access tenant; Entra ID (redirect flow) with Claude Code and Claude Desktop in a real enterprise deployment; and Google sign-in with Claude Code and the official MCP SDK. The full provider × client matrix lives in docs/live-verification.md.

Roadmap (v0.3)

Generic OIDC + Google/GitHub identity presets · device authorization flow (RFC 8628) · CIMD · npx mcp-sso init.

License

MIT