claude-code - 💡(How to fix) Fix Claude-in-Chrome MCP bridge fails on Windows: framing protocol mismatch between MCP client and native host

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

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:

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).
  2. Killing cowork-svc.exe (Claude Desktop's background service).
  3. Restarting Chrome.
  4. Reloading the extension.
  5. 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.
  2. 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.

Root Cause

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.

Fix Action

Fix / Workaround

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.

Why client-side workarounds didn't work

  1. Renaming Claude Desktop's native messaging manifest to .json.disabled (the macOS-style workaround documented for the Feb-2026 issue).
  2. Killing cowork-svc.exe (Claude Desktop's background service).
  3. Restarting Chrome.
  4. Reloading the extension.
  5. Downgrading Claude Code to 2.1.138 (newest cached version).

Code Example

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

---

==== WRAPPER START pid=20756 ====
  Spawning: C:\Users\USER\.local\bin\claude.exe --chrome-native-host
  Child spawned, pid=21788
  CHROMECLAUDE [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
  CLAUDECHROME [41 bytes] {"type":"pong","timestamp":1778554589426}
  CHROMECLAUDE [21 bytes] {"type":"get_status"}
  STDERR: [Claude Chrome Native Host] Handling Chrome message type: get_status
  CLAUDECHROME [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
  CLAUDECHROME [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
  CLAUDECHROME [27 bytes] {"type":"mcp_disconnected"}

---

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 + _) ...
    }
  });

---

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

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:

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).
  2. Killing cowork-svc.exe (Claude Desktop's background service).
  3. Restarting Chrome.
  4. Reloading the extension.
  5. 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.
  2. 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.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING