CP5 · Runbook · review only — no execution

Flip verify_jwt = true on pure-mcp

The highest-blast-radius step of the auth window. This is a plan for a watched window — nothing here has been run.

On this page

  1. What Mike / Operator does — the short list
  2. Current state (grounded)
  3. The one nuance that changes everything
  4. Pre-flight checks (Code)
  5. The flip, step by step
  6. Verification matrix
  7. Rollback
  8. Go / no-go gate

1 · What you do from your end Operator

Short version: you don't touch any code or config. Your part is decision + presence:

  1. Pick a low-traffic window (early morning is ideal) and say go. cp5 must run before cp7, and after cp4 (done).
  2. Have Code (Claude Code) on hand — the flip is a pure-mcp redeploy, which is Code's lane (or a Composio SUPABASE_DEPLOY_FUNCTION call). You don't run it.
  3. Be signed in and ready to test right after the flip: load the app, open Pure Chat, add a test ticket, open a couple of data pages. ~3 minutes of clicking.
  4. Keep the Supabase dashboard reachable in case we want the manual toggle instead of a redeploy (Edge Functions pure-mcp Details "Verify JWT").
Nothing to install, no keys to create. The ANTHROPIC_API_KEY and auth providers are already set (verified 6/27). Rollback is one action and reverts in seconds.

2 · Current state grounded 7/7

Edge Functionverify_jwtRole
pure-mcp (v53)falseThe authenticated MCP server — the cp5 target
pure-chat-apifalsePublic path the live site uses for chat / board.add
poppy-mcpfalsePoppy assistant — public-ish
pure-passkeyfalseWebAuthn passkey enroll/verify
mcp-report-extracttrueAlready gated — proves the flip works cleanly on this project
Good news that de-risks cp5: the live site's public features call pure-chat-api, a separate function — not pure-mcp directly. So gating pure-mcp does not, by itself, break public chat/board. The "split the public path" work the plan worried about is largely already true.

3 · The nuance that changes everything

verify_jwt = true does not mean "require a logged-in user." The anon key is itself a valid Supabase-signed JWT, so it still passes. What the flip actually does:

So the real access boundary is inside pure-mcp: does it act as the service role (bypasses RLS — full power) or as the caller's JWT (RLS applies — cp2–cp4 kick in)? That is the single most important thing Code confirms in pre-flight.

Why still do it? It closes the "no-token / forged-token" hole, makes the caller identity trustworthy, and is the prerequisite posture for cp7 (AAL2). It is a real hardening step even though it's not the whole story.

4 · Pre-flight checks Code

Before flipping, Code reads the pure-mcp source (supabase/functions/pure-mcp/index.ts) and confirms:

  1. Key mode: does it create its Supabase client with SERVICE_ROLE_KEY or with the incoming Authorization header? Note which — it decides whether RLS applies post-flip.
  2. Caller inventory: grep the deployed site + Poppy for functions/v1/pure-mcp. Confirm every caller sends Authorization (anon or user). The chat path uses pure-chat-api (safe), but confirm no page calls pure-mcp with no header.
  3. Public tools: list which pure-mcp tools are meant to be reachable anonymously (public reads, chat.send, board.add). If any are, they must stay on a public function (pure-chat-api / a public route) — never behind the gated endpoint.
  4. Break-glass: confirm the service-role admin/MCP path is unaffected (it authenticates with the service key, not a user JWT).
Stop condition: if any live public page calls pure-mcp directly with only the anon key and no Authorization header at all, split that call onto pure-chat-api first. Do not flip until caller inventory is clean.

5 · The flip, step by step

Step 0 — snapshot

Record current pure-mcp version (v53) so rollback is exact. Note verify_jwt=false.

Step 1 — confirm the split Code

Verify public chat/board stays on pure-chat-api (verify_jwt=false). Leave poppy-mcp public if Poppy is used anonymously; otherwise gate it in a later pass.

Step 2 — flip Code

Set verify_jwt=true on pure-mcp — either the dashboard toggle, or redeploy config via Composio:

SUPABASE_DEPLOY_FUNCTION ref: mbnakfrkfwxinhxlisyq slug: pure-mcp verify_jwt: true (same source, no code change)
Step 3 — smoke test Operator + Code

Run the verification matrix below. If any red cell, roll back immediately (Step R).

Step 4 — record

Mark cp5 done on the tracker + changelog, note new version number.

6 · Verification matrix

TestExpected after flipWho
Signed-in user loads app + data pagesWorks (JWT forwarded authenticated RLS)Op
Pure Chat sends a message + adds a ticketWorks (uses pure-chat-api, unaffected)Op
Admin / MCP service-role writeWorks (service key bypasses)Code
Direct pure-mcp call with no Authorization401 at gateway (this is the point)Code
Direct pure-mcp call with anon keyStill reaches function (anon JWT valid); RLS applies per cp3/cp4Code
Logged-out visitor on a public pagePublic reads work via public path; no console 401 stormOp

7 · Rollback seconds

Step R: set verify_jwt=false on pure-mcp (dashboard toggle or redeploy config) — or redeploy the snapshotted v53. Everything returns to the pre-flip state instantly; no data change was made.

Because cp5 changes only a gateway flag (not schema, not data, not the function's code), rollback is a single toggle with zero residue.

8 · Go / no-go gate

Sequence reminder: cp5 verify staff can still auth and use the app then cp7 (AAL2 enforce, with the 14-day grace gate). Never cp7 before cp5 is proven stable for a day.