Skip to content

feat: collapse web + gateway into a single public origin - #226

Merged
maakle merged 16 commits into
mainfrom
feat/single-origin-mcp-gateway
Jun 1, 2026
Merged

feat: collapse web + gateway into a single public origin#226
maakle merged 16 commits into
mainfrom
feat/single-origin-mcp-gateway

Conversation

@maakle

@maakle maakle commented Jun 1, 2026

Copy link
Copy Markdown
Owner

Summary

Collapse the two-host deployment (web on one hostname, gateway on another) into a single public origin via Next.js rewrites(). Self-hosters now need one DNS record, TLS cert, and tunnel/proxy. ngrok free is sufficient for contributors testing OAuth/MCP flows.

  • apps/web/next.config.mjs reverse-proxies /mcp, /mcp/*, /v1/*, /openapi.json, /docs, /docs/*, /slack/*, /teams-bot/*, /google-chat-app/*, and /.well-known/oauth-protected-resource to the gateway via GATEWAY_INTERNAL_URL (default http://localhost:8080, set to http://gateway:8080 in compose).
  • MCP_PUBLIC_URL is now optional in packages/env — derives from WEB_PUBLIC_URLBETTER_AUTH_URL. Two-origin operators can still publish the gateway separately by setting it explicitly; gateway code is unchanged.
  • docker-compose.yml wires GATEWAY_INTERNAL_URL=http://gateway:8080 and adds depends_on: gateway to the web service so the proxy target is reachable on boot.
  • @holo/cli init drops MCP_PUBLIC_URL from the generated .env (no longer needed).
  • /.well-known/oauth-authorization-server is intentionally NOT proxied — the web has its own canonical handler that derives the issuer from WEB_PUBLIC_URL. The catch-all rewrite reaches it correctly. Comment added in next.config.mjs.

Design rationale and migration steps for existing two-host operators: docs/decisions/0009-single-origin-gateway.md.

Test plan

  • pnpm -F @holo/env test — 10 tests pass (new tests cover GATEWAY_INTERNAL_URL default and MCP_PUBLIC_URL derivation including two-origin override)
  • pnpm -F @holo/cli test — 23 tests pass (init wizard no longer emits MCP_PUBLIC_URL)
  • pnpm -F @holo/web test — 59 tests pass (new rewrite-presence + ordering tests)
  • pnpm -F @holo/web -F @holo/gateway -F @holo/env -F @holo/cli typecheck — clean
  • pnpm verify:gateway — HTTP smoke (operator-runnable) verifies /v1/health, /openapi.json, and /mcp 401 + WWW-Authenticate header points at the single origin
  • Operator gate (manual): Claude Desktop / MCP Inspector OAuth + tool call through ${WEB_PUBLIC_URL}/mcp. Required before relying on this in production. See ADR 0009 "Verification" section for the procedure and the cloudflared path-routing fallback if SSE proxying breaks.

Migration for existing two-host deployments

Order matters — see ADR 0009 § "Migration notes for existing deployments":

  1. Add GATEWAY_INTERNAL_URL on the web service pointing at the gateway's internal address.
  2. Set MCP_PUBLIC_URL=$WEB_PUBLIC_URL on gateway/worker (or unset on web — derivation takes over).
  3. Update external services (OAuth callbacks for sign-in + connectors; webhook URLs for Slack, Stripe, GitHub App, Google Chat, Teams) to the single public origin.
  4. Remove the gateway's public domain and DNS record.

The README's Railway section now has an inline "Migrating from a two-host deployment" subsection with the same checklist.

Files

16 commits, ~1500 LOC across:

  • packages/env/src/index.ts + tests (schema)
  • apps/web/next.config.mjs + new test (rewrites)
  • apps/web/src/app/__tests__/gateway-rewrites.test.ts (regression test)
  • docker-compose.yml (compose wiring)
  • packages/cli/src/commands/init.ts + tests (CLI wizard)
  • scripts/verify-mcp-sse.mjs + package.json (HTTP smoke)
  • docs/decisions/0009-single-origin-gateway.md (ADR)
  • docs/superpowers/plans/2026-06-01-single-origin-mcp-gateway.md (implementation plan)
  • README.md, CONTRIBUTING.md, .env.example (docs)

🤖 Generated with Claude Code

maakle and others added 16 commits June 1, 2026 10:01
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
next.config.mjs resolves its types directly in TS 5.6; the suppression is no
longer needed and now fails typecheck with TS2578.
- .env.example: drop the MCP_PUBLIC_URL override block — ADR 0009 is the
  canonical reference now, .env.example stays minimal.
- next.config.mjs: update allowedDevOrigins from holo-app.maakle.com to
  holo.maakle.com to match the new single-origin tunnel hostname.
@maakle
maakle merged commit 0393085 into main Jun 1, 2026
5 checks passed
@maakle
maakle deleted the feat/single-origin-mcp-gateway branch June 1, 2026 09:02
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