Skip to content

Neo MCP rejects Electron clients with Fetch Metadata headers #2458

Description

@hackzyl

Issue Type

Browser Issue

Operating System

Windows

Description of the bug

Description

BrowserOS neo's MCP endpoint (Streamable HTTP) rejects any request that carries browser-default Fetch Metadata headers (Sec-Fetch-Site / Sec-Fetch-Mode / Sec-Fetch-Dest) with 403 {"error":"unsupported request"}. Requests carrying an Origin header are rejected as well.

This makes the neo endpoint unusable from Electron-based MCP clients such as Cherry Studio: Electron's network stack automatically attaches Sec-Fetch-* headers to outgoing requests, and apps cannot strip them. Other GUI clients built on Chromium/Electron likely hit the same wall.

The legacy BrowserOS MCP server (browseros_mcp v0.0.127) is less strict: it rejects Origin but accepts Sec-Fetch-*, so the same client connects to it natively. In short: the stricter request filter introduced in neo is what breaks Electron-based clients.

Test results (BrowserOS neo 0.0.44 vs legacy 0.0.127, Windows 11)

Request headers neo (0.0.44) legacy (0.0.127)
none (plain MCP initialize) 200 200
Sec-Fetch-Site/Mode/Dest only 403 {"error":"unsupported request"} 200
Origin only 403 {"error":"unsupported request"} 403

Impact

Cherry Studio (and likely other Electron-based MCP clients) cannot connect to neo directly. The only workaround is a local stdio bridge (npx mcp-remote http://127.0.0.1:9010/mcp --transport http-only), which adds a resident process and, in my testing, also breaks the run tool's output-schema negotiation (mcp-remote 0.8.2).

Suggested fix

Relax the request filter to match the legacy server's behavior: keep Origin/Host validation (DNS-rebinding protection — see #2424 for analysis of this filter layer), but allow requests that carry no Origin and only the browser-default Sec-Fetch-* metadata, which are added automatically by Chromium/Electron and are not attacker-controlled.

Steps to Reproduce

  1. Start BrowserOS neo (MCP endpoint at http://127.0.0.1:9010/mcp).

  2. POST a normal MCP initialize request — it returns 200:

curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:9010/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
  1. Repeat the exact same request with only the browser-default Fetch Metadata headers added — it returns 403 with body {"error":"unsupported request"}:
curl -s -o /dev/null -w "%{http_code}\n" -X POST http://127.0.0.1:9010/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -H "Sec-Fetch-Site: same-site" -H "Sec-Fetch-Mode: cors" -H "Sec-Fetch-Dest: empty" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"t","version":"1"}}}'
  1. Compare with the legacy MCP server (port 9000, browseros_mcp v0.0.127): the same request with Sec-Fetch-* headers returns 200, confirming the rejection is a neo-only regression.

Screenshots / Videos

No response

BrowserOS Version

BrowserOS neo 0.0.44 (MCP server version)

Additional Context

Feature request (bonus): please consider adding Cherry Studio (open-source, supports Streamable HTTP MCP servers) to the one-click MCP setup board in the neo cockpit.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions