Skip to content

docs: Add PRD-060 MCP OAuth session unification#2145

Merged
bjcoombs merged 6 commits intodevelopfrom
mcp-auth-session-unification
Apr 6, 2026
Merged

docs: Add PRD-060 MCP OAuth session unification#2145
bjcoombs merged 6 commits intodevelopfrom
mcp-auth-session-unification

Conversation

@bjcoombs
Copy link
Copy Markdown
Collaborator

@bjcoombs bjcoombs commented Apr 6, 2026

Summary

  • Add PRD-060: MCP OAuth Session Unification
  • Replaces MCP's direct Dex login redirect with a UI consent flow that reuses the existing browser session
  • Eliminates double-login and removes Dex as a user-facing surface for MCP auth

Context

MCP auth currently bypasses the UI and redirects directly to Dex, forcing users to log in twice.
The new design routes through the SPA consent page, which can detect an existing session
(JWT in sessionStorage) and show a consent screen instead of a login form.

Produced via a Six Thinking Hats huddle with security, backend, and frontend perspectives.

Test plan

  • PRD review by stakeholders
  • Parse into Task Master for implementation tracking

Replaces MCP's direct Dex login redirect with a UI consent flow
that reuses the existing browser session. Eliminates double-login,
removes Dex as a user-facing surface for MCP auth.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 6, 2026

Warning

Rate limit exceeded

@bjcoombs has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 5 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 0 minutes and 5 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fff38dea-877d-4a68-ac4e-b2f87264ad5e

📥 Commits

Reviewing files that changed from the base of the PR and between 565d7dd and 3f51958.

📒 Files selected for processing (1)
  • docs/prd/061-mcp-auth-session-unification.md
📝 Walkthrough

Walkthrough

Updates the .taskmaster submodule pointer and adds a new PRD outlining a redesigned MCP OAuth consent flow that routes users to a Meridian UI consent SPA, introduces a BFF-issued one-time consent code and ConsentCodeStore, and describes backend/frontend and store changes.

Changes

Cohort / File(s) Summary
Submodule Update
\.taskmaster
Updated submodule pointer to commit 022d3d26346da0d22a37fc9c5047bc10ef74ef42.
Product Requirements
docs/prd/061-mcp-auth-session-unification.md
New PRD defining MCP OAuth session unification: redirect to Meridian SPA consent page; persist requested scopes in OIDCFlowState; add unauthenticated GET /mcp/consent-info; change callback to accept one-time BFF consent codes from a ConsentCodeStore (TTL, one-time atomic consumption); add JWT-protected POST /api/auth/mcp-consent; frontend /auth/mcp-consent route and UI; remove MCP Dex-specific config; propose shared in-memory stores in cmd/meridian.

Sequence Diagram(s)

sequenceDiagram
  actor UserAgent as Browser
  participant MCP
  participant SPA as "Meridian SPA"
  participant BFF
  participant Store as "ConsentCodeStore"

  UserAgent->>MCP: GET /mcp/authorize (create OIDCFlowState)
  MCP->>UserAgent: 302 Redirect to /auth/mcp-consent?mcp_state=...&client_id=...
  UserAgent->>SPA: GET /auth/mcp-consent (load consent UI)
  SPA->>MCP: GET /mcp/consent-info?mcp_state=...&client_id=...
  MCP-->>SPA: return trusted client metadata (name, redirect_uri, scopes, is_dynamic)
  UserAgent->>SPA: user approves
  SPA->>BFF: POST /api/auth/mcp-consent (Bearer) {approve, mcp_state, client_id, identity}
  BFF->>Store: create one-time consent code (store with TTL)
  Store-->>BFF: consent_code
  BFF->>UserAgent: 302 Redirect to MCP callback ?consent_code=...&mcp_state=...
  UserAgent->>MCP: GET /mcp/callback?consent_code=...&mcp_state=...
  MCP->>Store: consume consent_code (atomic)
  Store-->>MCP: consent payload (identity, scopes, client_id)
  MCP->>MCP: validate mcp_state/client_id, issue scoped session
  MCP-->>UserAgent: final redirect (logged-in session)
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Title check ⚠️ Warning The PR title references PRD-060 but the document added is actually PRD-061 (as shown in the filename docs/prd/061-mcp-auth-session-unification.md). Update the title to reference PRD-061 instead of PRD-060 to match the actual document filename and content.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The description accurately describes the changes being made to add the MCP OAuth session unification design document and explains the motivation and context.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch mcp-auth-session-unification

Comment @coderabbitai help to get the list of available commands and usage tips.

claude[bot]
claude Bot previously requested changes Apr 6, 2026
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See summary comment for full analysis. 3 inline findings on specification defects that would block implementation.

Comment thread docs/prd/061-mcp-auth-session-unification.md
Comment thread docs/prd/061-mcp-auth-session-unification.md
Comment thread docs/prd/061-mcp-auth-session-unification.md
@claude
Copy link
Copy Markdown

claude Bot commented Apr 6, 2026

Claude Code Review

Commit: 3f51958 | CI: still running (lint checks passing, security scans pending)

Summary

Well-structured PRD that cleanly supersedes PRD-044's Option A (tenant-scoped Dex redirects) with a simpler approach that eliminates Dex as a user-facing surface entirely. The document has been iterated through multiple rounds of feedback — the latest commit addresses return_url open-redirect protection and the invalid_action error contract.

The PRD is internally consistent: the end-to-end flow, component changes, security requirements, acceptance criteria, and API contracts all align. The cross-reference to PRD-044 is explicit about what it supersedes and what remains independently needed (BFF SSO fix, HandlerOptionalTenant). The deployment topology constraint (in-memory stores require unified binary) is clearly documented with a forward path for separate deployment.

One undescribed change (.taskmaster symlink to submodule) should be addressed.

Risk Assessment

Area Level Detail
Blast radius Med MCP auth flow changes affect all MCP clients; BFF SSO unaffected
Rollback Safe PRD only — no code changes to roll back
Scale N/A Specification document
Cross-system Med Touches MCP, BFF, frontend, Caddy routing, docker-compose
Migration N/A No database changes

Findings

Severity Location Description Status
Improvement .taskmaster Symlink to submodule conversion is undescribed (see below) Open

.taskmaster symlink to submodule: The diff converts .taskmaster from a symlink to a git submodule pointing at commit 022d3d2. This structural change is not mentioned in the PR description and could affect other developers' local setups. Should this be in a separate PR, or at minimum documented in the PR description?

Bot Review Notes

CodeRabbit thread on line 278 (return_url validation): Already addressed. The latest commit added security requirement 12 which explicitly requires return_url to be a relative path starting with /. This matches exactly what CodeRabbit requested. The thread can be resolved.

All other CodeRabbit threads (explicit action enum, tenant binding type mismatch, invalid_action error) were resolved in prior commits.

Questions for the Author

  1. 061-mcp-auth-session-unification.md:276 — The PRD says "SPA checks sessionStorage for JWT." sessionStorage is tab-scoped — a new browser window opened by Claude Code will not inherit the existing session. This means the user will still need to log in once in the MCP tab (just via the Meridian UI instead of Dex). The "double login" framing in the Problem Statement implies zero re-authentication. Is this the intended UX, or does the frontend actually use localStorage?

Previously Flagged

Severity Location Description Status
Critical Line 170 In-memory store sharing between containers Resolved — deployment note added
Critical Line 64 Caddy routing conflict with /oauth/consent Resolved — path changed to /auth/mcp-consent
Critical Line 10 Undefined relationship to PRD-044 Resolved — explicit supersedes clause added
Improvement Line 278 return_url open-redirect protection Resolved — security req 12 added
Improvement Line 299 Missing invalid_action in error contract Resolved — added to API contract

claude[bot]
claude Bot previously requested changes Apr 6, 2026
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-review of c99c80d (renumber to PRD-061 + merge develop). PRD content unchanged — all 3 specification defects from previous review remain open. See summary comment for details.

- Fix Caddy routing conflict: consent page at /auth/mcp-consent (not
  /oauth/consent which Caddy routes to MCP server)
- Fix consent-info endpoint: /mcp/consent-info (under Caddy's MCP matcher)
- Clarify in-memory store sharing requires unified binary; document
  deployment topology constraint for separate MCP container
- Add PRD-044 cross-reference: this supersedes PRD-044 MCP auth approach;
  PRD-044 BFF SSO fix remains needed independently
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 6, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
docs/prd/061-mcp-auth-session-unification.md (1)

126-130: Add abuse controls for unauthenticated consent-info endpoint.

Because this endpoint is unauthenticated and keyed by query params, the PRD should explicitly require rate limiting and non-enumerable error behavior to reduce state probing/oracle risk.

Also applies to: 280-284

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/prd/061-mcp-auth-session-unification.md` around lines 126 - 130, Update
the GET /mcp/consent-info endpoint spec to mandate abuse controls: add explicit
rate-limiting requirements for the unauthenticated, query-param-keyed endpoint
(e.g., per-IP and per-client_id buckets) and require "non-enumerable" error
responses when mcp_state or client_id are missing/invalid (do not reveal
existence or validity of state or client_id in error bodies or status codes).
Reference the existing mcp_state validation and client_id equality check in the
description and state that dynamically registered flagging (is_dynamic) remains
allowed but must not be used to leak verification status; apply the same
controls to the other similar clause mentioned for the lines covering the second
instance.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/prd/061-mcp-auth-session-unification.md`:
- Around line 149-150: The PRD currently accepts an optional boolean
`{mcp_state, client_id, denied?}` which risks accidental approval when omitted;
change the consent payload to require an explicit action enum/string (e.g.,
`{mcp_state, client_id, action}` where action ∈ {"approve","deny"}) and update
all places that read/write the field (including the consent generation flow that
writes to ConsentCodeStore and any handlers that produce the one-time consent
code) to validate the presence and allowed values of `action`; also update the
related occurrences referenced around lines 287-290 to use and validate the new
`action` field instead of `denied?`.
- Around line 214-215: The security invariant incorrectly treats MCP
state.tenantSlug, BFF JWT.x-tenant-id, and consent code.tenantID as the same
type; tenantSlug (a human-friendly slug) and tenantID (a UUID) are different
identifiers, so change the wording and validation logic to assert semantic
binding rather than literal equality: require that tenantSlug maps to the
tenantID (or that both resolve to the same canonical tenant record) and that BFF
JWT.x-tenant-id is the canonical tenant ID (or includes both slug and ID),
updating the invariant text and any downstream checks to compare resolved
canonical IDs (e.g., resolve tenantSlug → tenantID then compare canonical IDs)
instead of direct slug==UUID comparisons.

---

Nitpick comments:
In `@docs/prd/061-mcp-auth-session-unification.md`:
- Around line 126-130: Update the GET /mcp/consent-info endpoint spec to mandate
abuse controls: add explicit rate-limiting requirements for the unauthenticated,
query-param-keyed endpoint (e.g., per-IP and per-client_id buckets) and require
"non-enumerable" error responses when mcp_state or client_id are missing/invalid
(do not reveal existence or validity of state or client_id in error bodies or
status codes). Reference the existing mcp_state validation and client_id
equality check in the description and state that dynamically registered flagging
(is_dynamic) remains allowed but must not be used to leak verification status;
apply the same controls to the other similar clause mentioned for the lines
covering the second instance.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c74a324b-e870-4cef-81ca-4706530360e3

📥 Commits

Reviewing files that changed from the base of the PR and between b3b0bd5 and 72108a0.

📒 Files selected for processing (3)
  • .taskmaster
  • .taskmaster
  • docs/prd/061-mcp-auth-session-unification.md

Comment thread docs/prd/061-mcp-auth-session-unification.md Outdated
Comment thread docs/prd/061-mcp-auth-session-unification.md Outdated
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previous 3 critical findings all resolved in 72108a0. Two valid CodeRabbit concerns remain (see summary comment) — using COMMENT rather than APPROVE per bot-gate policy.

- Use explicit action enum (approve/deny) instead of optional boolean
  for consent endpoint to prevent accidental approval on field omission
- Fix tenant binding invariant: clarify slug vs UUID distinction and
  require resolution at each boundary
coderabbitai[bot]
coderabbitai Bot previously requested changes Apr 6, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
docs/prd/061-mcp-auth-session-unification.md (1)

156-157: Define deterministic behavior when ConsentCodeStore reaches cap.

The PRD states TTL/cap/eviction, but not what happens at insertion time when full (reject vs evict oldest vs random). Please lock this down to avoid divergent implementations.

Also applies to: 239-240

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/prd/061-mcp-auth-session-unification.md` around lines 156 - 157, Define
deterministic full-cap behavior for ConsentCodeStore: when ConsentCodeStore
(same pattern as MCP's CodeStore) reaches the 10,000-entry cap, reject new
insertions (e.g., in the insert/save/putCode method) with a clear error/return
value rather than evicting silently; increment a metric (e.g.,
consent_code_store.insert_rejected) and log the rejection so callers can retry
or surface an error. Update the PRD text near the ConsentCodeStore description
(and at lines referenced 239-240) to state "insertion is rejected when full;
callers must retry" and note the TTL (2-minute), background eviction remains in
place to free space. Ensure the implementation and tests assert the rejection
behavior and metric/log emission.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@docs/prd/061-mcp-auth-session-unification.md`:
- Around line 272-274: Update the login redirect behavior to validate the
return_url query parameter: when generating or consuming /login?return_url=...,
require that return_url is either a same-origin absolute URL or a relative path
(no external hosts), normalize and percent-decode it, reject or replace
invalid/external values with a safe default (e.g., "/"), and log or surface
validation failures; apply this validation in the login redirect handler and the
auth check that reads return_url to prevent open-redirect regressions.
- Around line 291-295: The API spec lists the request shape including the action
field (`action: "approve" | "deny"`) but the 400 error list omits the case for
unrecognized actions; update the error contract to include `"invalid_action"` as
a possible 400 error and document that the backend returns `{ error:
"invalid_action" }` when the `action` field is not one of the allowed values
(i.e., when validation in the handler that processes `mcp_state`, `client_id`,
`action` rejects unrecognized actions).

---

Nitpick comments:
In `@docs/prd/061-mcp-auth-session-unification.md`:
- Around line 156-157: Define deterministic full-cap behavior for
ConsentCodeStore: when ConsentCodeStore (same pattern as MCP's CodeStore)
reaches the 10,000-entry cap, reject new insertions (e.g., in the
insert/save/putCode method) with a clear error/return value rather than evicting
silently; increment a metric (e.g., consent_code_store.insert_rejected) and log
the rejection so callers can retry or surface an error. Update the PRD text near
the ConsentCodeStore description (and at lines referenced 239-240) to state
"insertion is rejected when full; callers must retry" and note the TTL
(2-minute), background eviction remains in place to free space. Ensure the
implementation and tests assert the rejection behavior and metric/log emission.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6985b5d4-efcb-4c6a-8993-cd186be555b8

📥 Commits

Reviewing files that changed from the base of the PR and between 72108a0 and 565d7dd.

📒 Files selected for processing (1)
  • docs/prd/061-mcp-auth-session-unification.md

Comment thread docs/prd/061-mcp-auth-session-unification.md
Comment thread docs/prd/061-mcp-auth-session-unification.md
Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See summary comment for full review. 2 inline suggestions. Prior critical issues all resolved. No blockers.

Comment thread docs/prd/061-mcp-auth-session-unification.md
Comment thread docs/prd/061-mcp-auth-session-unification.md
- Add return_url open-redirect protection as security requirement
- Add invalid_action to API error contract for unrecognized action values
@bjcoombs bjcoombs dismissed stale reviews from claude[bot], claude[bot], coderabbitai[bot], and coderabbitai[bot] April 6, 2026 11:35

Addressed in subsequent commits

Copy link
Copy Markdown

@claude claude Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See summary comment for full review. 1 non-blocking finding (undescribed .taskmaster change), 1 question for the author. All 5 previously flagged issues have been resolved.

@bjcoombs bjcoombs merged commit ea716a3 into develop Apr 6, 2026
22 checks passed
@bjcoombs bjcoombs deleted the mcp-auth-session-unification branch April 6, 2026 11:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant