feat(web): configurable public addresses for the MCP Apps listeners (#1862)

Add MCP_SANDBOX_FULL_ADDRESS and MCP_APP_ORIGIN_FULL_ADDRESS so a reverse
proxy or ingress deployment can advertise the sandbox proxy URL and the
_meta.ui.domain app origin the browser actually reaches, instead of the
bind-derived http://localhost:<port> ones. The process still listens on
MCP_SANDBOX_PORT / MCP_APP_ORIGIN_PORT.

Both values are validated in public-address.ts and refused (warned, bind-
derived address kept) when malformed or when their origin collapses onto
the Inspector UI's (or, for the app origin, the sandbox's) — the MCP Apps
spec requires host != sandbox origin, and the single-hostname proxy layout
is exactly the one that breaks it. Plain http under an https UI is warned.

The sandbox proxy page's referrer check was a hardcoded
http://localhost|127.0.0.1 pattern no https embedder could match. It now
compares the referrer's parsed origin against an exact list injected by the
controller, derived from the same filtered allow-list as its frame-ancestors
header, with the old loopback check kept only as the no-list fallback.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzuzRPJv7M8wGe3wyw5sSg
Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-09-14 00:10:34 -04:00
commit 12544d251c

View file

@ -46,6 +46,7 @@ export async function startHonoServer(
port: config.sandboxPort,
host: config.sandboxHost,
allowedOrigins: config.allowedOrigins,
publicUrl: config.sandboxPublicUrl,
});
await sandboxController.start();
// The dedicated origin apps declaring `_meta.ui.domain` are served from
@ -54,6 +55,7 @@ export async function startHonoServer(
const appOriginController = createAppOriginController({
port: config.appOriginPort,
host: config.sandboxHost,
publicOrigin: config.appOriginPublicOrigin,
embedderOrigins: appDocumentEmbedders(
sandboxController.getUrl(),
config.allowedOrigins,