docs: ALLOWED_ORIGINS recipe for a *.localhost proxy - #2290
Conversation
Closes #2289 Running the Inspector behind a reverse proxy on a *.localhost name works today, but only with ALLOWED_ORIGINS set, and nothing said so or showed the shape. Raised by the reporter of #1944, whose whole service cluster uses those names. Three things the recipe has to carry, all learned the expensive way: - ALLOWED_ORIGINS REPLACES the default list rather than merging, so an entry of only the proxy origin silently breaks browsing at localhost:PORT. The loopback trio has to be listed too. - The failure is confusing rather than obvious. A same-origin GET carries no Origin header, so the page loads and only the POSTs that add or connect a server are rejected — which 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 per RFC 6761; the OS resolver on macOS does not and Safari does not at all, so an MCP *server* on such a host still needs /etc/hosts or dnsmasq — the Node backend is what dials it — and with OAuth is refused by the SDK regardless (typescript-sdk#2591). Documented rather than changing the default: see #2282 for why ~1,100 lines of origin and CSP code was a bad trade for removing one env var. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MvNbmAtQuWhDvzi4yXDju Signed-off-by: cliffhall <cliff@futurescale.com>
There was a problem hiding this comment.
🟡 Changes recommended
The troubleshooting text incorrectly implies that only POST requests fail, while PUT and DELETE operations are also rejected.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
Documents how to configure ALLOWED_ORIGINS when proxying the web Inspector through a *.localhost hostname.
Changes:
- Adds a complete proxy-origin configuration example.
- Documents loopback, MCP Apps, browser-resolution, and OAuth caveats.
File summaries
| File | Description |
|---|---|
clients/web/README.md |
Adds the *.localhost reverse-proxy recipe and troubleshooting guidance. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 1
- Review effort level: Balanced
💡 Add a code-review agent skill for context-aware, tailored reviews. Learn more in the docs.
…ound 1) The troubleshooting note said only the POSTs that add or connect a server are rejected. Verified otherwise: core/react/useServers.ts sends PUT for saving and reordering and DELETE for removing, the origin middleware is method-agnostic, and browsers attach Origin to anything that is not a GET/HEAD. So settings saves, reorders and deletes fail identically. That matters for a troubleshooting doc — someone whose *save* silently fails would not have recognised themselves in the old wording. The POSTs stay as the most visible examples. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018MvNbmAtQuWhDvzi4yXDju Signed-off-by: cliffhall <cliff@futurescale.com>
Review round 1 — addressedOne finding, correct. My wording implied only the add/connect POSTs are rejected; Now "every state-changing request", with the POSTs kept as the visible examples. For a troubleshooting note the precision is the whole value — someone whose save silently fails needs to recognise themselves in the text.
|
Closes #2289
Running the Inspector behind a reverse proxy on a
*.localhostname —inspector.localhost, alongsidemy-api.localhost/my-app.localhost— works today, but only withALLOWED_ORIGINSset, and nothing in the docs said so or showed the shape. Raised by the reporter of #1944, whose whole service cluster uses those names.Documented rather than changing the default: #2282 has the reasoning, but briefly, ~1,100 lines of security-sensitive origin and CSP code across four layers was a poor trade for removing one environment variable from a setup that already works.
Three things the recipe has to carry
Each was learned the expensive way while investigating #1944, and each is the kind of thing that turns a five-minute fix into an afternoon:
ALLOWED_ORIGINSreplaces the default list, it does not merge. An entry of onlyhttp://inspector.localhostsilently breaks browsing atlocalhost:6274. The loopback trio has to be listed too.GETcarries noOriginheader, so it never reaches the guard — the page loads fine, and only the POSTs that add or connect a server are rejected with a 403. That reads as a connection problem, not a configuration one, which is why it is worth documenting rather than leaving to be rediscovered.*.localhostfor free. Chrome and Firefox map those names to loopback per RFC 6761 §6.3; the OS resolver on macOS does not, and Safari does not at all. Fine for reaching the Inspector; an MCP server on such a host is dialled by the Node backend and still needs/etc/hostsor dnsmasq — and with OAuth is refused by the SDK regardless (typescript-sdk#2591).Also notes that the MCP Apps sandbox
frame-ancestorsderives from the same list, so one entry covers the Apps tab.Testing
Docs only, no behavior change.
npm run format+npm run local:gate→ EXIT=0, zero failures.