Per-user OAuth (Doctrine 4) is right for a signed-in human: the Console owns the connect/refresh flow and a cartridge acts as that user. A scheduled evaluator has no human at the keyboard — interactive sign-in and token refresh are exactly the fragile parts you don't want in a nightly cron. So this is machine-to-machine: a recyclable machine token the operator mints in the Console, stores on the scheduling machine, and rotates or revokes at will. It keeps the operator's repo access on their own desktop; only the token reaches the platform, and the token can only do what its boundaries allow.
The endpoint is POST /api/mcp on the Console, checked before the Cognito JWT gate and behind
CloudFront's X-Origin-Secret. A token is tokenId + secret; the secret is encrypted at
rest (AES-256-GCM under a master key) — reversibly, so the server can recompute the HMAC, not a one-way hash.
The client signs each request:
The server recomputes the HMAC from the stored secret and compares in constant time (timingSafeEqual). The pure
auth decision (authorizeRecord) lives in shared/modules/mcp/crypto.js and is unit-tested; the DDB
lookup is thin around it. Tokens live in a single-table store ai-sandbox-console-mcp-tokens
(pk MTK#<tokenId>, plus a by-cartridge GSI for listing).
One endpoint exposes a boundary-gated tool registry (shared/modules/mcp/server.js +
console/lambda/mcp-tools.js). A token is granted one or more boundaries; the multiplexer only
surfaces the tools whose boundary the token holds — least privilege, enforced at tools/list time.
| Boundary | Tools | Does |
|---|---|---|
grounding | grounding_manifest, grounding_get | list a cartridge's CLAUDE.md/skills.md with sha256, then fetch a changed one (read-only) |
publish | publish_evaluation, delete_evaluation | file an HTML report into a Doc Vault project through the same discipline gate as a human upload (write); and soft-delete (hide) a report as cleanup for a mistaken publish — nothing is purged (the file is tombstoned, restorable), and the target cartridge's operator can disable this cleanup from the cartridge (off by default) |
evaluate | list_evaluations, read_evaluation | read prior evaluations for the target cartridge's Doc Vault project — list them, then read a version's HTML (read-only, via Doc Vault system-list / system-read). Lets a recurring evaluator do delta reviews. Opt-in in the mint form. |
Two meta-tools, list_targets and switch_target, use boundary '*' (always available).
A token's targets are the cartridges it may act on (default its home cartridge); switch_target
sets the active one, and the grounding tools resolve and enforce membership — the active target rides an
unsigned x-mcp-target header, but the server refuses any target outside the token's set, so there is no escalation.
Managed in the Console's MCP Tokens pane, permission-scoped like the Content CMS: admins manage every token; a user with access to cartridge X manages X's tokens. The secret is shown once at mint (and on rotate) — the store keeps only the encrypted form.
| Action | Route | Effect |
|---|---|---|
| Mint | POST /api/mcp/tokens | new tokenId + secret; pick cartridge, boundaries, target cartridges |
| Rotate | POST /api/mcp/tokens/{id}/rotate | new secret, same id; old secret stops immediately |
| Revoke | POST /api/mcp/tokens/{id}/revoke | active=false; stops immediately |
| List | GET /api/mcp/tokens[?cartridge=] | public view (never the secret) + which cartridges the caller may manage |
scope that defaults to the cartridge-wide _ (the same convention as the Content
CMS keys). The id charset allows a leading underscore precisely so _ validates.
console/mcp/client/pc-mcp-client.mjs is a zero-dependency (Node 18+) stdio↔HTTPS bridge that Claude Code
launches: it speaks MCP over stdio to the client and forwards each tool call as an HMAC-signed HTTPS request to
/api/mcp. The client is served from the Console origin, so mint/rotate render a single copy-paste block that
needs no repo checkout:
mkdir -p ~/.pc-mcp
curl -fsSL <console>/pc-mcp-client.mjs -o ~/.pc-mcp/pc-mcp-client.mjs
cat > ~/.pc-mcp/config.json <<'JSON'
{ "endpoint": "<console>/api/mcp", "tokenId": "mtk_…", "secret": "…" }
JSON
chmod 600 ~/.pc-mcp/config.json
claude mcp add paycargo -- node ~/.pc-mcp/pc-mcp-client.mjs --config ~/.pc-mcp/config.json
A collapsible "How to set up a scheduled evaluator" guide is built into the MCP Tokens pane, and a cartridge-scoped version lives inside the Foundation Audit cartridge.
See also: 6.s Content CMS (the grounding docs the tools read) · 6.w Foundation Audit (the first real consumer) · 6.u Doc Vault (where evaluations are published).