Skip to main content
Glama
gangadharrr

Local OAuth + MCP Test Server

by gangadharrr
README.md
# Local OAuth + MCP Test Server

A tiny local server that reproduces GitHub's real OAuth behavior — including
its quirks — so you can test your AI Studio OAuth provider against something
you fully control, instead of debugging blind against `github.com`.

## Setup

Plain HTTP. For a public HTTPS URL, put a tunnel (ngrok, cloudflared,
tailscale funnel) in front — it terminates TLS at the edge and forwards
to this server over loopback HTTP.

```bash
npm install
cp .env.example .env   # optional - everything has working defaults
npm run dev            # tsx watch mode (TypeScript, no build step)
# or:
npm run build && npm start   # compile to dist/ then run
```

Configuration is read from a `.env` file and validated through a Zod
schema at startup. Invalid values produce a structured error report and
the process exits before binding the port.

**Precedence:** shell exports > `.env` file > built-in defaults — so
`PORT=9999 npm run dev` just works.

Booleans accept `true` / `false` / `1` / `0` / `yes` / `no` / `on` / `off`
(case-insensitive). Anything else fails with a clear error.

Server starts on `http://0.0.0.0:4587` by default, advertising
`http://localhost:4587` as its `PUBLIC_URL`.

## Environment variables

### Server / network

| Variable | Default | Purpose |
|---|---|---|
| `PORT` | `4587` | Listen port |
| `HOST` | `0.0.0.0` | Bind address. Set to `127.0.0.1` for localhost-only. |
| `PUBLIC_URL` | `http://localhost:PORT` | URL this server advertises in protocol metadata (OAuth issuer, resource identifier, discovery doc). Set when behind a tunnel so advertised identifiers match what clients see. |

### OAuth / MCP

| Variable | Default | Purpose |
|---|---|---|
| `CLIENT_ID` / `CLIENT_SECRET` | `test-client` / `test-secret` | Must match what you configure in your OAuth client (e.g. AI Studio). |
| `TOKEN_RESPONSE_MODE` | `form` | `form` \| `json` \| `always_json` — controls the token endpoint's response format. `form` mirrors GitHub's real default (returns JSON only when the client sends `Accept: application/json`); `always_json` forces JSON regardless. |
| `ISSUE_REFRESH_TOKENS` | `true` | Set `false` to skip issuing `refresh_token` alongside `access_token`. |
| `TOKEN_TTL_SECONDS` | `3600` | Access token lifetime — set low (e.g. `5`) to test expiry fast. |
| `REFRESH_TTL_SECONDS` | `86400` | Refresh token lifetime. |
| `ROTATE_REFRESH_TOKENS` | `true` | Each refresh issues a new refresh token and invalidates the old one; set `false` for "static" refresh tokens. |
| `ENABLE_DISCOVERY` | `false` | Set `true` to serve RFC 8414 / OIDC discovery metadata at `/.well-known/oauth-authorization-server` (and `/.well-known/openid-configuration`). Off by default to mirror GitHub's missing metadata. |
| `ENABLE_DCR` | `false` | Set `true` to serve `POST /register` (RFC 7591 Dynamic Client Registration). Off by default — real GitHub has no equivalent. |
| `DISABLE_AUTH` | `false` | Set `true` to bypass every client-side credential check: bearer-token middleware on `/mcp`, `client_id` / `client_secret` validation on `/oauth/token`, `client_id` and `redirect_uri` registration checks on `/oauth/authorize`, and code / refresh-token validity. With it on, `/oauth/authorize` and `/oauth/token` mint valid tokens for any input; `/mcp` accepts any request as authenticated. Also forces `ENABLE_DISCOVERY` and `ENABLE_DCR` off regardless of their values. **Local testing only** — do NOT enable this while exposing the server (PUBLIC_URL / ngrok). |
| `DCR_STORE_PATH` | `.dcr-clients.json` | Where dynamically-registered clients are persisted. Set `/dev/null` to disable persistence. |

Maintenance

ActivityMaintained
ResponsivenessNo issues