Claude-in-Chrome MCP bridge fails on Windows: framing protocol mismatch between MCP client and native host

Resolved 💬 1 comment Opened May 12, 2026 by GutierrezCastilloGonzalo Closed Jun 10, 2026

Summary

On Windows, every mcp__claude-in-chrome__* tool call returns "Browser
extension is not connected". Through end-to-end tracing of the Chrome Native
Messaging protocol and the local IPC pipe, the root cause is a framing
protocol mismatch internal to claude.exe
: the MCP client sends raw
JSON-RPC, but the native host's named-pipe server expects each message to be
prefixed by a 4-byte UInt32-LE length. The server reads the first four bytes
of {"jsonrpc":"2.0",...} as a 1.9 GB "message length", logs Invalid
message length
, destroys the client socket, and tells Chrome
mcp_disconnected.

This is a Claude Code bug, not a Chrome, extension, or Bun runtime issue.

## Environment

  • OS: Windows 11 Pro 10.0.26200
  • Chrome: 148.0.7778.97 (Official Build) (64-bit), Stable
  • Claude Code: 2.1.139 (native build, ~/.local/bin/claude.exe)
  • Claude in Chrome extension: fcoeoabgfenejglbffodgkkbkcdhcgfn version

1.0.70_0

  • Node.js (for diagnostic tooling): v22.16.0

## Steps to Reproduce

  1. Install Claude Code 2.1.139 and the Claude Chrome extension on Windows.
  2. Be signed in to claude.ai in both.
  3. From a Claude Code session, invoke any mcp__claude-in-chrome__* tool

(e.g. tabs_context_mcp).

## Observed Behavior

Tool returns:

``
Browser extension is not connected. Please ensure the Claude browser
extension is installed and running...
``

## Diagnostic Setup

I replaced ~/.claude/chrome/chrome-native-host.bat with a Node.js MITM
wrapper that:

  • Spawns claude.exe --chrome-native-host as a child
  • Forwards stdin/stdout bytes unchanged in both directions
  • Logs every Chrome ↔ native-host message (with length-prefix parsing), plus

the child's stderr

This let me capture exactly what happens when Chrome's extension spawns the
native host and what the host does next. I also wrote a small Node.js test
that connects directly to \\.\pipe\claude-mcp-browser-bridge-<user> to
verify the pipe layer in isolation.

## Captured Trace

```
==== WRAPPER START pid=20756 ====
Spawning: C:\Users\USER\.local\bin\claude.exe --chrome-native-host
Child spawned, pid=21788
CHROME→CLAUDE [15 bytes] {"type":"ping"}
STDERR: [Claude Chrome Native Host] Initializing...
STDERR: [Claude Chrome Native Host] Creating socket listener:
\\.\pipe\claude-mcp-browser-bridge-USER
STDERR: [Claude Chrome Native Host] Socket server listening for connections
STDERR: [Claude Chrome Native Host] Handling Chrome message type: ping
STDERR: [Claude Chrome Native Host] Responding to ping
CLAUDE→CHROME [41 bytes] {"type":"pong","timestamp":1778554589426}
CHROME→CLAUDE [21 bytes] {"type":"get_status"}
STDERR: [Claude Chrome Native Host] Handling Chrome message type: get_status
CLAUDE→CHROME [56 bytes]
{"type":"status_response","native_host_version":"1.0.0"}

# ... time passes; user invokes a tool from Claude Code ...

STDERR: [Claude Chrome Native Host] MCP client 1 connected. Total clients: 1
CLAUDE→CHROME [24 bytes] {"type":"mcp_connected"}
STDERR: [Claude Chrome Native Host] Invalid message length from MCP client 1:
1936335483
STDERR: [Claude Chrome Native Host] MCP client 1 disconnected. Remaining
clients: 0
CLAUDE→CHROME [27 bytes] {"type":"mcp_disconnected"}
```

So the Chrome ↔ native-host channel is healthy. The named pipe is created.
The MCP client successfully reaches it and is accepted. Then the very first
read of message bytes is rejected as "Invalid message length: 1936335483",
and the server destroys the socket.

## Root Cause

Decoded as a little-endian UInt32, 1936335483 is 0x736F6E7B, the four
bytes {"js — the first four characters of {"jsonrpc":"2.0",...}.

The native host's handleMcpClient reads incoming data with
buffer.readUInt32LE(0) and treats it as a length prefix:

``js
H.on("data",(K)=>{
$.buffer = Buffer.concat([$.buffer, K]);
while ($.buffer.length >= 4) {
let _ = $.buffer.readUInt32LE(0);
if (_===0 || _>VUq) {
tj(
Invalid message length from MCP client ${q}: ${_}),
H.destroy();
return;
}
if ($.buffer.length < 4 + _) break;
// ... parse JSON from buffer.slice(4, 4 + _) ...
}
});
``

The client side, however, is writing the JSON payload without the 4-byte
length prefix. The two halves of the same binary are speaking different
framing protocols.

## Independent Confirmation via Node.js

I wrote a minimal Node script that connects to the named pipe and sends raw
JSON.stringify(rpcMessage) + "\n":

``
$ node test-pipe-connect.js
Connecting to: \\.\pipe\claude-mcp-browser-bridge-USER
CONNECTED in 1ms
Sending a test MCP message...
Socket closed
``

The wrapper log shows the same "Invalid message length: 1936335483" for the
Node client. This rules out anything Claude-Code-MCP-client-specific: any
client that sends bare JSON-RPC hits the same wall. The framing on the wire
is the issue.

## Why client-side workarounds didn't work

Before tracing the protocol I tried, in order:

  1. Renaming Claude Desktop's native messaging manifest to .json.disabled

(the macOS-style workaround documented for the Feb-2026 issue).

  1. Killing cowork-svc.exe (Claude Desktop's background service).
  2. Restarting Chrome.
  3. Reloading the extension.
  4. Downgrading Claude Code to 2.1.138 (newest cached version).

None of these changed the outcome, because they all addressed a different
layer than the one that's broken. The Feb-2026 getSocketPaths() regression
that returned Unix-style paths on Windows is fixed in 2.1.139 — sH7()
correctly returns [\\.\pipe\claude-mcp-browser-bridge-USER] on win32 — so
the discovery side is healthy. The break is one layer deeper, in the framing
of bytes on the established pipe connection.

## Suggested Fix

Either:

  1. Update the server to accept raw JSON-RPC framing (e.g.

newline-delimited or Content-Length headers) — matching whatever the MCP
client now emits.

  1. Update the client to prepend each outgoing message with a UInt32-LE

length prefix before writing to the pipe — matching what the server already
expects.

Option 2 is more in keeping with how Chrome's own Native Messaging protocol
frames messages (4-byte length prefix), which is presumably why the server
was written this way.

A binary patch on the user's side is feasible but fragile across versions. A
first-party fix is needed.

## Reproducibility

100% on this machine. Diagnostic wrapper script + named-pipe test script
available on request — happy to attach them or open a PR.

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗