Skip to content

Commit 72108a0

Browse files
committed
docs: Address review feedback on PRD-061
- 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
1 parent c99c80d commit 72108a0

1 file changed

Lines changed: 26 additions & 7 deletions

File tree

docs/prd/061-mcp-auth-session-unification.md

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
## Problem Statement
44

5+
**Supersedes PRD-044 MCP auth approach.** PRD-044 proposed tenant-scoped Dex redirects
6+
(Option A). This PRD bypasses Dex entirely for MCP - a cleaner solution. PRD-044's BFF SSO
7+
tenant context fix and `HandlerOptionalTenant` middleware for Dex endpoints remain needed
8+
independently and are out of scope here.
9+
510
The MCP (Model Context Protocol) OAuth flow bypasses the Meridian UI entirely and redirects
611
users to the embedded Dex OIDC login page directly. This creates three problems:
712

@@ -50,15 +55,15 @@ remove the old Dex-direct flow entirely - no feature flags, no fallback.
5055
-> MCP validates client_id, PKCE, redirect_uri (unchanged)
5156
-> MCP stores OIDCFlowState {PKCE challenge, client_id, redirect_uri,
5257
state, tenant, scopes}
53-
-> MCP 302 -> https://{tenant}.{baseDomain}/oauth/consent?mcp_state=
58+
-> MCP 302 -> https://{tenant}.{baseDomain}/auth/mcp-consent?mcp_state=
5459
{key}&client_id={id}
5560
[CHANGED: was Dex redirect, now UI redirect]
5661
5762
3. Browser loads SPA consent page
5863
-> SPA checks sessionStorage for JWT
5964
-> If no JWT: redirect to /login with return_url
6065
-> After login (or if already logged in):
61-
SPA fetches GET /oauth/consent-info?client_id=...&mcp_state=...
66+
SPA fetches GET /mcp/consent-info?client_id=...&mcp_state=...
6267
-> MCP server validates state exists, returns trusted client metadata
6368
-> SPA renders consent card with client name, redirect URI, scopes,
6469
tenant, approve/deny
@@ -118,7 +123,7 @@ remove the old Dex-direct flow entirely - no feature flags, no fallback.
118123
that redirects to the UI consent page URL with `mcp_state` and `client_id` query params.
119124
Store `requested_scopes` from the authorize request in `OIDCFlowState`.
120125

121-
**New endpoint `GET /oauth/consent-info`**: Returns trusted client metadata (client_name,
126+
**New endpoint `GET /mcp/consent-info`**: Returns trusted client metadata (client_name,
122127
redirect_uri, scopes) after validating the `mcp_state` exists in the state store.
123128
Unauthenticated endpoint - returns display data only. Cross-checks `client_id` in URL
124129
matches client_id in state. For dynamically registered clients, include `is_dynamic: true`
@@ -152,10 +157,13 @@ consumption, 2-minute TTL, capped at 10,000 entries, background eviction.
152157

153158
### 3. Frontend (`frontend/src/`)
154159

155-
**New route**: `/oauth/consent` in `App.tsx`.
160+
**New route**: `/auth/mcp-consent` in `App.tsx`. This path avoids the Caddy
161+
`@mcp_transport` matcher which intercepts all `/oauth/*` paths and routes them to
162+
the MCP server. Using `/auth/mcp-consent` ensures the request falls through to the
163+
SPA catch-all.
156164

157165
**New page component**: `OAuthConsentPage` - checks auth state, fetches client metadata
158-
from `/oauth/consent-info`, renders consent card, handles approve/deny.
166+
from `/mcp/consent-info`, renders consent card, handles approve/deny.
159167

160168
**New display component**: `ConsentCard` - shows application name, tenant context, scope
161169
description, redirect URI, approve/deny buttons. For dynamically registered clients (where
@@ -166,10 +174,21 @@ login page.
166174

167175
### 4. Wiring (`cmd/meridian/`)
168176

177+
The unified binary (`cmd/meridian`) runs both the BFF (api-gateway) and MCP server in
178+
the same Go process. This enables shared in-memory stores between them.
179+
169180
Shared `ConsentCodeStore` created once and passed to both BFF's `MCPConsentHandler` and
170181
MCP's `OIDCHandler`. Shared `OIDCStateStore` also passed to BFF handler (needed for deny
171182
flow and redirect_uri lookup).
172183

184+
**Deployment note**: The demo docker-compose runs a separate `mcp-server` container
185+
alongside the unified `meridian` container. The consent flow requires both BFF and MCP
186+
to share in-memory stores, so the consent flow runs within the unified binary only.
187+
The separate `mcp-server` container's OAuth endpoints are not used for the consent
188+
flow - Caddy routes `/mcp/*` to the MCP handler within the unified binary. If the
189+
MCP server is ever deployed as a fully separate service, the in-memory stores must
190+
be replaced with HTTP-based inter-service calls (BFF calls MCP to exchange codes).
191+
173192
### 5. Cleanup
174193

175194
**Remove from docker-compose env vars**: `MCP_DEX_ISSUER_URL`, `MCP_DEX_CLIENT_ID`,
@@ -258,7 +277,7 @@ The "Unverified application" badge only appears for dynamically registered clien
258277

259278
### API Contracts (Frontend View)
260279

261-
**GET `/oauth/consent-info?client_id=...&mcp_state=...`** (MCP server, unauthenticated)
280+
**GET `/mcp/consent-info?client_id=...&mcp_state=...`** (MCP server, unauthenticated)
262281

263282
- 200: `{ client_id, client_name, redirect_uri, scopes, is_dynamic }`
264283
- 400: invalid/expired state or client_id mismatch
@@ -276,7 +295,7 @@ The "Unverified application" badge only appears for dynamically registered clien
276295
### Backend
277296

278297
1. MCP `/oauth/authorize` redirects to UI consent page (not Dex)
279-
2. MCP `/oauth/consent-info` returns trusted client metadata including `redirect_uri`,
298+
2. MCP `/mcp/consent-info` returns trusted client metadata including `redirect_uri`,
280299
`scopes`, and `is_dynamic` after validating state
281300
3. BFF `POST /api/auth/mcp-consent` requires valid JWT, issues one-time consent code
282301
4. MCP `/oauth/callback` accepts consent codes and cross-validates against flow state

0 commit comments

Comments
 (0)