Everything the client sees comes from the upstream server; this only adds the token. Two details make it worth using over hand-rolled auth: endpoint discovery usually removes the OAuth configuration entirely, and the refresh token lands encrypted at mode `0600` rather than in plain text beside your config.
A Node proxy. Your client launches it over stdio; it forwards every JSON-RPC message to an OAuth2-protected HTTP MCP server with a bearer token attached, and handles login, caching and refresh on its own.
- An interactive `authorization_code` login with PKCE and a built-in local browser callback listener, so no code is copied by hand
- A refresh token cached on disk under AES-256-GCM with mode `0600`, so the browser opens once per machine
- A `client_credentials` grant for headless and service-to-service use
- OAuth endpoints discovered from the upstream through RFC 9728 and RFC 8414, which usually means no OAuth configuration at all
- Proactive token refresh with skew, in-flight de-duplication, and a 401 → invalidate → retry loop
- Streamable-HTTP upstreams handled properly: single-shot JSON, SSE `text/event-stream` responses, the optional server-notification channel, and `Mcp-Session-Id`
- Stderr-only logging with tokens, secrets and `Authorization` headers redacted, leaving stdout a clean JSON-RPC channel
Node.js 20+, an OAuth2-protected MCP server speaking the Streamable HTTP transport, and an OAuth2 client registered with your identity provider. For the interactive flow, register `http://127.0.0.1:53682/callback` as a redirect URI, or whatever you set `OAUTH2_CALLBACK_PORT` to. Launch with `npx -y mcp-oauth2-proxy` and configure through `UPSTREAM_URL`, `OAUTH2_GRANT`, `OAUTH2_CLIENT_ID`, `OAUTH2_CLIENT_SECRET`, `OAUTH2_TOKEN_URL`, `OAUTH2_SCOPE`, `OAUTH2_RESOURCE` and `LOG_LEVEL`, or with a JSON file named by `MCP_PROXY_CONFIG`. The package is `mcp-oauth2-proxy` 0.2.0.
One command plus a key — npx -y mcp-oauth2-proxy, then supply credentials
