feat: enable Claude in Chrome MCP with full browser control#93
Conversation
Replace the 6-line stub in @ant/claude-for-chrome-mcp with the complete implementation (8 files, 3038 lines) from the reference project. Provides 17 browser tools: navigate, screenshot, click, type, read DOM, execute JS, record GIF, monitor console/network, manage tabs, etc. No feature flag needed. No changes to src/ (already matches official). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughImplements a complete Chrome MCP (Model Context Protocol) integration, replacing stub implementations with full-featured bridge WebSocket client, multi-path socket clients, socket pool management, 17 browser tool definitions, request routing, permission handling, and supporting type definitions. Total ~3,389 lines of implementation code plus documentation. Changes
Sequence Diagram(s)sequenceDiagram
participant CLI as Claude Code CLI
participant Server as MCP Server
participant Client as Socket Client<br/>(Bridge/Pool/Direct)
participant Ext as Chrome Extension<br/>or Server
CLI->>Server: startup / ensureConnected()
Server->>Client: ensureConnected()
Client->>Ext: connect (WebSocket / Socket)
Ext-->>Client: connected / authenticated
Client-->>Server: connection ready
CLI->>Server: /chrome command<br/>(tool request)
Server->>Server: ListToolsRequest
Server-->>CLI: return BROWSER_TOOLS
CLI->>Server: CallToolRequest<br/>(e.g., read_page)
Server->>Server: handleToolCall()
Server->>Client: ensureConnected()
Client-->>Server: connected ✓
Server->>Client: callTool(name, args)
alt Bridge Mode
Client->>Ext: tool_call message<br/>(JSON)
Ext->>Ext: execute tool
Ext-->>Client: tool_result message
Client-->>Server: normalized response
else Socket Mode
Client->>Ext: execute_tool request<br/>(framed)
Ext->>Ext: execute tool
Ext-->>Client: tool response<br/>(framed)
Client-->>Server: parsed response
end
Server->>Server: normalize & convert<br/>(images, errors, auth)
Server-->>CLI: CallToolResult<br/>(content blocks)
sequenceDiagram
participant Client as BridgeClient
participant WS as Bridge<br/>WebSocket
participant Ext1 as Extension A<br/>(deviceId:X)
participant Ext2 as Extension B<br/>(deviceId:Y)
Client->>Client: first callTool() → no extensions known
Client->>WS: list_extensions request
WS->>Ext1: discover
WS->>Ext2: discover
Ext1-->>WS: peer_connected
Ext2-->>WS: peer_connected
WS-->>Client: [ext_a, ext_b]
Client->>Client: no persisted match → broadcast pairing_request
Ext1->>Client: pairing_response (ext_a)
Client->>Client: select ext_a, persist deviceId:X
Client->>Ext1: tool_call (target: deviceId:X)
Ext1->>Ext1: execute browser action
Ext1-->>Client: tool_result
Client-->>Client: resolve pending call
Client->>Client: switchBrowser() requested
Client->>WS: pairing_request (clear prior)
Ext2->>Client: pairing_response (ext_b)
Client->>Client: switch to deviceId:Y
Client-->>Client: return { deviceId:Y, name:... }
Estimated code review effort🎯 4 (Complex) | ⏱️ ~75 minutes Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (4)
packages/@ant/claude-for-chrome-mcp/src/toolCalls.ts (1)
85-98: Consider adding runtime validation for response structure.The type assertion at line 85-88 assumes the response follows the expected
{ result?: ...; error?: ... }shape. While the protocol is well-defined, defensive validation would improve robustness against malformed extension responses.const response = await socketClient.callTool(name, args, permissionOverrides); // Current: Type assertion const { result, error } = response as { result?: ...; error?: ... }; // Safer: Runtime check if (typeof response !== 'object' || response === null) { return { content: [{ type: "text", text: "Tool execution completed" }] }; } const { result, error } = response as { result?: ...; error?: ... };This is a minor defensive coding suggestion; the current implementation will work correctly with conforming extensions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@ant/claude-for-chrome-mcp/src/toolCalls.ts around lines 85 - 98, Add defensive runtime validation for the tool call response before asserting its shape: after the socketClient.callTool(...) call, verify response is a non-null object and that at least one of response.result or response.error exists and is an object or string; if validation fails, return the existing fallback ({ content: [{ type: "text", text: "Tool execution completed" }] }). Then safely destructure into result and error (used by contentData and isError) knowing the shape is valid. Ensure validations reference the existing symbols response, result, error, contentData, and isError so the fallback behavior stays identical when the response is malformed.docs/features/claude-in-chrome-mcp.md (1)
16-51: Add language specifiers to fenced code blocks.Static analysis flagged these code blocks as missing language specifiers. For the ASCII flow diagram (line 16) and directory structure (line 132), use
textorplaintextas the language identifier.📝 Suggested fix
Line 16:
-``` +```text CLI 启动Line 132:
-``` +```text packages/@ant/claude-for-chrome-mcp/src/Also applies to: 132-142
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@docs/features/claude-in-chrome-mcp.md` around lines 16 - 51, Add a language specifier (e.g., text or plaintext) to the fenced code blocks that contain the ASCII flow diagram and the directory listing shown in the diff (the block starting with "CLI 启动" and the block showing "packages/@ant/claude-for-chrome-mcp/src/"); update those triple-backtick fences to ```text so static analysis no longer flags them as missing language identifiers.packages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts (1)
249-285: Polling loop continues even if connect() throws.In
ensureConnected(), ifconnect()at line 257 throws an error (e.g., security validation failure), the polling loop at lines 275-283 will continue checkingthis.connectedindefinitely until the 5-second timeout. The error fromconnect()is not propagated.This is acceptable behavior since:
- Security failures are logged in
connect()- The timeout will eventually resolve the promise
- The caller will get
falsefrom the rejection, indicating connection failedHowever, for faster failure feedback on security errors, you could track the validation failure state.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts around lines 249 - 285, ensureConnected() starts polling after calling connect(), but if connect() throws (e.g., security validation failure) the polling loop keeps running until the timeout and the original error is lost; change ensureConnected() to catch errors from this.connect() and record the failure (e.g., set a new property like this.lastConnectError or this.connectError), then have the polling routine (in ensureConnected) check that property and immediately reject with that stored error (and clear timers) instead of waiting for the 5s timeout; reference ensureConnected, connect, this.connected, this.connecting and add a short-lived error flag/property to propagate connect() failures quickly.packages/@ant/claude-for-chrome-mcp/src/bridgeClient.ts (1)
8-17: Use the configuredsrc/alias for these imports.These new relative imports break the repository import rule for TypeScript files. Please switch them to the configured
src/path alias before merging.As per coding guidelines, "Import
src/path alias via tsconfig mapping instead of relative paths in imports".🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 8 - 17, Replace the relative imports that pull in SocketConnectionError and the type symbols from "./mcpSocketClient.js" and "./types.js" with the configured src/ path alias imports (e.g. import { SocketConnectionError } from "src/mcpSocketClient" and import { localPlatformLabel, type BridgePermissionRequest, type ChromeExtensionInfo, type ClaudeForChromeContext, type PermissionMode, type PermissionOverrides, type SocketClient } from "src/types"), preserving the exact imported identifiers; update any file extension usage to match the project's tsconfig path mapping so TypeScript resolves the aliased modules.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts:
- Around line 567-574: The log and telemetry currently include the full
user-specific WebSocket URL (wsUrl) and potentially raw bridge messages; update
the code around wsUrl construction and usage (references: wsUrl,
bridgeConfig.url, serverName, logger.info, trackEvent) to redact sensitive
components before logging or tracking by replacing or removing the userId and
any query/path fragments, and ensure trackEvent only sends a minimal allowlist
of safe fields (e.g., host/origin and connection status) rather than the full
URL or raw message payloads; apply the same redaction when logging in the
connection/handshake code paths (also at the other affected site around the code
referenced at lines 617-619).
- Around line 529-564: Awaiting bridgeConfig.getUserId() and
bridgeConfig.getOAuthToken() is unprotected so if either rejects connect()
leaves this.connecting true; wrap each await (or the whole credential-fetch
block in connect()) in a try/catch, and in the catch set this.connecting =
false, compute durationMs using this.connectionStartTime, logger.error with the
caught error, call trackEvent with an appropriate error_type (e.g.,
"credential_lookup_error"), invoke this.context.onAuthenticationError?.(), and
then return/exit so reconnect attempts and ensureConnected() aren’t blocked.
- Around line 626-660: The close/error handlers for this.ws (inside the
ws.on("close", ...) and ws.on("error", ...) blocks) currently schedule
reconnection but leave this.pendingCalls unresolved, causing callers to hang;
add logic to immediately fail and clear all in-flight calls when the socket
drops by iterating this.pendingCalls (or similar pending call store), invoking
each stored reject/callback with a descriptive Error (e.g. "bridge disconnected"
or include code/message), removing entries from the map, and then proceed to set
connected/authenticated flags and call this.scheduleReconnect(); alternatively
extract this into a helper like failPendingCalls(reason) and call it from both
the close and error handlers.
- Around line 452-471: Before calling queryBridgeExtensions(), first check the
WebSocket connection state (this.ws and this.ws.readyState === WebSocket.OPEN)
and return null immediately if the socket is not open; move the existing
readiness check (this.ws?.readyState !== WebSocket.OPEN) to precede the
queryBridgeExtensions() call in the method that uses
selectedDeviceId/previousSelectedDeviceId/pendingPairingRequestId so you don't
wait DISCOVERY_TIMEOUT_MS and mistakenly return "no_other_browsers".
In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts:
- Around line 144-149: The logger calls in the isToolResponse branch and the
final else currently interpolate the message object into a template literal
(producing “[object Object]”); change those logger.info calls to either pass the
object as a separate argument (e.g. logger.info(`[${serverName}] Received tool
response:`, message) and logger.info(`[${serverName}] Received unknown
message:`, message)) or stringify the object (e.g. JSON.stringify(message)) so
the actual message content is logged; update the two spots around
isToolResponse, logger.info and the final else to use one of these approaches
and leave handleResponse(message) unchanged.
---
Nitpick comments:
In `@docs/features/claude-in-chrome-mcp.md`:
- Around line 16-51: Add a language specifier (e.g., text or plaintext) to the
fenced code blocks that contain the ASCII flow diagram and the directory listing
shown in the diff (the block starting with "CLI 启动" and the block showing
"packages/@ant/claude-for-chrome-mcp/src/"); update those triple-backtick fences
to ```text so static analysis no longer flags them as missing language
identifiers.
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts:
- Around line 8-17: Replace the relative imports that pull in
SocketConnectionError and the type symbols from "./mcpSocketClient.js" and
"./types.js" with the configured src/ path alias imports (e.g. import {
SocketConnectionError } from "src/mcpSocketClient" and import {
localPlatformLabel, type BridgePermissionRequest, type ChromeExtensionInfo, type
ClaudeForChromeContext, type PermissionMode, type PermissionOverrides, type
SocketClient } from "src/types"), preserving the exact imported identifiers;
update any file extension usage to match the project's tsconfig path mapping so
TypeScript resolves the aliased modules.
In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts:
- Around line 249-285: ensureConnected() starts polling after calling connect(),
but if connect() throws (e.g., security validation failure) the polling loop
keeps running until the timeout and the original error is lost; change
ensureConnected() to catch errors from this.connect() and record the failure
(e.g., set a new property like this.lastConnectError or this.connectError), then
have the polling routine (in ensureConnected) check that property and
immediately reject with that stored error (and clear timers) instead of waiting
for the 5s timeout; reference ensureConnected, connect, this.connected,
this.connecting and add a short-lived error flag/property to propagate connect()
failures quickly.
In `@packages/`@ant/claude-for-chrome-mcp/src/toolCalls.ts:
- Around line 85-98: Add defensive runtime validation for the tool call response
before asserting its shape: after the socketClient.callTool(...) call, verify
response is a non-null object and that at least one of response.result or
response.error exists and is an object or string; if validation fails, return
the existing fallback ({ content: [{ type: "text", text: "Tool execution
completed" }] }). Then safely destructure into result and error (used by
contentData and isError) knowing the shape is valid. Ensure validations
reference the existing symbols response, result, error, contentData, and isError
so the fallback behavior stays identical when the response is malformed.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 653a9c53-3dd2-4285-b67f-b4b72e5ee86c
📒 Files selected for processing (10)
DEV-LOG.mddocs/features/claude-in-chrome-mcp.mdpackages/@ant/claude-for-chrome-mcp/src/bridgeClient.tspackages/@ant/claude-for-chrome-mcp/src/browserTools.tspackages/@ant/claude-for-chrome-mcp/src/index.tspackages/@ant/claude-for-chrome-mcp/src/mcpServer.tspackages/@ant/claude-for-chrome-mcp/src/mcpSocketClient.tspackages/@ant/claude-for-chrome-mcp/src/mcpSocketPool.tspackages/@ant/claude-for-chrome-mcp/src/toolCalls.tspackages/@ant/claude-for-chrome-mcp/src/types.ts
| const extensions = await this.queryBridgeExtensions(); | ||
| const currentDeviceId = | ||
| this.selectedDeviceId ?? this.previousSelectedDeviceId; | ||
| if ( | ||
| extensions.length === 0 || | ||
| (extensions.length === 1 && | ||
| (!currentDeviceId || extensions[0]!.deviceId === currentDeviceId)) | ||
| ) { | ||
| return "no_other_browsers"; | ||
| } | ||
|
|
||
| this.previousSelectedDeviceId = this.selectedDeviceId; | ||
| this.selectedDeviceId = undefined; | ||
| this.discoveryComplete = false; | ||
| this.pairingInProgress = false; | ||
|
|
||
| const requestId = crypto.randomUUID(); | ||
| this.pendingPairingRequestId = requestId; | ||
| if (this.ws?.readyState !== WebSocket.OPEN) { | ||
| return null; |
There was a problem hiding this comment.
Check the socket state before listing extensions.
When the bridge is already disconnected, queryBridgeExtensions() waits for DISCOVERY_TIMEOUT_MS and returns [], so this path reports "no_other_browsers" after ~5 seconds instead of the disconnected null case.
Suggested fix
public async switchBrowser(): Promise<
| {
deviceId: string;
name: string;
}
| "no_other_browsers"
| null
> {
+ if (this.ws?.readyState !== WebSocket.OPEN) {
+ return null;
+ }
+
const extensions = await this.queryBridgeExtensions();
const currentDeviceId =
this.selectedDeviceId ?? this.previousSelectedDeviceId;
if (
extensions.length === 0 ||
(extensions.length === 1 &&
(!currentDeviceId || extensions[0]!.deviceId === currentDeviceId))
) {
return "no_other_browsers";
}
@@
- if (this.ws?.readyState !== WebSocket.OPEN) {
- return null;
- }
this.ws.send(📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| const extensions = await this.queryBridgeExtensions(); | |
| const currentDeviceId = | |
| this.selectedDeviceId ?? this.previousSelectedDeviceId; | |
| if ( | |
| extensions.length === 0 || | |
| (extensions.length === 1 && | |
| (!currentDeviceId || extensions[0]!.deviceId === currentDeviceId)) | |
| ) { | |
| return "no_other_browsers"; | |
| } | |
| this.previousSelectedDeviceId = this.selectedDeviceId; | |
| this.selectedDeviceId = undefined; | |
| this.discoveryComplete = false; | |
| this.pairingInProgress = false; | |
| const requestId = crypto.randomUUID(); | |
| this.pendingPairingRequestId = requestId; | |
| if (this.ws?.readyState !== WebSocket.OPEN) { | |
| return null; | |
| if (this.ws?.readyState !== WebSocket.OPEN) { | |
| return null; | |
| } | |
| const extensions = await this.queryBridgeExtensions(); | |
| const currentDeviceId = | |
| this.selectedDeviceId ?? this.previousSelectedDeviceId; | |
| if ( | |
| extensions.length === 0 || | |
| (extensions.length === 1 && | |
| (!currentDeviceId || extensions[0]!.deviceId === currentDeviceId)) | |
| ) { | |
| return "no_other_browsers"; | |
| } | |
| this.previousSelectedDeviceId = this.selectedDeviceId; | |
| this.selectedDeviceId = undefined; | |
| this.discoveryComplete = false; | |
| this.pairingInProgress = false; | |
| const requestId = crypto.randomUUID(); | |
| this.pendingPairingRequestId = requestId; |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 452 -
471, Before calling queryBridgeExtensions(), first check the WebSocket
connection state (this.ws and this.ws.readyState === WebSocket.OPEN) and return
null immediately if the socket is not open; move the existing readiness check
(this.ws?.readyState !== WebSocket.OPEN) to precede the queryBridgeExtensions()
call in the method that uses
selectedDeviceId/previousSelectedDeviceId/pendingPairingRequestId so you don't
wait DISCOVERY_TIMEOUT_MS and mistakenly return "no_other_browsers".
| logger.debug(`[${serverName}] Fetching user ID for bridge connection`); | ||
| const fetchedUserId = await bridgeConfig.getUserId(); | ||
| if (!fetchedUserId) { | ||
| const durationMs = Date.now() - this.connectionStartTime; | ||
| logger.error( | ||
| `[${serverName}] No user ID available after ${durationMs}ms`, | ||
| ); | ||
| trackEvent?.("chrome_bridge_connection_failed", { | ||
| duration_ms: durationMs, | ||
| error_type: "no_user_id", | ||
| reconnect_attempt: this.reconnectAttempts, | ||
| }); | ||
| this.connecting = false; | ||
| this.context.onAuthenticationError?.(); | ||
| return; | ||
| } | ||
| userId = fetchedUserId; | ||
|
|
||
| logger.debug( | ||
| `[${serverName}] Fetching OAuth token for bridge connection`, | ||
| ); | ||
| token = await bridgeConfig.getOAuthToken(); | ||
| if (!token) { | ||
| const durationMs = Date.now() - this.connectionStartTime; | ||
| logger.error( | ||
| `[${serverName}] No OAuth token available after ${durationMs}ms`, | ||
| ); | ||
| trackEvent?.("chrome_bridge_connection_failed", { | ||
| duration_ms: durationMs, | ||
| error_type: "no_oauth_token", | ||
| reconnect_attempt: this.reconnectAttempts, | ||
| }); | ||
| this.connecting = false; | ||
| this.context.onAuthenticationError?.(); | ||
| return; | ||
| } |
There was a problem hiding this comment.
Clear this.connecting if credential lookup throws.
bridgeConfig.getUserId() and bridgeConfig.getOAuthToken() are awaited outside any try/catch. If either rejects, connect() exits with this.connecting still true, which blocks later reconnect attempts and makes ensureConnected() poll until its timeout.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 529 -
564, Awaiting bridgeConfig.getUserId() and bridgeConfig.getOAuthToken() is
unprotected so if either rejects connect() leaves this.connecting true; wrap
each await (or the whole credential-fetch block in connect()) in a try/catch,
and in the catch set this.connecting = false, compute durationMs using
this.connectionStartTime, logger.error with the caught error, call trackEvent
with an appropriate error_type (e.g., "credential_lookup_error"), invoke
this.context.onAuthenticationError?.(), and then return/exit so reconnect
attempts and ensureConnected() aren’t blocked.
| // Connect to user-specific endpoint: /chrome/<user_id> | ||
| const wsUrl = `${bridgeConfig.url}/chrome/${userId}`; | ||
| logger.info(`[${serverName}] Connecting to bridge: ${wsUrl}`); | ||
|
|
||
| // Track connection started | ||
| trackEvent?.("chrome_bridge_connection_started", { | ||
| bridge_url: wsUrl, | ||
| }); |
There was a problem hiding this comment.
Redact sensitive bridge data from logs and telemetry.
wsUrl includes the user-specific /chrome/${userId} path, and raw bridge messages can contain URLs, tab metadata, DOM text, and tool results. Both of these paths persist sensitive browsing/account data that should be redacted down to a small allowlist of safe fields.
Also applies to: 617-619
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 567 -
574, The log and telemetry currently include the full user-specific WebSocket
URL (wsUrl) and potentially raw bridge messages; update the code around wsUrl
construction and usage (references: wsUrl, bridgeConfig.url, serverName,
logger.info, trackEvent) to redact sensitive components before logging or
tracking by replacing or removing the userId and any query/path fragments, and
ensure trackEvent only sends a minimal allowlist of safe fields (e.g.,
host/origin and connection status) rather than the full URL or raw message
payloads; apply the same redaction when logging in the connection/handshake code
paths (also at the other affected site around the code referenced at lines
617-619).
| this.ws.on("close", (code: number) => { | ||
| const durationSinceConnect = this.connectionEstablishedTime | ||
| ? Date.now() - this.connectionEstablishedTime | ||
| : 0; | ||
| logger.info( | ||
| `[${serverName}] Bridge connection closed (code: ${code}, duration: ${durationSinceConnect}ms)`, | ||
| ); | ||
| trackEvent?.("chrome_bridge_disconnected", { | ||
| close_code: code, | ||
| duration_since_connect_ms: durationSinceConnect, | ||
| reconnect_attempt: this.reconnectAttempts + 1, | ||
| }); | ||
| this.connected = false; | ||
| this.authenticated = false; | ||
| this.connecting = false; | ||
| this.connectionEstablishedTime = null; | ||
| this.scheduleReconnect(); | ||
| }); | ||
|
|
||
| this.ws.on("error", (error: Error) => { | ||
| const durationMs = this.connectionStartTime | ||
| ? Date.now() - this.connectionStartTime | ||
| : 0; | ||
| logger.error( | ||
| `[${serverName}] Bridge WebSocket error after ${durationMs}ms: ${error.message}`, | ||
| ); | ||
| trackEvent?.("chrome_bridge_connection_failed", { | ||
| duration_ms: durationMs, | ||
| error_type: "websocket_error", | ||
| reconnect_attempt: this.reconnectAttempts, | ||
| }); | ||
| this.connected = false; | ||
| this.authenticated = false; | ||
| this.connecting = false; | ||
| }); |
There was a problem hiding this comment.
Fail in-flight work immediately when the socket drops.
The close/error handlers schedule reconnects, but they leave pendingCalls alive until each tool timeout expires. A mid-call disconnect can therefore stall the caller for up to 120 seconds even though that bridge session is already gone.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/`@ant/claude-for-chrome-mcp/src/bridgeClient.ts around lines 626 -
660, The close/error handlers for this.ws (inside the ws.on("close", ...) and
ws.on("error", ...) blocks) currently schedule reconnection but leave
this.pendingCalls unresolved, causing callers to hang; add logic to immediately
fail and clear all in-flight calls when the socket drops by iterating
this.pendingCalls (or similar pending call store), invoking each stored
reject/callback with a descriptive Error (e.g. "bridge disconnected" or include
code/message), removing entries from the map, and then proceed to set
connected/authenticated flags and call this.scheduleReconnect(); alternatively
extract this into a helper like failPendingCalls(reason) and call it from both
the close and error handlers.
| } else if (isToolResponse(message)) { | ||
| logger.info(`[${serverName}] Received tool response: ${message}`); | ||
| this.handleResponse(message); | ||
| } else { | ||
| logger.info(`[${serverName}] Received unknown message: ${message}`); | ||
| } |
There was a problem hiding this comment.
Fix object logging to show actual content.
Lines 145 and 148 log the message object directly using template literal interpolation, which will output [object Object] instead of the actual content.
🔧 Suggested fix
} else if (isToolResponse(message)) {
- logger.info(`[${serverName}] Received tool response: ${message}`);
+ logger.info(`[${serverName}] Received tool response: ${JSON.stringify(message)}`);
this.handleResponse(message);
} else {
- logger.info(`[${serverName}] Received unknown message: ${message}`);
+ logger.info(`[${serverName}] Received unknown message: ${JSON.stringify(message)}`);
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/`@ant/claude-for-chrome-mcp/src/mcpSocketClient.ts around lines 144
- 149, The logger calls in the isToolResponse branch and the final else
currently interpolate the message object into a template literal (producing
“[object Object]”); change those logger.info calls to either pass the object as
a separate argument (e.g. logger.info(`[${serverName}] Received tool response:`,
message) and logger.info(`[${serverName}] Received unknown message:`, message))
or stringify the object (e.g. JSON.stringify(message)) so the actual message
content is logged; update the two spots around isToolResponse, logger.info and
the final else to use one of these approaches and leave handleResponse(message)
unchanged.
|
@amDosion 你是有 claude 订阅吗, 我这边显示要有订阅才行 |
|
需要有订阅,并且安装claude的官方浏览器插件才可以. (你可以更新说面,目前官方没有开放插件的api,要么就是自己写个浏览器插件挂进来也可以)
121802744
***@***.***
---- Replied Message ----
***@***.***>Date4/3/2026 ***@***.***>***@***.***>,
***@***.***>SubjectRe: [claude-code-best/claude-code] feat: enable Claude in Chrome MCP with full browser control (PR #93)
claude-code-best left a comment (claude-code-best/claude-code#93)
@amDosion 你是有 claude 订阅吗, 我这边显示要有订阅才行
—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you were mentioned.Message ID: ***@***.***>
|
|
我的截图左上角有个标识的,需要在浏览器安装claude 的官方插件:https://chromewebstore.google.com/detail/claude/fcoeoabgfenejglbffodgkkbkcdhcgfn?pli=1 |
|
还有一个就是接入chrome-dev-mcp,安装这个mcp也能操作浏览器 |

Summary
@ant/claude-for-chrome-mcp的 6 行 stub 为完整实现(8 文件,3038 行)DEV-LOG.md追加章节docs/features/claude-in-chrome-mcp.md完整计划与测试文档Why
/chrome命令已可见但功能为空壳——createClaudeForChromeMcpServer()返回null,BROWSER_TOOLS为空数组。src/下所有 claudeInChrome 源码已与官方一致(0 行差异),只需替换 stub 包。不需要 feature flag,不改 dev.ts/build.ts,不改 src/ 下任何文件。
Test plan
bun run build成功,产物包含javascript_tool、ListToolsRequestSchemabun run dev -- --chrome→/chrome显示设置菜单--chrome启动正常,无 chrome 报错/voice、/schedule不受影响🤖 Generated with Claude Code
Summary by CodeRabbit
Release Notes