docs: add CLAUDE.md + README
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
# intervalsicu-mcp-auth
|
||||
|
||||
The **OAuth 2.1 / OIDC authorization server** (identity provider) for the self-hosted, multi-tenant
|
||||
Intervals.icu MCP product. Built on [Better Auth](https://better-auth.com) (TypeScript, plain Node
|
||||
HTTP server), served at `https://intervalsicu.farhoodlabs.com/api/auth`.
|
||||
|
||||
## Role in the system
|
||||
|
||||
Three services share one host and one CloudNativePG Postgres database:
|
||||
|
||||
- **intervalsicu-mcp** — Python/FastMCP MCP server at `/mcp`. Verifies this server's EdDSA-signed
|
||||
JWT access tokens against its JWKS, then resolves per-user Intervals.icu credentials by the token
|
||||
`sub`.
|
||||
- **intervalsicu-mcp-ui** — Python portal at `/portal`. An OIDC client of this auth server.
|
||||
- **intervalsicu-mcp-auth** (this repo) — the authorization server both depend on.
|
||||
|
||||
Users sign in with **Google** (Apple optional). This server:
|
||||
|
||||
- Provides **Dynamic Client Registration** (RFC 7591) so Claude's MCP connector self-registers.
|
||||
- Uses **PKCE**.
|
||||
- Issues **EdDSA-signed JWT access tokens** with a JWKS endpoint, so the MCP server verifies them
|
||||
RFC 9068 style (with `aud` = the requested `resource`).
|
||||
- The token `sub` is this service's user id — the shared identity the MCP server and portal both key
|
||||
per-user Intervals credentials on.
|
||||
|
||||
## Layout
|
||||
|
||||
- `src/auth.ts` — Better Auth config: `jwt()` + `oauthProvider()` plugins, Google/Apple social
|
||||
providers (env-gated), shared `pg.Pool`.
|
||||
- `src/server.ts` — Node HTTP server; custom `/login`, `/consent`, `/healthz` routes, everything
|
||||
else delegated to Better Auth.
|
||||
- `src/migrate.ts` — applies Better Auth's schema and seeds the portal OIDC client (run as a k8s
|
||||
initContainer).
|
||||
|
||||
See [`CLAUDE.md`](./CLAUDE.md) for the detailed architecture (why `jwt()` comes before
|
||||
`oauthProvider()`, why `validAudiences` matters, and why `/login` and `/consent` are custom routes).
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
npm ci # install
|
||||
npm run typecheck # tsc --noEmit (the CI gate; there are no tests)
|
||||
npm run build # tsc -> dist/
|
||||
npm run dev # tsx watch src/server.ts (hot reload)
|
||||
npm run migrate # apply schema + seed the portal client
|
||||
npm start # node dist/server.js
|
||||
```
|
||||
|
||||
## Key environment variables
|
||||
|
||||
| Var | Notes |
|
||||
| --- | --- |
|
||||
| `BETTER_AUTH_URL` | **required** — public base URL. |
|
||||
| `BETTER_AUTH_SECRET` | **required** — signing secret. |
|
||||
| `AUTH_DATABASE_URL` | **required** — Postgres (shared CNPG DB). |
|
||||
| `GOOGLE_CLIENT_ID` / `GOOGLE_CLIENT_SECRET` | enable Google login. |
|
||||
| `APPLE_CLIENT_ID` / `APPLE_CLIENT_SECRET` | enable Apple login (optional). |
|
||||
| `VALID_AUDIENCES` | comma-separated allowed token audiences / RFC 8707 resources (has default). |
|
||||
| `TRUSTED_ORIGINS` | comma-separated trusted origins. |
|
||||
| `LOG_LEVEL` | `debug` / `info` (default `info`). |
|
||||
| `PORT` | HTTP port (default `8080`). |
|
||||
| `PORTAL_CLIENT_ID` / `PORTAL_REDIRECT_URI` | portal client seeded by `migrate.ts` (have defaults). |
|
||||
| `DEBUG_EMAIL_PASSWORD` | **debug only** — `=true` enables email/password auth for headless OAuth debugging. Keep off in normal operation. |
|
||||
|
||||
## Deployment
|
||||
|
||||
Push to `main` triggers Gitea CI (`git.farh.net`): typecheck, then build and push the Docker image
|
||||
to `git.farh.net/farhoodlabs/intervalsicu-mcp-auth`. Flux (GitOps) deploys it on Kubernetes, running
|
||||
`node dist/migrate.js` as an initContainer. Served under `/api/auth` (+ `/login`, `/consent`) behind
|
||||
the cluster gateway.
|
||||
Reference in New Issue
Block a user