mcp-oauth2-proxy
mcp-oauth2-proxy
Documentation
mcp-oauth2-proxy
> A local stdio MCP server that proxies to a remote, **OAuth2-protected
> HTTP MCP server**. Drop it into Claude Desktop, Cursor, VS Code Copilot,
> or any other MCP client — log in once in your browser — done.
MCP client ─stdio (JSON-RPC)─▶ mcp-oauth2-proxy ─HTTP+SSE + Bearer─▶ upstream MCP server
│
└─ OAuth2 token endpoint (IdP)Documentation
This README is a quick start. **Full documentation lives in the
wiki.**
| Page | What's there |
|---|---|
| Getting Started | Install, first run, wiring into a client. |
| Configuration | Every field and environment variable. |
| OAuth2 Grants and Tokens | Grants, interactive login, token + cache lifecycle. |
| Discovery | RFC 9728 / RFC 8414 endpoint discovery. |
| Security | Threat model and built-in defenses. |
| Remote Hosts (SSH Port Forwarding) | Logging in when the proxy runs remotely. |
| Troubleshooting | Common problems and a FAQ. |
| Architecture · Bridge · OAuth2 internals | How it works inside. |
| Contributing and Releases | Dev setup, tests, release process. |
Contents
- Features
- How it works
- Requirements
- Install
- Quick start
- Wire it into an MCP client
- Configuration
- Security
- Development
- Out of scope
- License
Features
- Interactive `authorization_code` + PKCE flow with a built-in local
browser callback listener — no manual code copy/paste.
- Refresh-token cache on disk (AES‑256‑GCM, `0600`), so the browser
only opens once per machine.
- `client_credentials` grant for headless / service-to-service use.
- RFC 9728 + RFC 8414 discovery of token and authorization endpoints
from the upstream — usually zero OAuth config required.
- Proactive token refresh with skew, in-flight de-duplication, and a
401 → invalidate → retry loop.
- Streamable-HTTP upstream support: single-shot JSON, SSE
`text/event-stream` responses, and the optional long-lived
server-notification channel. Honors `Mcp-Session-Id`.
- Stderr-only logging (pino) with redaction of tokens, secrets, and
`Authorization` headers — stdout stays a clean JSON-RPC channel.
How it works
1. Discovery. Optionally fetch RFC 9728/8414 metadata to fill in
`tokenUrl`, `authorizationUrl`, `scope`, and `resource`.
2. Token manager. Wrap the configured `Grant` with caching,
refresh-skew, in-flight dedup, and 401 invalidation.
3. Prefetch. Call `getToken()` once at startup so the interactive
browser flow (if needed) happens before the first MCP message arrives.
4. Bridge. For each stdin JSON-RPC line, POST to `upstream.url` with a
`Bearer` token; single-shot JSON responses become one stdout line, SSE
responses one line per event. A `401` triggers `invalidate()` and a
single retry.
5. Server stream. After `initialize`, optionally hold open a
`GET text/event-stream` channel for server-initiated notifications.
For the full internals, see the
and OAuth2 Internals
wiki pages.
Requirements
- Node.js 20+
- An OAuth2-protected MCP server speaking the
Streamable HTTP MCP transport.
- An OAuth2 client registered with your IdP. For the interactive flow,
register `http://127.0.0.1:53682/callback` as a redirect URI (or
whatever you set `OAUTH2_CALLBACK_PORT` to).
Install
You don't need to install anything — MCP clients can launch the proxy
directly via `npx`:
npx -y mcp-oauth2-proxyFor development against a local checkout:
git clone https://github.com/ChengleiYuan/mcp-oauth2-proxy.git
cd mcp-oauth2-proxy
npm install
npm run buildQuick start
Interactive login (recommended for end users)
UPSTREAM_URL=https://mcp.example.com/mcp \
OAUTH2_GRANT=authorization_code \
OAUTH2_CLIENT_ID= \
npx -y mcp-oauth2-proxyOn first launch the proxy discovers the OAuth endpoints, opens your browser
for a PKCE login, captures the code on `http://127.0.0.1:53682/callback`,
caches the refresh token (encrypted) under your OS config dir, and starts the
bridge. Subsequent launches reuse the cached token silently. Details:
Headless / service account
UPSTREAM_URL=https://mcp.example.com/mcp \
OAUTH2_GRANT=client_credentials \
OAUTH2_TOKEN_URL=https://idp.example.com/oauth2/token \
OAUTH2_CLIENT_ID=my-service \
OAUTH2_CLIENT_SECRET='…' \
OAUTH2_SCOPE='mcp:read mcp:write' \
npx -y mcp-oauth2-proxyWire it into an MCP client
{
"mcpServers": {
"remote-oauth2-mcp": {
"command": "npx",
"args": ["-y", "mcp-oauth2-proxy"],
"env": {
"UPSTREAM_URL": "https://mcp.example.com/mcp",
"OAUTH2_GRANT": "authorization_code",
"OAUTH2_CLIENT_ID": ""
}
}
}
}On Windows hosts that don't resolve `.cmd` shims (so `command: "npx"`
fails to start), use the explicit form:
{
"command": "cmd",
"args": ["/c", "npx", "-y", "mcp-oauth2-proxy"]
}Paths in the `env` block must be absolute and use forward slashes on
every platform.
Configuration
The proxy can be configured by a JSON file (`MCP_PROXY_CONFIG`), by
environment variables, or a mix — env vars override file values, then the
merged result is validated. Minimal example file:
{
"upstream": { "url": "https://mcp.example.com/mcp" },
"oauth2": {
"grant": "authorization_code",
"clientId": "my-client",
"scope": "mcp:read mcp:write",
"resource": "https://mcp.example.com/mcp"
}
}See `config.example.json` for a fuller sample.
Most-used environment variables:
| Env var | Maps to |
|---|---|
| `UPSTREAM_URL` | `upstream.url` |
| `OAUTH2_GRANT` | `oauth2.grant` |
| `OAUTH2_CLIENT_ID` | `oauth2.clientId` |
| `OAUTH2_CLIENT_SECRET` | `oauth2.clientSecret` |
| `OAUTH2_TOKEN_URL` | `oauth2.tokenUrl` |
| `OAUTH2_SCOPE` | `oauth2.scope` |
| `OAUTH2_RESOURCE` | `oauth2.resource` |
| `LOG_LEVEL` | `log.level` |
> Full reference: every field, default, and environment variable is
> documented in the
> wiki page. Endpoint auto-discovery is covered in
> Discovery.
> Running the proxy on a remote machine? See
> Remote Hosts (SSH Port Forwarding).
> Hitting a snag? See
Security
- Tokens are acquired by the proxy itself; the upstream `Authorization`
header is always set by the proxy, never passed through from the client.
- Access tokens live in memory only. Refresh tokens are cached encrypted
(AES‑256‑GCM, key file mode `0600`) — honest obfuscation against casual
disk reads, not protection against a process running as the same OS
user.
- Cleartext `http://` to non-loopback hosts is rejected at startup
(override with `ALLOW_INSECURE_HTTP=true`); `https://` and loopback
`http://` are always allowed. The interactive callback listener validates
the `Host` header to defeat DNS-rebinding.
- All logs go to stderr with tokens, secrets, and `Authorization` headers
redacted; stdout is reserved for JSON-RPC.
Full threat model and defenses:
Development
npm install
npm run build # compile TS to dist/
npm run dev # tsx watch
npm test # vitest (unit + integration)The integration test spins up a mock OAuth2 token endpoint and a mock MCP
upstream in-process and drives the real bridge through `PassThrough`
streams — no network required. Project layout, the full test strategy, and
the automated release process are documented in
Out of scope
- Multiple upstream MCP servers per process
- OS-keychain-backed refresh-token storage (DPAPI / Keychain / libsecret)
- mTLS / JWT-bearer / device-code / ROPC grants
- HTTP / SSE inbound transport (this is a stdio MCP server)
License
Frequently asked questions
What is mcp-oauth2-proxy?
mcp-oauth2-proxy is mcp-oauth2-proxy
How do I install mcp-oauth2-proxy?
Open the GitHub repository and follow its README. Most MCP servers are added to your client's MCP config, then called by your agent.
Is mcp-oauth2-proxy open source?
Yes — it is hosted on GitHub at https://github.com/ChengleiYuan/mcp-oauth2-proxy and has 2 stars.
Related MCP tools
Model Context Protocol Servers
The Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra
A MCP for Claude Desktop / Claude Code / Windsurf / Cursor to build n8n workflows for you
MCP server to provide Figma layout information to AI coding agents like Cursor
The world's best AI personal assistant for email. Open source app to help you reach inbox zero fast.
Instant is the best backend for AI-coded apps. You get auth, permissions, storage, presence, and streams — everything you need to ship apps your users will love.
Run your own MCP server? See who uses it and what to fix.
Measure it with TrackMCP