Skip to main content
Glama
manio143

mcp-auth-proxy

by manio143
README.md
# mcp-auth-proxy

Stdio-to-HTTP MCP proxy with OAuth 2.1 authentication (PKCE + browser login).

Bridges the gap when your LLM harness only supports stdio MCP servers but the remote MCP server requires OAuth (e.g., Microsoft Entra ID).

```
LLM Harness (stdio only)
  ↕ stdin/stdout (JSON-RPC)
mcp-auth-proxy
  ↕ Streamable HTTP + Bearer token
Remote MCP Server (OAuth-protected)
```

## Features

- **RFC 9728 discovery** — auto-discovers authorization server and scopes from the MCP server's protected resource metadata
- **OAuth 2.1 + PKCE** — browser-based login, no client secrets needed (public client)
- **Random callback port** — each instance uses a random port, safe to run multiple instances simultaneously
- **Auto token refresh** — handled transparently by the SDK transport
- **In-memory only** — tokens are never written to disk
- **Full proxy** — tools, resources, and prompts are all proxied

## Installation

> ⚠️ **This package is not published on npm.** Install from source using `npm link`.

```bash
git clone https://github.com/manio143/mcp-stdio-proxy.git
cd mcp-stdio-proxy
npm install
npm run build
npm link
```

## Usage

```bash
# Basic — discovers client ID from server metadata
mcp-auth-proxy https://mcp.company.com/api

# With explicit client ID
mcp-auth-proxy https://mcp.company.com/api --clientId=my-app-client-id
```

### In MCP client config (e.g., Claude Desktop, VS Code)

```json
{
  "mcpServers": {
    "my-server": {
      "command": "mcp-auth-proxy",
      "args": ["https://mcp.company.com/api", "--clientId=my-app-id"]
    }
  }
}
```

## How authentication works

1. Proxy probes the remote MCP endpoint → gets `401`
2. Parses `WWW-Authenticate` header for resource metadata URL (RFC 9728)
3. Fetches protected resource metadata → finds authorization server + scopes
4. Fetches authorization server metadata (RFC 8414 / OIDC Discovery)
5. Opens browser for OAuth 2.1 authorization code flow with PKCE
6. Receives callback on `http://localhost:{random-port}/callback`
7. Exchanges code for tokens
8. All subsequent requests include `Authorization: Bearer <token>`
9. Token refresh happens automatically when tokens expire

## Client ID resolution

1. If `--clientId` is provided, uses that
2. If the server's protected resource metadata advertises a client ID, uses that
3. If the server supports dynamic client registration (RFC 7591), registers automatically
4. Otherwise, exits with an error

## Requirements

- Node.js ≥ 20
- A browser for the OAuth login flow
- The remote MCP server must support Streamable HTTP transport

## Security

- Tokens held in memory only — process exit clears them
- PKCE prevents authorization code interception
- Callback server binds to `127.0.0.1` only
- Random port prevents port conflicts and prediction

## License

MIT