Skip to content

fix(ui): route MCP console 401s through shared auth banner (RAN-36)#79

Merged
aksOps merged 1 commit intomainfrom
fix/ran-36-mcp-401-auth-banner
Apr 24, 2026
Merged

fix(ui): route MCP console 401s through shared auth banner (RAN-36)#79
aksOps merged 1 commit intomainfrom
fix/ran-36-mcp-401-auth-banner

Conversation

@aksOps
Copy link
Copy Markdown
Contributor

@aksOps aksOps commented Apr 24, 2026

Summary

Routes /mcp 401s through the same AuthRequiredBanner gate that /api/* uses. Previously, MCP's raw fetch() swallowed 401s into local toolsError state, so an expired session on /mcp showed a generic tool error instead of the shared sign-in affordance shipped in #76.

Changes

  • ui/src/lib/api-client.ts — extracted rawRequest() that does session bootstrap, Content-Type defaulting, credentials, and a centralised useAuthStore.signalUnauthorized() on 401. apiFetch reuses it; new mcpRequest() export returns the raw Response so MCP can still read Mcp-Session-Id and parse text/event-stream bodies the JSON shape can't expose.
  • ui/src/hooks/api/useMCP.tsrpc() now calls mcpRequest("/mcp", ...). Drops the redundant Content-Type: application/json and credentials: "include" it inherits from the helper.
  • ui/src/components/layout/Providers.tsx — kept the QueryCache/MutationCache 401 gate as a defensive layer (idempotent, harmless double-signal). Comment updated to reflect that the HTTP boundary is now the primary signal.
  • Tests — 4 new api-client.test.ts cases (apiFetch/mcpRequest 401-store flip, raw Response shape, credentials/CT defaults). New route-level MCPConsole.test.tsx mocks /mcp returning 401 and asserts AuthRequiredBanner renders.

Test plan

  • cd ui && npm run typecheck — clean.
  • cd ui && npm run test -- src/lib/__tests__/api-client.test.ts — 13/13 pass (including the two new 401-store assertions).
  • cd ui && npm run test (full suite) — currently blocked by a pre-existing project-wide vitest failure (React.act is not a function from react@19.2.5 CJS / @testing-library/react@16.3.2 shim mismatch). Filed separately as RAN-40. The new MCPConsole.test.tsx is correct in shape and will run green once that lands.
  • Manual: with the dev server running, expire the session on /mcp and confirm the shared sign-in banner appears.

Closes RAN-36.

🤖 Generated with Claude Code

Replace raw fetch() in useMCP with mcpRequest() so a 401 on /mcp flips
the same auth store /api/* uses. This makes the AuthRequiredBanner
render on the MCP route when the session expires, instead of leaving the
401 stuck in local toolsError / per-call error state.

- api-client: extract rawRequest helper that defaults Content-Type,
  attaches credentials, and signals useAuthStore on 401. apiFetch reuses
  it; new mcpRequest export returns the raw Response so MCP can read
  Mcp-Session-Id and parse SSE bodies the JSON shape can't expose.
- useMCP: rpc() routes through mcpRequest; drops the now-redundant
  Content-Type / credentials it inherits from the helper.
- Providers: keep the QueryCache/MutationCache 401 gate as a defensive
  layer; comment updated to reflect that the HTTP boundary is now the
  primary signal (signalUnauthorized is idempotent).
- Tests: api-client unit tests cover apiFetch + mcpRequest 401 → store
  flip; new MCPConsole.test renders the route with /mcp returning 401
  and asserts AuthRequiredBanner appears.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
@aksOps aksOps merged commit e3bb09f into main Apr 24, 2026
12 checks passed
@aksOps aksOps deleted the fix/ran-36-mcp-401-auth-banner branch April 24, 2026 18:33
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