docs: MCP OAuth tri-mode implementation plan (PR-cycle, no-regression, SOLID) - #457
docs: MCP OAuth tri-mode implementation plan (PR-cycle, no-regression, SOLID)#457seanhanca wants to merge 27 commits into
Conversation
…ass 3) The previous pass ended mid-document: sections 5-10 were missing while the review verdict already claimed them. This writes the PR breakdown (17 PRs across 5 tracks), the PR-cycle loop, the 12-row regression matrix, risks, rollout with an explicit stop line, and the owner asks. Two findings from verifying claims against source rather than assuming: - The supplied pymthouse M2M value is a client id, not a secret. Both the composite mint and every usage/metering read use HTTP Basic id:secret and fail closed without both halves, so the client secret remains a hard blocker for the mint providers (OA-1). - Storyboard has no durable KV; its only durable store is public blob storage. The oauth-store binds a billing credential, so neither an in-memory map nor blob is acceptable. Raised as blocker OA-6 and anti-pattern AP-11, gating phase 1 on a provisioning decision. Regression stance is unchanged and now testable: Daydream sk_* to Daydream signer to BYOC orchestrator stays byte-identical, pinned by INV-1..INV-6 landing before any refactor. No credentials appear in this document. Co-authored-by: Cursor <cursoragent@cursor.com>
Two owner decisions, applied consistently across the plan. KV (was blocker OA-6): a real private KV will be provisioned rather than shipping P0-passthrough only. This un-gates PR-06 through PR-08 and all of phase 1. Provisioning becomes owner task T-KV with acceptance criteria: resource reachable from prod and preview, env wired with clean boot when absent, encryption at rest, server-side TTL under 24h for token bindings, per-user namespacing with no scan surface, and the credential never returned to the client. Recommendation is Vercel KV / Upstash Redis for native key TTL. AP-9 (in-memory map) and AP-11 (public blob) remain blocking anti-patterns: funding the right answer does not license a cheaper one. The stop line is demoted to a contingency. Metering: usage-API debit reads are deferred as follow-up FU-1. The proof bar for a billed call becomes signer 200, numTickets in range, and a real asset URL. Verified against source that this does NOT unblock the mint path: createPymthouseApiKey and the user-upsert both call readM2mAuthHeader, which throws 503 without both the M2M client id and secret, so PR-09/PR-10/PR-15 stay blocked. Only the verification bar moves; the billed tail is testable solely because RS-1 supplied an already-minted composite. Daydream sk_* to Daydream signer to BYOC orchestrator remains byte-identical and pinned by INV-1..INV-6. No credentials appear in this document. Co-authored-by: Cursor <cursoragent@cursor.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
…entation Found while implementing PR-01 against the real repo: - The planned test path lib/mcp-server/__tests__/ would never run; the vitest config only includes tests/unit, tests/sim and the creative-kit package. Corrected to tests/unit/mcp-oauth-auth-invariants.test.ts. - Clarified that storyboard-a3 is the local working-copy name while the GitHub remote is livepeer/storyboard, verified via git remote -v. - Marked PR-01 as shipped (livepeer/storyboard#868). Co-authored-by: Cursor <cursoragent@cursor.com>
Track 1 on storyboard main reject unbound mcp_at_* unconditionally; remove the flag-OFF opaque-forward contradiction, mark PRs MERGED with SHAs, and require a single worker for Track 2+ auth work. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Doc sync: aligned plan with merged Track 1 Auth Resolution contract.
No Track 2 implementation in this push. |
Record PR-04/PR-05 merge SHAs, PR-06 open behind T-KV, and the project-scoped token limitation blocking Upstash Marketplace create.
Owner-provisioned store smoke-tested; PR-06 merge gate cleared. Co-authored-by: Cursor <cursoragent@cursor.com>
Record PR-06/07/08 merge SHAs, T-KV already signed off, PR-09 naap#458 open, and OA-1 secret received with NaaP Preview env-wire still pending. Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Grounded summary of Tracks 1–3 / T-KV: merged PR table with SHAs, honest FULLY/PARTIALLY/NOT VERIFIED prod curl evidence, Preview env wiring steps (names only), and user-story enablement boundaries. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Added MCP-OAUTH-TRIMODE-STATUS.html — verified status + ops wiring guide (no secrets).
File: https://github.com/livepeer/naap/blob/feat/mcp-oauth-tri-mode-plan/MCP-OAUTH-TRIMODE-STATUS.html |
STATUS + plan: PR-11/12/15/16 + NaaP login bridge merged; remaining work is Preview env, web_ client, LR canary deploy, and deferred PR-13/17/FU-1.
Implementation complete (code) — ops remainingAll IMPLEMENT NOW PRs from Tracks 4–5 + NaaP login bridge are merged. Prod
INV: storyboard User ops next (guides in STATUS + plan HTML updated on this branch. |
Flag ON confirmed (AS/PRM 200); PKCE blocked on Storyboard client redirect allowlist + NaaP login mcp_oauth_invalid. Tripwire: delete MCP_OAUTH_ENABLED. Co-authored-by: Cursor <cursoragent@cursor.com>
Scenario A Production flip probe (2026-08-09T03:05–03:07Z) — BLOCKEDFlip OFF now: delete Storyboard Production Evidence
One-line fixes (if you re-open the window)
Report: |
Scenario A re-probe: flag ON + NaaP allowlist OK; e2e still BLOCKED on Storyboard MCP_OAUTH_REDIRECT_ALLOWLIST. Remind flip OFF after test. Co-authored-by: Cursor <cursoragent@cursor.com>
Prod host correction + Scenario A re-probe (2026-08-09T03:11Z)Correct Production MCP/OAuth host: Probe results
E2E status: BLOCKED (not GREEN)One-line fix: on Storyboard Production set Corrected Storyboard Production env (copy-paste)(Var name NaaP Production already correct: RemindFlip OFF after test — delete Production Docs updated on |
…man SSO Storyboard Production MCP_OAUTH_REDIRECT_ALLOWLIST is live: authorize PKCE returns 302 to NaaP login (not 400) for localhost and agent.livepeer.org callback. Login bridge still sets naap_mcp_oauth_pending. Playwright reached GitHub OAuth login; no E2E session in agent env, so bound mcp_at_* / create_media not proven. Flip OFF Production MCP_OAUTH_ENABLED now. Co-authored-by: Cursor <cursoragent@cursor.com>
Scenario A prod re-probe (allowlist fixed) — BLOCKED (not GREEN)Window: 2026-08-09T03:16–03:18Z against Evidence
Why not GREENRedirect allowlist is fixed and the broker head works through NaaP login. Full Scenario A proof bar (bound Flip OFF nowDelete Production |
Record 3× Google SSO → opaque callback access_denied, live curls, and the Storyboard PR that returns a safe reason enum for the next retry. Co-authored-by: Cursor <cursoragent@cursor.com>
Scenario A prod update — opaque
|
| Failure | Env / condition |
|---|---|
invalid_or_expired_state |
Redis pending miss/TTL/one-shot (KV_REST_API_* or UPSTASH_REDIS_REST_*) |
kv |
store unset → OauthStoreUnavailableError |
identity |
missing external_user_id/code; or NAAP_MCP_ORIGIN / identity exchange |
mint_config |
MCP_OAUTH_BILLING_APP_ID or PYMTHOUSE_PUBLIC_CLIENT_ID ≠ app_98575870d7ae33589a3f0660; or missing NAAP_MCP_MINT_URL + MCP_INTERNAL_MINT_SECRET + MCP_INTERNAL_MINT_CALLER_ORIGIN |
mint_http_<n> |
mint HTTP non-OK |
mint_failed |
non-composite response |
Shipped + merged: livepeer/storyboard#880 — callback now returns { error: "access_denied", reason: "<safe enum>" } + server log. Production redeploy in progress (probe still opaque at comment time).
E2E report updated + pushed on this branch (MCP-OAUTH-TRIMODE-E2E-REPORT.html / STATUS).
Next user action (ONE retry after prod shows reason)
- Confirm deploy:
curl -sS 'https://agent.livepeer.org/api/mcp/oauth/callback?state=st_fake&external_user_id=u1'→ must include"reason":"invalid_state". - Fresh authorize (durable local PKCE listener) → Google/GitHub login → if fail, paste the new JSON body (with
reason). - Flip OFF reminder: when the window ends, delete Production
MCP_OAUTH_ENABLEDon Storyboard → Redeploy.
Vercel runtime logs: 403 from this worker (no project token) — reason enum unblocks without needing logs.
Production reason enum is LIVEConfirmed on {"error":"access_denied","reason":"invalid_state"}(fake-state probe) ONE retry URL (fresh pending, ~10 min TTL)Start a local PKCE listener on Complete Google (or GitHub).
Flip OFF after window: delete Production |
Co-authored-by: Cursor <cursoragent@cursor.com>
Fresh retry ready (reason codes LIVE + durable listener)Open now (~10 min Redis TTL):
Complete Google/GitHub once. Success → agent auto token-exchange + Flip OFF after window: delete Production |
…retry Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Fresh retry URL (re-minted — prior pending expired unused)
On fail paste JSON with |
Record agent.livepeer.org SSO → mcp_ac → mcp_at token 200 evidence, invalid_grant on code reuse, and Flip-OFF reminder. create_media asset pending optional re-login after harness action=generate patch. Co-authored-by: Cursor <cursoragent@cursor.com>
Scenario A prod e2e update (agent.livepeer.org)OAuth = GREEN (2026-08-09T04:46Z)
create_media asset: first tools/call failed with Flip OFF now: delete Storyboard Production E2E report: |
SSO → token → create_media returned a real fal image URL; remind Flip-OFF. Co-authored-by: Cursor <cursoragent@cursor.com>
Scenario A = GREEN end-to-end ✅Flip OFF now: delete Storyboard Production Evidence (2026-08-09T04:49:29Z, no secrets):
Hosts: E2E report updated on |
Scenario A = FULL GREEN ✅Flip OFF now: delete Storyboard Production
Asset: https://v3b.fal.media/files/b/0aa59a49/OPfu9uKMx82zolqCboa2J.jpg Hosts: |
|
This PR has been inactive for 7 days. It will be closed in 5 days if no activity occurs. Feel free to reopen anytime. |
Purpose
Adds
MCP-OAUTH-TRIMODE-IMPL-PLAN.html— the PR-cycle implementation plan for MCP OAuth tri-mode auth. Docs only; no production code changes.Three ways to connect: paste a Daydream
sk_*, paste a pymthouse composite, or sign in via OAuth. All auth-mode selection lives in one new Auth Resolution Layer; inference, SDK, gateway and signer stay auth-agnostic (Bearer in →ResolvedCredential { plane, forwardBearer, metadata? }out).Review verdict
resolveAuth()is the sole mode-selection site;sdkCallkeeps itsapiKey: string; 11 anti-patterns reviewers must blockThe 17 PRs by track
storyboard-a3, no flag): PR-01 golden invariants · PR-02resolveAuth()· PR-03pmth-compositescheme (log-only)MCP_OAUTH_ENABLEDunset): PR-04 PRM/AS metadata + cached-404 fix · PR-05 401 challenge · PR-06 oauth-store · PR-07 broker routes · PR-08 store lookupMCP_OAUTH_PROVIDER): PR-09 NaaP mint route · PR-10 naap provider · PR-15 pymthouse providersimple-infra): PR-11 build-time guard · PR-12 isolated canary · PR-13 promotionweb_client · PR-16 connector docs · PR-17 revocation (deferred)No-regression guarantee
Daydream
sk_*→ Daydream signer → BYOC orchestrator stays byte-identical. All new behavior is scoped to LR orchestrators and the pymthouse signer. Pinned by six golden invariants that land in PR-01 before any refactor:mcp_at_*-shaped bearer with the flag unset behaves as an unrecognized opaque bearersk_*byte-identity in → outmcp_at_*asforwardBearer; store miss ⇒ 401, never a fallthroughsdk-call.tsbyte-unchanged across the planauthForRestProxyunchanged — OAuth never reaches the 10+ REST routesRows 1–4 of the 12-row matrix re-run on every merge in tracks 1–3. Deviation halts the plan rather than triggering a fix-forward.
Top risks
key_routing.py.well-knownserves its 404immutablefor 24h, so a flag flip would not reach clients; PR-04 fixes headers before any flipOwner decisions folded in (2026-08-08)
numTicketsin range + real asset URL.Remaining open asks
PYMTHOUSE_M2M_CLIENT_SECRET— still required. The suppliedm2m_…value is a client id. Verified in code:createPymthouseApiKey()and the user-upsert both callreadM2mAuthHeader(), which throws 503 without both halves. This blocks the mint path (PR-09/PR-10/PR-15). The metering de-scope lowers the verification bar but does not unblock these.web_confidential client for Scenario B (publicapp_clients cannot do authorization_code).live_runner.py, socall_runneris missing.No secrets appear in the document or this PR.
Made with Cursor