Claude in Chrome:通过原生消息桥接管浏览器侧 MCP 工具
先搞清楚这是什么
Claude in Chrome 在 Claude Code 中并不是指 CLI 包含了一个内置浏览器,也不是简单地调用 Chrome 的远程控制接口(如 CDP)。它的本质是一种跨进程的动态工具注入:将用户浏览器中的 Claude 扩展包装成一个临时的、动态的 MCP Server,并将其接入当前的会话循环。
这种集成意味着:
- 浏览器扩展是真正持有标签页、Console 日志和页面上下文的端点。
- CLI 通过一个“原生消息宿主”(Native Messaging Host)作为翻译层,与浏览器扩展通信。
- 模型获得了一组专门的“浏览器技能”,能够通过指令操作 Tab、读取页面内容、甚至录制 GIF 轨迹。
压成一句话:Claude in Chrome 不是浏览器版的 Claude Code,而是 Chrome 扩展通过本机桥接协议向 CLI 会话输出的一组动态 MCP 工具集。
实现细节
实现这一集成的关键在于三个层次的深度打通:
1. 宿主接头人:Native Messaging Host
由于浏览器安全限制,Chrome 扩展无法直接与本地任意进程通信。在 claude-code-opensource/src/utils/claudeInChrome/setup.ts 中,Claude Code 会安装一个“包装脚本”和“宿主清单”。 当 Chrome 扩展尝试连接时,它会拉起 claude-code-opensource/src/utils/claudeInChrome/chromeNativeHost.ts。这个进程作为中转站,将 Chrome 的标准输入输出(stdin/stdout)转换为本地安全的 Socket 信号,从而让 Claude Code 主进程能够像访问普通 MCP Server 一样访问浏览器扩展。
2. 工具注入:动态 MCP 与专用 Prompt
仅仅建立连接是不够的,模型必须“知道”如何使用浏览器。在 claude-code-opensource/src/main.tsx 的会话启动路径中,系统会进行一系列注入:
- 动态配置:生成
claude-in-chromeMCP Server 配置。 - 系统指令:注入
claude-code-opensource/src/utils/claudeInChrome/prompt.ts中定义的专用 System Prompt,明确告诉模型“不要复用旧 Tab ID”、“优先调用tabs_context_mcp”等行为约束。 - Skill 封装:通过
claude-code-opensource/src/skills/bundled/claudeInChrome.ts将零散的 MCP 工具打包为一套完整的浏览器自动化技能。
3. 结果渲染:专门的 UI 组件
浏览器返回的数据(如截图、控制台日志)通常比普通文本更复杂。claude-code-opensource/src/utils/claudeInChrome/toolRendering.tsx 负责将这些结果渲染为特定的 UI 元素(如 Tab 卡片、可滚动的日志块、甚至 GIF 回放容器),以提升用户在终端中的交互感知。
4. 权限与认证:OAuth 与 Bridge
claude-code-opensource/src/utils/claudeInChrome/mcpServer.ts 在创建 Server 时,会同时注入 OAuth Token、已配对的设备 ID 以及可选的远端 Bridge(bridge.claudeusercontent.com)。这套机制既支持纯本地的 Socket 通道,也支持在特定网络环境下通过 WebSocket 桥进行跨设备连接。
踩坑指南
- 启用优先级:是否启用 Chrome 集成由
shouldEnableClaudeInChrome()裁决,顺序为:环境变量 > CLI 参数(--chrome) > 用户全局配置。非交互式会话默认禁用该功能。 - 权限真边界在扩展侧:Claude Code 无法绕过浏览器的站点授权。如果某个域名在扩展里没给权限,即便 CLI 侧模型想要访问,请求也会在扩展端被拦截。
- 工具搜索依赖:如果你开启了
ToolSearch功能,模型必须先显式调用搜索工具找到mcp__claude-in-chrome__*系列工具,然后才能正常使用它们。 - 宿主清单安装:功能运行的前提是
~/.claude/chrome/下的包装脚本和配置清单必须存在。如果你手动删除了这些文件,即便开启参数,集成也会静默失效。 - 限制场景:源码中明确
/chrome命令仅在claude-ai登录且交互模式下可用。API Key 用户目前无法直接使用这套由 OAuth 驱动的扩展集成。
相关主题
- 跨设备桥接机制:查看
claude-code-opensource/src/bridge/相关代码,研究bridge.claudeusercontent.com是如何处理公网 WebSocket 穿透的。 - 浏览器交互协议:阅读
claude-code-opensource/src/utils/claudeInChrome/prompt.ts,了解模型在使用浏览器时遵循的完整指令集。 - UI 渲染细节:探索
claude-code-opensource/src/utils/claudeInChrome/toolRendering.tsx是如何利用 ANSI 和墨水屏(Ink)技术渲染复杂媒体信息的。
源码锚点
claude-code-opensource/src/utils/claudeInChrome/setup.ts: 启用逻辑、原生消息宿主安装与包装脚本生成。
📄 src/utils/claudeInChrome/setup.ts — 启用逻辑、原生消息宿主安装与包装脚本生成。
const CHROME_EXTENSION_RECONNECT_URL = 'https://clau.de/chrome/reconnect'
const NATIVE_HOST_IDENTIFIER = 'com.anthropic.claude_code_browser_extension'
const NATIVE_HOST_MANIFEST_NAME = `${NATIVE_HOST_IDENTIFIER}.json`claude-code-opensource/src/utils/claudeInChrome/chromeNativeHost.ts: 浏览器标准 I/O 到本地 Socket 的翻译层实现。
📄 src/utils/claudeInChrome/chromeNativeHost.ts — 浏览器标准 I/O 到本地 Socket 的翻译层实现。
socket: Socket
buffer: Buffer
}
class ChromeNativeHost {claude-code-opensource/src/utils/claudeInChrome/mcpServer.ts: 动态 MCP Server 的创建、OAuth 注入与设备配对管理。
📄 src/utils/claudeInChrome/mcpServer.ts — 动态 MCP Server 的创建、OAuth 注入与设备配对管理。
* Build the ClaudeForChromeContext used by both the subprocess MCP server
* and the in-process path in the MCP client.
*/
export function createChromeContext(
env?: Record<string, string>,
): ClaudeForChromeContext {
const logger = new DebugLogger()
const chromeBridgeUrl = getChromeBridgeUrl()
logger.info(`Bridge URL: ${chromeBridgeUrl ?? 'none (using native socket)'}`)
const rawPermissionMode =
env?.CLAUDE_CHROME_PERMISSION_MODE ??
process.env.CLAUDE_CHROME_PERMISSION_MODE
let initialPermissionMode: PermissionMode | undefined
if (rawPermissionMode) {
if (isPermissionMode(rawPermissionMode)) {
initialPermissionMode = rawPermissionMode
} else {
logger.warn(
`Invalid CLAUDE_CHROME_PERMISSION_MODE "${rawPermissionMode}". Valid values: ${PERMISSION_MODES.join(', ')}`,
)
}
}
return {
serverName: 'Claude in Chrome',
logger,
socketPath: getSecureSocketPath(),
getSocketPaths: getAllSocketPaths,
clientTypeId: 'claude-code',
onAuthenticationError: () => {
logger.warn(claude-code-opensource/src/utils/claudeInChrome/prompt.ts: 浏览器自动化专用指令集与 Tab 使用协议。
📄 src/utils/claudeInChrome/prompt.ts — 浏览器自动化专用指令集与 Tab 使用协议。
## Tab context and session startup
IMPORTANT: At the start of each browser automation session, call mcp__claude-in-chrome__tabs_context_mcp first to get information about the user's current browser tabs. Use this context to understand what the user might want to work with before creating new tabs.
Never reuse tab IDs from a previous/other session. Follow these guidelines:
1. Only reuse an existing tab if the user explicitly asks to work with it
2. Otherwise, create a new tab with mcp__claude-in-chrome__tabs_create_mcp
3. If a tool returns an error indicating the tab doesn't exist or is invalid, call tabs_context_mcp to get fresh tab IDs
4. When a tab is closed by the user or a navigation error occurs, call tabs_context_mcp to see what tabs are available`
/**
* Additional instructions for chrome tools when tool search is enabled.
* These instruct the model to load chrome tools via ToolSearch before using them.
* Only injected when tool search is actually enabled (not just optimistically possible).
*/
export const CHROME_TOOL_SEARCH_INSTRUCTIONS = `**IMPORTANT: Before using any chrome browser tools, you MUST first load them using ToolSearch.**
Chrome browser tools are MCP tools that require loading before use. Before calling any mcp__claude-in-chrome__* tool:
1. Use ToolSearch with \`select:mcp__claude-in-chrome__<tool_name>\` to load the specific tool
2. Then call the tool
For example, to get tab context:
1. First: ToolSearch with query "select:mcp__claude-in-chrome__tabs_context_mcp"
2. Then: Call mcp__claude-in-chrome__tabs_context_mcp`
/**
* Get the base chrome system prompt (without tool search instructions).
* Tool search instructions are injected separately at request time in claude.ts
* based on the actual tool search enabled state.
*/claude-code-opensource/src/main.tsx: 集成在会话启动时的全量注入点。
📄 src/main.tsx — 集成在会话启动时的全量注入点。
const getTeammateUtils = () => require('./utils/teammate.js') as typeof import('./utils/teammate.js');
const getTeammatePromptAddendum = () => require('./utils/swarm/teammatePromptAddendum.js') as typeof import('./utils/swarm/teammatePromptAddendum.js');
const getTeammateModeSnapshot = () => require('./utils/swarm/backends/teammateModeSnapshot.js') as typeof import('./utils/swarm/backends/teammateModeSnapshot.js');
/* eslint-enable @typescript-eslint/no-require-imports */
// Dead code elimination: conditional import for COORDINATOR_MODE
/* eslint-disable @typescript-eslint/no-require-imports */
const coordinatorModeModule = feature('COORDINATOR_MODE') ? require('./coordinator/coordinatorMode.js') as typeof import('./coordinator/coordinatorMode.js') : null;
/* eslint-enable @typescript-eslint/no-require-imports */
// Dead code elimination: conditional import for KAIROS (assistant mode)
/* eslint-disable @typescript-eslint/no-require-imports */
const assistantModule = feature('KAIROS') ? require('./assistant/index.js') as typeof import('./assistant/index.js') : null;
const kairosGate = feature('KAIROS') ? require('./assistant/gate.js') as typeof import('./assistant/gate.js') : null;
import { relative, resolve } from 'path';claude-code-opensource/src/commands/chrome/chrome.tsx:/chrome状态面板与权限重连控制台。
📄 src/commands/chrome/chrome.tsx — `/chrome` 状态面板与权限重连控制台。
const CHROME_EXTENSION_URL = 'https://claude.ai/chrome';
const CHROME_PERMISSIONS_URL = 'https://clau.de/chrome/permissions';
const CHROME_RECONNECT_URL = 'https://clau.de/chrome/reconnect';
type MenuAction = 'install-extension' | 'reconnect' | 'manage-permissions' | 'toggle-default';
type Props = {
onDone: (result?: string) => void;
isExtensionInstalled: boolean;
configEnabled: boolean | undefined;
isClaudeAISubscriber: boolean;
isWSL: boolean;
};