mcp-remote-auth-example
by unprototyped
README.md
# mcp-remote-auth-example
A minimal **remote** (Streamable HTTP) MCP server that is an **OAuth 2.1 resource server** — the shape the MCP authorization spec requires for remote servers. It shows the only auth code you actually own on your server:
- Publishes **Protected Resource Metadata** at `/.well-known/oauth-protected-resource` (RFC 9728).
- Rejects unauthenticated requests with `401` + a `WWW-Authenticate` header pointing at that metadata (the discovery entry point).
- Validates every access token: signature, issuer, expiry, and **audience** (RFC 8707) — the check most walkthroughs skip.
You do **not** run your own OAuth server. In production you delete the local dev issuer and point the metadata at your IdP (Auth0, Okta, Keycloak, WorkOS, Entra). The dev issuer here exists only so the whole flow runs on your machine in one command.
Pinned to `@modelcontextprotocol/sdk@1.29.0`, TypeScript 5.9, `jose` 6.
## One-command run
```bash
npm install
npm run demo
```
`npm run demo` boots the server and walks the four wire behaviors, printing the real request/response for each:
1. Unauthenticated `POST /mcp` → `401` + `WWW-Authenticate` referencing the PRM URL.
2. `GET /.well-known/oauth-protected-resource` → the RFC 9728 document.
3. A correctly audience-bound token → `initialize` + `tools/list` + `tools/call whoami` succeed.
4. A token minted for a **different** resource → `401 invalid_token` (the RFC 8707 audience check doing its job).
## Tests
```bash
npm test
```
Six guard tests cover the same behaviors plus expired and malformed tokens.
## What's yours vs. what's the dev stand-in
| File | Role | Keep in production? |
| --- | --- | --- |
| `src/auth.ts` | The resource-server guard: PRM doc + token validation + audience check. | **Yes — this is the code you own.** |
| `src/server.ts` | Remote MCP server (Streamable HTTP) with the guard in front. | Yes. |
| `src/issuer.ts`, `/jwks` | A tiny local dev authorization server so the demo runs offline. | **No — delete and use your IdP.** |
| `src/config.ts` | `resource` (your server's canonical URI = the token audience) and `issuer` (your IdP). | Yes — set from env. |
## Going to production
1. Delete `src/issuer.ts` and the `/jwks` route.
2. Set `OAUTH_ISSUER` to your IdP's issuer URL and `MCP_RESOURCE` to your server's public canonical URI.
3. Your IdP already serves its own JWKS from its metadata; `src/auth.ts` verifies against it unchanged.
## License
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessSyncing