Skip to content

docs: ALLOWED_ORIGINS recipe for a *.localhost proxy - #2290

Merged
cliffhall merged 2 commits into
v2/mainfrom
v2/docs/2289-allowed-origins-localhost
Sep 7, 2026
Merged

docs: ALLOWED_ORIGINS recipe for a *.localhost proxy#2290
cliffhall merged 2 commits into
v2/mainfrom
v2/docs/2289-allowed-origins-localhost

Conversation

@cliffhall

Copy link
Copy Markdown
Member

Closes #2289

Running the Inspector behind a reverse proxy on a *.localhost name — inspector.localhost, alongside my-api.localhost / my-app.localhost — works today, but only with ALLOWED_ORIGINS set, 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:

  1. ALLOWED_ORIGINS replaces the default list, it does not merge. An entry of only http://inspector.localhost silently breaks browsing at localhost:6274. The loopback trio has to be listed too.
  2. The failure is confusing rather than obvious. A same-origin GET carries no Origin header, 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.
  3. Only the browser resolves *.localhost for 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/hosts or dnsmasq — and with OAuth is refused by the SDK regardless (typescript-sdk#2591).

Also notes that the MCP Apps sandbox frame-ancestors derives 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.

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>
@cliffhall cliffhall added the v2 Issues and PRs for v2 label Sep 7, 2026
@cliffhall
cliffhall requested a balanced review from Copilot September 7, 2026 13:45

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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.

Comment thread clients/web/README.md Outdated
…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>
@cliffhall

Copy link
Copy Markdown
Member Author

Review round 1 — addressed

One finding, correct. My wording implied only the add/connect POSTs are rejected; PUT (save, reorder) and DELETE (remove) carry Origin too and fail identically, since the middleware is method-agnostic and browsers attach Origin to everything that is not GET/HEAD.

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.

npm run local:gate → EXIT=0, zero failures.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟢 Approval recommended

The documentation accurately reflects the implementation and fulfills the linked issue’s requirements.

Review details
  • Files reviewed: 1/1 changed files
  • Comments generated: 0 new
  • Review effort level: Balanced

@cliffhall
cliffhall merged commit a5b356d into v2/main Sep 7, 2026
5 checks passed
@cliffhall
cliffhall deleted the v2/docs/2289-allowed-origins-localhost branch September 7, 2026 14:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

v2 Issues and PRs for v2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

docs: ALLOWED_ORIGINS recipe for running the Inspector behind a *.localhost proxy

2 participants