From 1dcb112987f9fd9ae58e6794edb49ed5e4c4e149 Mon Sep 17 00:00:00 2001 From: cliffhall Date: Mon, 14 Sep 2026 00:38:58 -0400 Subject: [PATCH] docs: add the public-address vars to the environment-variable reference (#1862) Document MCP_SANDBOX_FULL_ADDRESS and MCP_APP_ORIGIN_FULL_ADDRESS in docs/environment-variables.md, note that ALLOWED_ORIGINS is now also the sandbox proxy's embedder allow-list, and list public-address.ts among the web server files. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HzuzRPJv7M8wGe3wyw5sSg Signed-off-by: cliffhall --- clients/web/README.md | 2 +- docs/environment-variables.md | 13 ++++++++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/clients/web/README.md b/clients/web/README.md index bb3218f8..bdce4099 100644 --- a/clients/web/README.md +++ b/clients/web/README.md @@ -19,7 +19,7 @@ The `server/` directory holds the Node-only backend: - **`web-server-config.ts`** — env parsing, the `GET /api/config` payload, the startup banner, the default origin allow-list. - **`resolve-bind-host.ts`** — the shared bind-host guard (refuses an all-interfaces `HOST` unless `DANGEROUSLY_BIND_ALL_INTERFACES`), used by both bind points (`web-server-config.ts` + `vite.config.ts`); see [Host binding & the origin allow-list](#host-binding--the-origin-allow-list). - **`inject-auth-token.ts`** — embeds the API token into the served `index.html` (see [Auth token](#auth-token)). -- **`sandbox-controller.ts`** — the MCP Apps sandbox HTTP server; **`app-origin-controller.ts`** — the dedicated app-origin server for `_meta.ui.domain` (see [MCP App dedicated origins](#mcp-app-dedicated-origins-metauidomain)); **`ensure-web-build.ts`** — builds `dist/` on demand for prod `--web`; **`vite-base-config.ts`** — shared `optimizeDeps` exclusions. +- **`sandbox-controller.ts`** — the MCP Apps sandbox HTTP server; **`app-origin-controller.ts`** — the dedicated app-origin server for `_meta.ui.domain` (see [MCP App dedicated origins](#mcp-app-dedicated-origins-metauidomain)); **`public-address.ts`** — validates `MCP_SANDBOX_FULL_ADDRESS` / `MCP_APP_ORIGIN_FULL_ADDRESS`, the public addresses those two advertise behind a reverse proxy; **`ensure-web-build.ts`** — builds `dist/` on demand for prod `--web`; **`vite-base-config.ts`** — shared `optimizeDeps` exclusions. - **`browser-externalized-builtin-gate.ts`** — Vite-agnostic build-gate logic that fails `vite build` when a Node built-in reaches the browser bundle (#1769); the thin Vite plugin wiring lives in `vite.config.ts`. It sits under `server/` (rather than `src/`) as the home for Node-only, build-time tooling — it's imported by the Vite config, never by the browser — alongside the other `vite-*` config helpers here. ## Development diff --git a/docs/environment-variables.md b/docs/environment-variables.md index 70546de2..b0801508 100644 --- a/docs/environment-variables.md +++ b/docs/environment-variables.md @@ -19,7 +19,7 @@ These guard the web backend, which spawns processes on request. Read [Host bindi | `DANGEROUSLY_OMIT_AUTH` | web | unset | Disables the API token entirely. ⚠️ **Any non-empty value turns auth off, including `false` and `0`** — unset the variable to keep auth on. | | `HOST` | web, CLI | `127.0.0.1` | Address the web server binds. An all-interfaces host (`0.0.0.0`, `::`, an empty string, and equivalent spellings) is **refused** unless `DANGEROUSLY_BIND_ALL_INTERFACES` is enabled. The CLI reads it only to build its deep link. | | `DANGEROUSLY_BIND_ALL_INTERFACES` | web | off | Opts in to an all-interfaces `HOST`. Only `true` or `1` (case-insensitive) enable it, so `false` reads as off. The Docker image sets it. | -| `ALLOWED_ORIGINS` | web | derived from `HOST` | Comma-separated origins allowed to call the API. Unset, the list follows `HOST` at `CLIENT_PORT`: the loopback origins for a loopback host, the loopback origins plus `http://0.0.0.0` and `http://[::]` for an all-interfaces bind, and otherwise only the configured host's own origin (so binding a LAN address does **not** also allow `localhost`). **Replaces** the default list rather than adding to it, so list every form you browse from. Each entry must include the scheme (`http://localhost:6274`). | +| `ALLOWED_ORIGINS` | web | derived from `HOST` | Comma-separated origins allowed to call the API. Unset, the list follows `HOST` at `CLIENT_PORT`: the loopback origins for a loopback host, the loopback origins plus `http://0.0.0.0` and `http://[::]` for an all-interfaces bind, and otherwise only the configured host's own origin (so binding a LAN address does **not** also allow `localhost`). **Replaces** the default list rather than adding to it, so list every form you browse from. Each entry must include the scheme (`http://localhost:6274`). The same list is the MCP Apps sandbox proxy's embedder allow-list (its `frame-ancestors` header and its referrer check), so a public Inspector origin must be listed here for the Apps tab to render. | ## Ports @@ -32,6 +32,17 @@ These guard the web backend, which spawns processes on request. Read [Host bindi The sandbox port resolves as: a valid `MCP_SANDBOX_PORT`, else a valid `SERVER_PORT`, else `6275`. The CLI reads `CLIENT_PORT`, `MCP_SANDBOX_PORT`, `MCP_APP_ORIGIN_PORT` and `HOST` only to build the deep link and port list it hands to a web session; it binds none of them. It normalizes `HOST` (an all-interfaces host becomes `localhost`, any other host is canonicalized), but it validates none of the three **port** variables: any non-empty port value is copied into the URLs and port-forwarding command as-is, with no range check, no warning, and no `SERVER_PORT` fallback, so a malformed value produces a broken hand-off rather than an error. +## Public addresses (reverse proxy) + +The sandbox and app-origin servers advertise a URL built from their own bind (`http://localhost:6275/sandbox` under a wildcard bind). Behind a reverse proxy or an ingress, where the browser reaches them at a public hostname, set the address the browser should use instead. Neither changes what is bound: the process still listens on the port above, and routing the public address to it is the proxy's job. See [Behind a reverse proxy](../clients/web/README.md#host-binding--the-origin-allow-list). + +| Variable | Read by | Default | Effect | +| ----------------------------- | ------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `MCP_SANDBOX_FULL_ADDRESS` | web | `http://:/sandbox` | Public URL of the MCP Apps sandbox proxy, returned as `sandboxUrl` by `GET /api/config` and printed in the banner (e.g. `https://inspector-sandbox.example.com/sandbox`). A bare origin gets `/sandbox` appended. An empty value counts as unset. | +| `MCP_APP_ORIGIN_FULL_ADDRESS` | web | `http://:` | Public origin that `_meta.ui.domain` app documents are published under (e.g. `https://inspector-apps.example.com`). **Origin only** — a path is refused, since documents are served at `/app-document/`. An empty value counts as unset. | + +Both are **refused** — ignored with a warning, keeping the bind-derived address — when the value is not an absolute `http(s)` URL, carries credentials, a query string, a fragment or a wildcard, or is a bracketed IPv6 literal. ⚠️ **Each needs its own origin.** The MCP Apps spec requires the sandbox origin to differ from the Inspector's, so a sandbox address sharing an `ALLOWED_ORIGINS` origin is refused (`https://inspector.example.com/sandbox` behind the same hostname as the UI is the common case), as is an app origin equal to the Inspector's or the sandbox's. Neither is used if its pinned port was taken at startup and the server fell back to an OS-assigned one, since the proxy would route it to the wrong listener. A plain-`http` address while `ALLOWED_ORIGINS` lists an `https` origin is used, but warned about: the browser blocks it as mixed content. + ## Behavior | Variable | Read by | Default | Effect |