DEV Community

Cover image for MCP clients can't easily authenticate with Entra. Here's the bridge.
Arnold Cartagena
Arnold Cartagena

Posted on

MCP clients can't easily authenticate with Entra. Here's the bridge.

If you work in a company using AI and MCPs, you put an MCP server on the network. You realize you need auth. The easy path is an API key, you go and generate one, paste it into every client config, done.

It works. But that key lives forever in plaintext configs. No expiry. No per-user identity. No revocation except rotating the one shared secret for everyone. It's what leaks in a git add ., a support screenshot, or a supply chain attack grabbing credentials.

It's a security nightmare.

The MCP spec says: use OAuth 2.1. The clients all support it. So you go to wire it up. This was me last week, I thought it was a simple thing. Spoiler: it isn't.

The paste-a-URL experience doesn't work as you'd expect. Here's why.

The DCR wall

You add a remote MCP server in Claude Code or claude.ai by pasting a URL. The client tries to connect, discovers it needs OAuth, and — here's the key part — registers itself automatically at the authorization server. No manual app creation. No copy-pasting a client secret. The client calls an endpoint, gets a client_id, and the whole flow just works.

That's Dynamic Client Registration (DCR). It's how "paste a URL and it connects" works.

Your identity provider doesn't have that endpoint.

Entra ID? App registrations are a human-in-the-portal operation. Cloudflare Access? It authenticates users against a policy — it's not an OAuth authorization server you can register a client against.

The MCP spec says servers should support DCR. It doesn't mandate it. So technically, a client can fall back to manual registration — hardcode a client_id, configure the redirect URI by hand, paste a secret into a config file. But that defeats the whole point: the clean, self-onboarding, "paste a URL and it connects" experience is gone. You're back to hand-rolling OAuth glue per deployment, or breaking the client's self-onboarding entirely.

That's the DCR wall. And it's the specific problem mcp-sso solves.

The bridge

mcp-sso sits between your MCP clients and your identity provider. It does three things:

  1. Speaks DCR + PKCE + consent to the client. From Claude Code's perspective, mcp-sso is a normal OAuth server. Paste the URL → it connects → browser opens → consent → token → tools work.
  2. Verifies identity through your real IdP. Cloudflare Access or Entra ID stays the source of truth. mcp-sso checks the upstream identity and rejects anything it can't verify.
  3. Mints its own tokens. Each token is scoped to your server only. Your IdP's tokens never reach the client — nothing to leak.

Try it in 30 seconds

node examples/fastify-sqlite/index.ts
# → prints a one-time code to the console

claude mcp add --transport http my-bridge http://localhost:3000/mcp
# → browser opens → approve → tool is callable
Enter fullscreen mode Exit fullscreen mode

No identity provider. No keys to generate. That's a full OAuth round-trip — register, authorize, consent, token, /mcp call — on your machine, with zero setup. The one-time code is a console-pairing identity for local dev; don't expose it publicly.

For a real IdP (Cloudflare Access or Entra), it's environment variables and a public URL. Set the config, point the client at it, done.

Does it actually work with real clients?

Yes. I drove it against five real MCP clients — Claude Code, Codex CLI, claude.ai, ChatGPT, and the official MCP SDK — all against a real Cloudflare Access tenant. Each completed the full flow: register → authorize → consent → token → /mcp tool call.

Entra ID was also tested — the redirect orchestrator against a real Entra tenant, driving Claude Code and Claude Desktop end to end. Those results are being formalized in the verification matrix.

The full provider × client matrix — what's proven, what's still open — lives in the repo.

Security

  • jose is the only runtime dependency.
  • Fail-closed everywhere: ambiguous config, wrong audience, replayed token = hard rejection.
  • Codes and tokens are hashed and single-use.
  • Published STRIDE threat model.
  • npm publishes via GitHub Actions with Sigstore provenance.

What's not done yet

Today: Cloudflare Access, Entra ID (redirect flow + group-based authorization), and console pairing. Cloudflare Access is live-verified against five clients. Entra was tested in a real enterprise deployment.

Roadmap (v0.3): generic OIDC, Google/GitHub presets, device flow, npx mcp-sso init.

If your IdP already supports DCR, you don't need this — use mcp-auth. mcp-sso is for when it doesn't.


Using Entra ID or Cloudflare Access with a remote MCP server? Open an issue with your IdP + client combo — I'll add it to the verification matrix.

MIT · npm · GitHub

Top comments (0)