← Blog/Websocket

Inspect WebSocket traffic on a localhost tunnel

Inspect handshake and frames on a Mercur WebSocket tunnel, share the session, and debug CORS that is actually a 503 from the relay.

A WebSocket client that fails in the browser often leaves you with a CORS banner and no payload. Mercur now ships a WSS session inspector on Solo and Team: each public client on a WebSocket endpoint becomes a Connection you can open, share, and read from an MCP client.

This post covers what shipped, who can use it, how it differs from HTTP history, and the Socket.IO / CORS cases that look like a browser policy problem when the relay actually returned 503.

What shipped

WebSocket endpoints already forwarded wss:// to a local port. The inspector is the missing HTTP-history analogue for those sessions.

Open a running WebSocket endpoint and switch to Connections. Each public client is a row: path, query, state (connecting, open, closed, error), duration, and an event timeline. Select a row for handshake headers and the frames that crossed after Upgrade — text or binary. Ping and pong are not stored.

The list polls every few seconds; Refresh fetches immediately. Filters cover path contains and grouped open/closed. There is no inject into a live socket and no Retry on frames.

Share uses the same 24-hour revocable token as HTTP request history. /share/[token] renders connection history for a WebSocket endpoint. TCP stays unshareable.

Remote MCP gained read-only tools: list_connections, get_connection, list_connection_events (no payloads), and get_connection_event (stored frame). Typical flow: list_endpointslist_connectionsget_connection_event.

After 200 message events on one connection, later frames increment a dropped counter. Close and error still persist. Retention matches HTTP logs: 30 days, then the plan cap drops the oldest rows.

The public URL still looks like this (your subdomain differs):

Example URL
wss://your-endpoint.mercur.sh

Walkthrough: Inspect a WebSocket session.

Who can use it

WebSocket endpoints and the inspector are included on paid plans. Guest mode and the free plan stay HTTP-only. The public webhook inspector is a different product: it records HTTP and answers 204, and it never forwards WebSocket upgrade.

  • Solo / Team — create a WebSocket endpoint, run the macOS app or mercur ws <port>, inspect Connections, share, MCP
  • Guest / Free — HTTP only

Team members share workspace endpoints. See Pricing for the catalog.

What you can use it for

Use it when a browser, mobile app, or bot must hold a WebSocket to a process on your laptop, and you need to see whether Upgrade succeeded and what frames followed.

Local chat, presence, or live boards. Point the client at the public wss:// URL, watch the connection go open, and confirm the first text frame is the payload you expected.

Handshake debugging. The Connections inspector shows the path, query, and headers the public client sent, including Origin. That is the origin Socket.IO CORS will check — the page origin, not the tunnel host.

Share a live failure. Send the 24-hour link so a teammate can read the same handshake without signing in.

Inspect from the IDE. With a workspace API key or OAuth grant, ask an MCP client to list connections instead of pasting console JSON into chat. Setup: Remote MCP.

What it is not

The inspector stores WebSocket frames. It does not decode Socket.IO packets, GraphQL subscriptions, or MQTT.

  • No console send / inject into a live socket
  • No log-only WSS bin without an agent
  • No Socket.IO or GraphQL first-class parser
  • No TCP connection inspector
  • Guest inspector stays HTTP-only

HTTP and WebSocket are different Mercur endpoints. Polling HTTP and Upgrade do not share one tunnel. That split is the usual cause of “CORS” on Socket.IO.

Start using the inspector

Sign in on Solo or Team, create a WebSocket endpoint, and start the agent on the local port. Copy the wss:// URL into the client you are testing. Open Connections and trigger one connect.

Open Endpoints

Leave Public access on if anyone with the URL may connect. Restrict the address list when the caller should be a known office range.

Troubleshooting

The browser reports CORS, curl shows 503

Chrome’s CORS error on XMLHttpRequest to /socket.io/?transport=polling means the response had no Access-Control-Allow-Origin. Mercur error pages (503, 502, 403, 429) are plain text without CORS headers. The browser then reports CORS even when the real problem is “no agent” or “wrong protocol”.

Check with curl before changing Socket.IO cors:

Run
curl -i https://your-endpoint.mercur.sh/socket.io/?EIO=4&transport=polling

503 No agent for client means the relay has no live HTTP agent for that host. Any of these produce the same body:

  • the desktop app or CLI is not running
  • the claim lapsed after 15 seconds without a heartbeat
  • the hostname does not match an endpoint
  • you sent HTTP (Socket.IO polling) to a WebSocket endpoint

Socket.IO polling vs WebSocket endpoints

Default Socket.IO starts with HTTP long-polling, then tries Upgrade: websocket. Mercur HTTP endpoints forward GET/POST and do not proxy WebSocket upgrade. Mercur WebSocket endpoints accept upgrade and do not serve polling.

So:

  • Polling against a WebSocket endpoint → 503 No agent for client → browser CORS
  • Upgrade against an HTTP endpoint → the socket is dropped, no HTTP CORS header

transports: ["websocket"] only helps on a WebSocket endpoint. Serving the HTML from the same tunnel origin avoids cross-origin XHR. If you keep the page on http://localhost:63342, the local Socket.IO server must allow that origin in cors.origin — not the *.mercur.sh host.

The HTTP agent buffers the full upstream body before returning it. Socket.IO long-poll through an HTTP tunnel is a poor fit even after CORS is correct.

Catalog: Troubleshooting.

Connections tab is empty

The tab exists only on WebSocket endpoints. An HTTP endpoint shows request history, not Connections. If the endpoint is WebSocket and the list is empty, the agent is not claimed, or no client completed Upgrade. Failed handshakes can still appear as state: error.

Handshake dropped with no HTTP status

Rejections before Upgrade completes close the socket without an HTTP response. The client reports a reset. Causes match the HTTP list: unknown host, stopped endpoint, address not allowed, no agent, quota, or session limit. Open the endpoint in the console; an error connection row is the inspectable form of that failure.

FAQ

Which plans include the WSS session inspector? Solo and Team. Guest and Free cannot create WebSocket endpoints.

Does Mercur decode Socket.IO events? No. Frames are stored as text or binary. Use the Connections timeline plus your own client logs.

How long does a share link last? 24 hours, same as HTTP request-history share. You can revoke it from the endpoint.

Can an agent read connections over MCP? Yes, on Solo and Team, with the same OAuth grant or workspace API key as HTTP history. Tools are read-only.

Related

Open Endpoints

Read the WebSocket inspector docs

websocketdebugginglocalhost