An OAuth token broker and reverse proxy for MCP servers.
This service sits in front of an upstream MCP endpoint, performs OAuth login (Authorization Code + PKCE), stores the refresh_token in macOS Keychain, refreshes access_token automatically, and injects Authorization: Bearer <token> for proxied MCP calls.
This proxy turns that into a one-time login + automatic token refresh flow, so your CLI keeps working against MCP without repeated manual auth.
- Discovers OAuth endpoints from MCP metadata when possible.
- Supports manual OAuth endpoint overrides via environment variables.
- Supports dynamic OAuth client registration (or fixed client via env).
- Stores refresh token and registered client in macOS Keychain.
- Exposes health/login/status/logout endpoints for operations and debugging.
- Proxies
/mcp/*(and root/for backward compatibility) to upstream MCP with auth injection.
MCP Client
|
v
mcp-auth-proxy (:5177)
|-- /oauth/login -> upstream OAuth authorize
|-- /oauth/callback -> token exchange (PKCE)
|-- refresh token in Keychain
|-- auto refresh access token
v
Upstream MCP (/links/<id>/mcp, etc.)
- Node.js 18+ (ESM runtime)
- macOS (uses
securityCLI for Keychain storage) - A reachable upstream MCP URL
npm install
export UPSTREAM_MCP_URL="https://mcp.craft.do/links/<id>/mcp"
npm run startThen open:
- Login:
http://127.0.0.1:5177/oauth/login - Status:
http://127.0.0.1:5177/oauth/status - MCP endpoint for cli(e.g. kimi-cli):
http://127.0.0.1:5177
Add the exposed MCP URL to kimi mcp:
kimi mcp add --transport http craft http://127.0.0.1:5177
kimi mcp test craftGET /health: basic process health.GET /: service info and useful URLs.GET /oauth/status: auth/discovery/token/debug status.GET /oauth/login: start OAuth login.GET {REDIRECT_PATH}: OAuth callback (default/oauth/callback).ALL /oauth/logout: clear local tokens/client cache.GET /mcp/health: MCP-facing health.ALL /mcp/*: authenticated proxy to upstream MCP.ALL /*: backward-compatible authenticated proxy to upstream root.
| Variable | Required | Default | Notes |
|---|---|---|---|
UPSTREAM_MCP_URL |
Yes | - | Upstream MCP base URL. |
PORT |
No | 5177 |
Local listen port. |
BIND |
No | 127.0.0.1 |
Local bind address. |
PUBLIC_BASE_URL |
No | - | Public base URL used to build redirect URI. |
REDIRECT_PATH |
No | /oauth/callback |
OAuth callback path. |
KEYCHAIN_SERVICE |
No | mcp-auth-proxy-refresh-token |
Keychain service namespace. |
SCOPES |
No | auto/fallback | Defaults to mcp:tools mcp:resources offline_access if needed. |
PROBE_PATH |
No | empty | Optional upstream probe path for OAuth discovery. |
AUTHORIZATION_ENDPOINT |
No | - | Must be set together with TOKEN_ENDPOINT to override discovery. |
TOKEN_ENDPOINT |
No | - | Must be set together with AUTHORIZATION_ENDPOINT. |
ISSUER |
No | - | Optional issuer metadata when endpoints are overridden. |
CLIENT_ID |
No | mcp-auth-proxy |
If unchanged, proxy may auto-register client dynamically. |
CLIENT_SECRET |
No | - | Used for env-provided confidential client mode. |
AUTO_REGISTER_CLIENT |
No | true |
Set false to disable dynamic registration. |
npm run start:bg
npm run status:bg
npm run logs:bg
npm run stop:bgRuntime artifacts are written to .run/ (pid + log file).
- This project currently has no automated tests wired (
npm testis a placeholder). - Token cache is in-memory for access token and Keychain for refresh token.
- If refresh fails (revoked/expired token), login is required again.
- For non-macOS environments, Keychain helper implementation must be replaced.
OAuth discovery failed: ensureUPSTREAM_MCP_URLpoints to the actual MCP endpoint (for Craft, usually includes/links/<id>/mcp), or set bothAUTHORIZATION_ENDPOINTandTOKEN_ENDPOINT.- No refresh token returned: ensure scopes include
offline_access, then login again. - Proxy returns
401 not_authenticated: complete/oauth/loginfirst and verify/oauth/status.
- Never commit tokens, secrets, or private MCP URLs.
- Keep
.run/andnode_modules/untracked. - Use localhost binding unless you explicitly need remote access.