feat(mcp): serve the read-only tools over remote Streamable HTTP at /mcp

Bridge the existing transport-agnostic MCP server factory to a Next.js
route at /mcp using the SDK's Web-standard Streamable HTTP transport, so a
remote client can connect with a manually-minted hlk_ Bearer token. The
same tool/resource layer the local stdio transport serves is reused
verbatim — no duplicated tool logic.

The route owns four gates in front of the transport:

- Auth: Authorization: Bearer hlk_… resolved by the same validator the
  HTTP edge uses, so revoked/expired/scope semantics never drift. A cookie
  is never consulted, so the cookie-only admin boundary stays unreachable
  here regardless of token scope.
- Rate limit: the Postgres limiter keyed by the <userId>:<tokenId> binding
  (mcp:<binding>), so a single shared credential cannot drain the account.
- Module gate: a new opt-in mcp module, OFF by default. With it off the
  endpoint answers 404 and reveals nothing — a self-hoster who never opts
  in gets zero new exposure. The Modules settings hub renders the toggle.
- Transport: a fresh stateless server + transport per request.

Read-only by construction (only read tools are registered) and same-origin
by construction (no CORS headers). The proxy lets /mcp skip the page cookie
redirect since it authenticates by header, mirroring the webhook surfaces.
This commit is contained in:
Marc-André Bombeck committed 2026-06-27 13:47:24 +02:00
1 parent d947bb1104
commit 03d41bc5c0
17 files changed
+599 -2

No files matched your search

@@ -34,6 +34,7 @@ import {
FileText,
MessageCircleHeart,
Moon,
Plug,
Pill,
Smile,
Sparkles,
@@ -71,6 +72,8 @@ const MODULE_ICONS: Record<ModuleKey, LucideIcon> = {
// v1.18.1 (D3) — medications graduated from CORE to a toggleable module.
medications: Pill,
doctorReport: FileText,
// v1.22.0 — the remote MCP endpoint (opt-in connectivity module).
mcp: Plug,
};
export function ModulesSection() {