mcp-oauth-gateway
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mcp-oauth-gatewaylog in to https://mcp.example.com/mcp and print my client config"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mcp-oauth-gateway
OAuth 2.1 for MCP clients that only know how to send a static Authorization header.
Many MCP clients can't authenticate against an MCP server that implements the spec's
authorization flow. They send one fixed header, never discover the authorization server, and
don't react to a 401 challenge — so the server is simply unreachable:
Error POSTing to endpoint: {"error":{"code":-32001,"message":"Bearer token required"}}
Server status: needs-auth
SDK auth failed: Dynamic Client Registration rejected (HTTP 400):
{"error":"invalid_client_metadata","error_description":"dynamic client registration is disabled..."}Hand-pasting a long-lived JWT into the client's config "works", and then goes stale: the token expires, ends up in plaintext in several config files, and every client needs its own copy.
This gateway sits on loopback and holds the credential for the client:
static header OAuth 2.1, refreshed automatically
client ───────────────▶ 127.0.0.1:33419/mcp ─────────────────────▶ your MCP server
│
└── performs RFC 9728 discovery, PKCE, and silent refresh;
keeps tokens in the OS config dir (0600), not in your configThe client keeps talking to a loopback URL with a stable local token that is not the OAuth credential, so nothing in the client's config ever changes when the OAuth token rotates.
Zero dependencies. Node ≥ 20 only (it uses the built-in fetch, node:http and node:test).
What it does on the wire
POST <mcp-url>→ reads the401and itsWWW-Authenticatechallenge.Fetches the Protected Resource Metadata (RFC 9728) the challenge points at.
Fetches the authorization server's metadata (RFC 8414, falling back to
openid-configuration).Registers a client dynamically (RFC 7591) if the server allows it; otherwise it tells you exactly what to configure instead.
Opens the browser for an authorization code + PKCE (S256) flow, with
resource=<mcp-url>(RFC 8707) so the token's audience is bound to your server.Stores the access and refresh token, and renews silently before expiry.
Nothing is vendor-specific: any MCP server that publishes protected-resource metadata and any OAuth 2.1 authorization server with PKCE works.
Related MCP server: Uno MCP Stdio
Quick start
No clone needed — it is published on npm:
# 1. Authorize once (opens a browser; stores tokens outside any repo)
npx mcp-oauth-gateway login --url https://your-mcp-host/mcp
# 2. Run the gateway
npx mcp-oauth-gateway serve --url https://your-mcp-host/mcp --port 33419
# 3. Print ready-to-paste client configuration
npx mcp-oauth-gateway print-config --url https://your-mcp-host/mcpOr from a checkout (git clone https://github.com/zmhhaha/mcp-oauth-gateway), replacing
npx mcp-oauth-gateway with node bin/mcp-oauth-gateway.mjs.
If your authorization server does not offer dynamic client registration, add --client-id <id>
to login. The error message tells you the redirect URI to register.
Pointing a client at it
DSH (DeepSeek Harness)
DSH's MCP client has no OAuth at all, so this is the intended use case.
This repository is the DSH bundle — its package.json declares dsh.bundle.patch — so the
Plugins panel can install it straight from this repository's URL or from a local checkout.
(DSH has no browsable marketplace: it installs from a package name on npm, a Git repository URL,
a tarball, or a local path, and otherwise only lists DSH's own official plugins. A package-name
install would need this package published to npm; it currently is not.)
See dsh/README.md for the two values print-config gives you, and the caveats.
Any client that accepts custom headers
{
"mcpServers": {
"your-server": {
"type": "http",
"url": "http://127.0.0.1:33419/mcp",
"headers": { "x-mcp-gateway-token": "<from print-config>" }
}
}
}Good old curl
curl -sS -X POST http://127.0.0.1:33419/mcp \
-H "x-mcp-gateway-token: <from print-config>" \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"0"}}}'Clients that do implement OAuth (Claude Code, Codex with its experimental Rust client) should keep using their own flow — this gateway is for the ones that can't.
Commands
Command | What it does |
| Interactive authorization; stores access + refresh token |
| Runs the loopback gateway until interrupted |
| Shows the stored credential — never the tokens themselves |
| Forces a refresh now (handy from a scheduled task) |
| Prints client configuration for DSH, JSON clients and curl |
| Deletes the stored credential |
Option | Meaning |
| Skip dynamic registration and use this client id |
| Loopback port (default |
| Host inside the redirect URI (default |
| Space/comma separated scopes (default |
| Issuer override for servers without an RFC 9728 challenge |
| State directory (default: the OS config dir) |
| Don't try to open a browser; print the URL instead |
Security and limitations
Loopback only. It binds
127.0.0.1and::1(both, becauselocalhostresolves to either — a mismatch here breaks the browser redirect). It refuses any request without its token.The local token is not the OAuth credential. It is stable and can live in client config files; the OAuth tokens stay in the gateway's state file (mode
0600under the OS config dir, never in a client's config).Same-user processes can read that state file. The gateway's token is readable by anything running as you — the same trust boundary as any local dev tool. Do not run it on a shared machine as a shared user.
One upstream per process, one login at a time, in-memory state. Run several gateways (one port each) for several MCP servers.
Keep it running. Tokens refresh only while the gateway runs. A refresh token has its own lifetime on the authorization server, so a gateway that stays down past it needs a fresh
login— starting it on demand works, but leaving it running is what makes the setup hands-off.No retry on a mid-flight
401. Expiry is handled proactively (including readingexpfrom the JWT when the server omitsexpires_in); a token that the server rejects anyway means re-login.chmod 0600is POSIX-only. On Windows the ACL of your own profile directory is the boundary.The gateway does not verify tokens or signatures — that is the MCP server's job. It only obtains and forwards them.
Troubleshooting
Symptom | Cause | Fix |
| The authorization server has DCR disabled (many do) | Register an application manually and pass |
| Same, and there is nothing to register against | Same |
| The token's audience is the | Accept both in the server's audience allow-list |
Login page loads, then "redirect URI mismatch" | The redirect URI is not registered | Register |
The authorization server says something like "Failed to sign in" | The URL was opened in a context that has no session there — an embedded preview/popup window, or a different browser. (Some clients open links in a webview, which also cannot finish a provider sign-in.) | Open it in the browser where you are signed in. |
Browser says success, client still unauthenticated | The client got a token but it is not bound to this server (RFC 8707 | Same as the |
Works, then stops after some days | The refresh token was revoked, or the server rotated it before Casdoor supported that | Re-run |
| Would be a bug in this gateway | Please report it |
Development
node --test # 32 tests, no network requiredThe suite covers the RFC 7636 PKCE vector, challenge and metadata parsing, the manual-client error paths, token-store semantics, the loopback proxy (including a test that proves SSE frames are streamed and not buffered), and the transport selection per upstream scheme.
Releasing
Two traps that cost real time on the first publish, both silent:
Never write a
binpath with a leading./."mcp-oauth-gateway": "./bin/x.mjs"makes npm consider the entry invalid and drop the wholebinfield from the published package —npm publishonly warns, andnpxthen fails with no obvious cause. Use"bin/x.mjs".npm pkg fixcorrects it.npm 11 has staged publishing, and a bypass-2FA token is no longer the recommended route.
npm publishmay leave the version unpublished while the registry reserves the name with a0.0.0-stageplaceholder; the metadata for a brand-new package also takes a minute to appear, so an immediatenpm viewornpx <pkg>@<version>can reportETARGETeven though the publish succeeded. Check the version endpoint (registry.npmjs.org/<pkg>/<version>) before concluding anything, and prefernpm stage publish+npm stage approveover a bypass token (npm's own guidance).npm stage listreadsGET /-/stage.
Notes on specific authorization servers
Casdoor — DCR behaviour,
aud = resource, redirect matching, and the configuration cache that quietly reverts database edits.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Self-hosted federated MCP gateway: one OAuth 2.1 MCP server in front of N apps, user-level scopes.
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
MCP server for mandates, delegation, policy-gated execution, credential grants, and audit.
- StytchOAuthdev.stytch.mcp
The Stytch MCP server is a reference implementation that demonstrates remote MCP server authentication and authorization using Stytch Connected Apps. It provides OAuth 2.1-compliant authorization (including PKCE), Dynamic Client Registration, and validates Stytch-issued access tokens to enable AI agents to securely interact with external services through permissioned access, supporting scopes like openid, email, profile, and manage:project_data.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables Claude.ai to connect to a Hermes MCP server via OAuth 2.1 authorization code flow with PKCE, acting as a reverse proxy and single-user authorization gateway.-
- AlicenseAqualityCmaintenanceLocal stdio proxy for Uno MCP Gateway that enables MCP clients without OAuth support to securely connect to authenticated remote servers.8185 PyPI1MIT
- AlicenseNot gradedqualityDmaintenanceActs as a secure OAuth 2.0/2.1 proxy gateway for MCP servers, enabling integration with Claude and ChatGPT platforms.32 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables MCP clients to connect to remote servers that have OAuth issuer mismatches (e.g., OutSystems) by relaying stdio and handling OAuth flows with optional issuer override.10 npmMIT