diff --git a/docs/mcptoken-implementation.md b/docs/mcptoken-implementation.md index 9b08847..f2f24f1 100644 --- a/docs/mcptoken-implementation.md +++ b/docs/mcptoken-implementation.md @@ -126,8 +126,9 @@ The extracted `parseRoleBinding` helper is what PR 3's `mcpctl create mcptoken - ### Deploy-time steps still owed (outside this repo) -- **Pulumi (`../kubernetes-deployment`, stack `homelab`)** — add a `Deployment` named `mcplocal` in ns `mcpctl` pointing at the new image, a `Service` named `mcp` (port 3200→80), an `Ingress` for `mcp.ad.itaz.eu` with TLS via the existing cluster-issuer, a PVC `mcplocal-cache` (10Gi RWO), a Secret `mcplocal-env` with `MCPLOCAL_MCPD_URL` + `MCPLOCAL_MCPD_TOKEN`, and a NetworkPolicy mirroring mcpd's. `fulldeploy.sh` already runs `pulumi preview` first and halts on drift. -- **mcplocal's own identity** — recommend minting a dedicated `ServiceAccount:mcplocal-http` subject in mcpd with a non-expiring session token and putting it in `MCPLOCAL_MCPD_TOKEN`. The current session-minting path expires after 30d. +- **Pulumi (`../kubernetes-deployment`, stack `homelab`)** — add a `Deployment` named `mcplocal` in ns `mcpctl` pointing at `10.0.0.194:3012/michal/mcplocal:latest` (internal registry), a `Service` named `mcp` (port 3200→80, ClusterIP), an `Ingress` for `mcp.ad.itaz.eu` with TLS via the existing cluster-issuer, a PVC `mcplocal-cache` (10Gi RWO, mounted `/var/lib/mcplocal/cache`), and a NetworkPolicy mirroring mcpd's. Required env: **just `MCPLOCAL_MCPD_URL`** (point at `http://mcpd.mcpctl.svc.cluster.local:3100`). Optionally `MCPLOCAL_TOKEN_POSITIVE_TTL_MS` / `MCPLOCAL_TOKEN_NEGATIVE_TTL_MS` for stricter revocation. `fulldeploy.sh` already runs `pulumi preview` first and halts on drift. +- **No pod-level secret required** (revised from earlier draft) — the pod has no persistent identity to mcpd. Every inbound `Authorization: Bearer mcpctl_pat_…` is forwarded verbatim to mcpd, and mcpd's auth middleware resolves the McpToken principal. This eliminates the original `MCPLOCAL_MCPD_TOKEN` secret and its rotation story. Trade-off: a token with `--rbac=empty` can't read `/api/v1/projects/:name/servers`, but it also can't meaningfully serve MCP, so this is the right failure mode. See `src/mcplocal/src/serve.ts` header comment. +- **LLM provider config** — if any project served by this pod is `gated: true`, mount your `~/.mcpctl/config.json` as a ConfigMap at `/root/.mcpctl/config.json`. Ungated projects (proxyModel `content-pipeline` or no LLM-driven stages) need nothing. ### Test stats diff --git a/src/mcplocal/src/serve.ts b/src/mcplocal/src/serve.ts index 8b16314..9aad665 100644 --- a/src/mcplocal/src/serve.ts +++ b/src/mcplocal/src/serve.ts @@ -9,6 +9,14 @@ * - Requires MCPLOCAL_MCPD_URL to point at mcpd inside the cluster. * - Registers a token-auth preHandler on `/projects/*` and `/mcp`. * - FileCache directory honours MCPLOCAL_CACHE_DIR (wired via project-mcp-endpoint). + * + * Identity model: **the pod has no persistent identity to mcpd.** Every + * inbound request's `Authorization: Bearer mcpctl_pat_…` is forwarded + * verbatim for all downstream mcpd calls (introspect + project + * discovery). mcpd's auth middleware dispatches on the `mcpctl_pat_` + * prefix and resolves the McpToken principal. As a result there is + * deliberately no MCPLOCAL_MCPD_TOKEN env var — adding one would only + * create a rotation problem for a state we don't need. */ import { McpRouter } from './router.js'; import { createHttpServer } from './http/server.js';