Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions clients/web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,19 @@ Both the prod backend (`server/web-server-config.ts`) and the dev Vite server (`

The backend's `/api/*` routes also enforce an **origin allow-list** (`allowedOrigins`) as DNS-rebinding protection. When left to default on a loopback host, it expands to all three interchangeable loopback origin forms for the port — `http://localhost:PORT`, `http://127.0.0.1:PORT`, and `http://[::1]:PORT` — because `localhost` resolves to either IPv4 or IPv6 loopback and Node/Vite may bind the IPv6 form, so the browser can legitimately arrive at `http://[::1]:PORT`. Set `ALLOWED_ORIGINS` (comma-separated) to override; entries are canonicalized (`new URL(o).origin`), so a trailing slash / uppercase host / explicit `:80` still match. **Each entry must include the scheme** — `http://localhost:6274`, not `localhost:6274` (a scheme-less value is dropped with a warning). `ALLOWED_ORIGINS` **replaces** the default list (it does not merge), so **list every origin you'll browse from, including the loopback forms** you still want (`http://localhost:PORT`, `http://127.0.0.1:PORT`, `http://[::1]:PORT`) — otherwise local access stops working. A blank `ALLOWED_ORIGINS` does **not** disable the check — it falls back to the default (fail closed); there is no env knob to turn origin validation off.

**Running the Inspector behind a `*.localhost` proxy.** A common local-dev shape gives each service a friendly name — `my-api.localhost`, `my-app.localhost`, `inspector.localhost` — instead of a set of ports. The Inspector works there, but the origin allow-list does not include those names by default, so set it explicitly:

```sh
ALLOWED_ORIGINS=http://inspector.localhost,http://localhost:6274,http://127.0.0.1:6274,http://[::1]:6274 \
mcp-inspector --web
```

⚠️ **List the loopback forms too, not just your proxy origin.** `ALLOWED_ORIGINS` **replaces** the default list rather than merging with it, so an entry of only `http://inspector.localhost` silently breaks browsing at `http://localhost:6274`. Add the port to the proxy origin if it is not on `:80`. The MCP Apps sandbox `frame-ancestors` is derived from the same list, so one entry covers the Apps tab as well.

Without this you get a confusing failure rather than an obvious one: the page loads, because a same-origin `GET` carries no `Origin` header and so never reaches the guard, while **every state-changing request is rejected with a 403** — browsers attach `Origin` to anything that is not a `GET`/`HEAD`. Adding and connecting a server (`POST`) are the most visible, but saving settings, reordering and deleting a server (`PUT`/`DELETE`) fail the same way. The result reads as a connection problem rather than a configuration one.

⚠️ **Only the browser resolves `*.localhost` for free.** Chrome and Firefox map those names to loopback internally per [RFC 6761 §6.3](https://www.rfc-editor.org/info/rfc6761/); the OS resolver on macOS does **not**, and Safari does not resolve them at all. That is fine for reaching the Inspector, but an **MCP server** URL on such a host is dialled by the Inspector's Node backend, so it still needs an `/etc/hosts` entry or dnsmasq. Note also that an MCP server on a `*.localhost` host **using OAuth** is currently refused by the SDK, which exempts only `localhost`, `127.0.0.1` and `::1` from its TLS requirement — see [typescript-sdk#2591](https://github.com/modelcontextprotocol/typescript-sdk/issues/2591).

### Hosting on a network

The guard blocks only the **wildcard** all-interfaces addresses. Binding a **specific** IP or hostname is allowed with no opt-in — that's a single, deliberate exposure, unlike the wildcard which binds every interface at once (the pattern DNS-rebinding exploits). To serve the Inspector on a LAN or the internet:
Expand Down