@@ -146,11 +146,12 @@ and related helpers. The MCP server no longer talks to Dex.
146146### 2. BFF / API Gateway (` services/api-gateway/ ` )
147147
148148** New endpoint ` POST /api/auth/mcp-consent ` ** : Behind full auth middleware chain (JWT
149- validated, tenant resolved). Accepts ` {mcp_state, client_id, denied?} ` . On approve:
150- generates one-time consent code, stores identity in ` ConsentCodeStore ` , returns
151- ` {redirect_url} ` pointing to MCP callback. On deny: consumes MCP state, returns
152- ` {redirect_url} ` pointing to client's redirect_uri with ` error=access_denied ` and the
153- client's original state.
149+ validated, tenant resolved). Accepts ` {mcp_state, client_id, action} ` where ` action `
150+ is an explicit enum: ` "approve" ` or ` "deny" ` (no boolean default - a missing or
151+ unrecognized action is rejected). On approve: generates one-time consent code, stores
152+ identity in ` ConsentCodeStore ` , returns ` {redirect_url} ` pointing to MCP callback.
153+ On deny: consumes MCP state, returns ` {redirect_url} ` pointing to client's
154+ redirect_uri with ` error=access_denied ` and the client's original state.
154155
155156** New ` ConsentCodeStore ` ** : In-memory store, same pattern as MCP's ` CodeStore ` . One-time
156157consumption, 2-minute TTL, capped at 10,000 entries, background eviction.
@@ -211,8 +212,11 @@ All mandatory - no "recommended" tier. Everything listed here ships or the PRD i
211212 opaque one-time codes travel in redirects.
2122132 . ** One-time code consumption** : ` Consume() ` must be atomic - concurrent calls for the
213214 same code return success for exactly one caller.
214- 3 . ** Tenant binding chain** : `MCP state.tenantSlug == BFF JWT.x-tenant-id ==
215- consent code.tenantID` must hold end-to-end. Break at any link means rejection.
215+ 3 . ** Tenant binding chain** : The tenant must be consistent across the entire flow.
216+ MCP state stores ` tenantSlug ` (from subdomain). BFF JWT contains ` x-tenant-id `
217+ (UUID). The consent code stores both ` tenantSlug ` and ` tenantID ` . At each
218+ boundary, resolve slug to UUID (or vice versa) and verify they refer to the
219+ same tenant. A mismatch at any link means rejection.
2162204 . ** PKCE integrity** : The outer PKCE chain (client-to-MCP) must be preserved unmodified
217221 by the consent flow.
2182225 . ** Explicit consent** : BFF consent endpoint only callable via POST with Bearer auth.
@@ -284,7 +288,7 @@ The "Unverified application" badge only appears for dynamically registered clien
284288
285289** POST ` /api/auth/mcp-consent ` ** (BFF, requires Bearer JWT)
286290
287- - Request: ` { mcp_state, client_id, denied?: boolean } `
291+ - Request: ` { mcp_state, client_id, action: "approve" | "deny" } `
288292- 200 (approved): ` { redirect_url: "/oauth/callback?code=...&state=..." } `
289293- 200 (denied): ` { redirect_url: "https://client/callback?error=access_denied&state=..." } `
290294- 400: ` { error: "invalid_state" | "state_expired" | "client_mismatch" } `
0 commit comments