1. Project Overview
This project turns your Chrome browser into a local AI-enabled automation server. An AI assistant connects over JSON-RPC to perform browser actions, extract content, monitor network traffic, and run high-performance vector computations—all without leaving Chrome.
Value Proposition
- Embed AI assistants directly in Chrome for real-time browsing automation.
- Leverage native messaging for secure, low-latency communication.
- Run SIMD-accelerated vector ops in WebAssembly for semantic search and analysis.
- Share schemas, types and utilities across extension, native host, and client code.
Supported Use Cases
- Browser Automation: Navigate, click, fill forms, capture screenshots.
- Content Extraction: Scrape text, links, metadata.
- Network Monitoring: Record HAR logs, inspect requests.
- Data Analysis: Perform cosine similarity, matrix math, vector search via WASM.
- AI-Driven Tasks: Summarization, question answering, translation inside the browser.
High-Level Features
Chrome Extension
• Exposes background script & content scripts for tool execution
• Vue-based UI panel for settings and logs
• Streamable HTTP bridge (defaulthttp://127.0.0.1:12306/mcp
)
• WXT support for multi-browser packagingNative Messaging Host
• Node.js CLI for registering/unregistering the host
• JSON-RPC server handling MCP protocol
• Cross-platform builds, tests, and lintingShared TypeScript Package
• Zod-based tool schemas and type definitions
• Common utilities and protocol layers
• Builds both ESM and CommonJS artifactsWASM-SIMD Package
• Rust + wasm-bindgen vector operations (cosine similarity, matrix ops)
• High throughput for browser and Node contexts
Repo Structure
.
├── app/
│ ├── chrome-extension/ # Vue UI, background & content scripts, build/pack scripts (package.json)
│ └── native-server/ # Node.js native-messaging host, CLI tools, server implementation
└── packages/
├── shared/ # Core TS library: schemas, types, utilities
└── wasm-simd/ # Rust + WebAssembly SIMD module for vector math
├── docs/
│ └── ARCHITECTURE.md # System overview: protocol layers, tool execution, WASM DB
└── README.md # Quick start, integration patterns, value proposition
Quick Start
# Clone and install dependencies (Yarn workspaces)
git clone https://github.com/hangwin/mcp-chrome.git
cd mcp-chrome
yarn install
# Build all packages
yarn build
# Load the Chrome extension
# 1. Open chrome://extensions → Toggle Developer mode
# 2. Click "Load unpacked" → select app/chrome-extension/dist
# Register native messaging host
cd app/native-server
npm run register
# Start the native server
npm start
# Connect your AI client to http://127.0.0.1:12306/mcp
This setup powers an AI assistant with seamless browser automation, content analysis, and data-driven interactions inside Chrome.
2. Getting Started
This guide takes you through installing the Chrome MCP Server extension, registering the native‐messaging host, and verifying a live connection on Windows, macOS, and Linux.
2.1 Prerequisites
- Chrome or Chromium (v87+).
- Node.js (v12+).
- npm or yarn for installing the bridge CLI.
2.2 Install the Chrome Extension
2.2.1 From the Chrome Web Store
- Open chrome://extensions.
- Enable “Developer mode”.
- Click “Get more extensions” → search “Chrome MCP Server” → Install.
2.2.2 Load Unpacked (Dev)
- Clone https://github.com/hangwin/mcp-chrome.git
- cd mcp-chrome/extension/dist
- chrome://extensions → “Load unpacked” → select
dist/
2.3 Install & Register the Native Host
The chrome-mcp-bridge
CLI writes a native‐messaging manifest pointing to your Node.js host.
2.3.1 Install the CLI
npm install -g chrome-mcp-bridge
# or
yarn global add chrome-mcp-bridge
2.3.2 User-Level Registration
No admin rights required.
chrome-mcp-bridge register
Expected output:
✓ Native Messaging host registered successfully!
2.3.3 System-Level Registration (Optional)
Use when multiple users share the host or your user‐level path isn’t writable:
chrome-mcp-bridge register --system
- macOS/Linux: automatic sudo prompt.
- Windows: run in an elevated PowerShell.
To overwrite an existing manifest, append --force
.
2.4 Verify the Connection
- Open the extension popup → click “Connect Native”.
- Look for “Status: connected”.
Or use the DevTools console in background/page script:
// Connect to the native host
const port = chrome.runtime.connectNative('com.chrome-mcp.native-host');
// Send a ping
port.postMessage({ type: 'ping' });
// Listen for a response
port.onMessage(msg => {
console.log('Native host replied:', msg);
});
Expected console log:
Native host replied: { type: 'pong', version: '1.0.0' }
2.5 Platform-Specific Notes
Windows
- Ensure PowerShell or CMD runs as Administrator for system-level registration.
- If “host not found”, check registry under
HKCU:\Software\Google\Chrome\NativeMessagingHosts
macOS
- Manifests live in
/Library/Google/Chrome/NativeMessagingHosts
(system) or~/Library/Application Support/Google/Chrome/NativeMessagingHosts
(user). - Grant execute permission:
chmod +x /usr/local/bin/chrome-mcp-bridge
Linux
- System manifest directory:
/etc/opt/chrome/native-messaging-hosts
. - User manifest directory:
~/.config/google-chrome/NativeMessagingHosts
.
2.6 Troubleshooting
- “Permission denied” → rerun with
--system
or usesudo
. - No response in DevTools → confirm your host process is running and listening.
- Inspect manifest paths:
# macOS/Linux ls ~/.config/google-chrome/NativeMessagingHosts # Windows (PowerShell) Get-Item HKCU:\Software\Google\Chrome\NativeMessagingHosts\com.chrome-mcp.native-host
- For verbose CLI logging:
chrome-mcp-bridge register --verbose
You now have a working Chrome extension ↔ native host connection. Proceed to configure and use MCP endpoints in the popup UI.
3. Core Concepts & Architecture
This section explains how the Chrome extension, offscreen document, native host server, semantic engine, and WebAssembly SIMD module interoperate to deliver high-performance browser automation and semantic search.
3.1 Extension Background Entrypoint
The background script (app/chrome-extension/entrypoints/background/index.ts
) initializes services in this order:
- Cache cleanup
- Semantic engine warm-start from indexedDB
- Native messaging host connection
- Chrome listeners for messages, storage changes, and connections
Initialization Flow
import { initializeEngine, cleanupCache } from '@/utils/semantic-similarity-engine';
import { connectNativeHost } from '@/utils/native-host-connector';
import { onIncomingRequest } from '@/utils/request-router';
(async function initBackground() {
// 1. Remove stale embeddings
await cleanupCache();
// 2. Load model and index vectors from cache
await initializeEngine();
// 3. Establish native host channel
const host = await connectNativeHost();
// 4. Route incoming extension messages
chrome.runtime.onMessage.addListener(onIncomingRequest(host));
})();
3.2 Offscreen Document Management
The offscreen document (app/chrome-extension/utils/offscreen-manager.ts
) isolates heavy tasks (e.g., batching similarity computations). It ensures exactly one offscreen HTML page runs at a time.
API
ensureOffscreenDocument(): Promise<void>
closeOffscreenDocument(): Promise<void>
isOffscreenDocumentCreated(): boolean
Usage Example
import {
ensureOffscreenDocument,
closeOffscreenDocument,
isOffscreenDocumentCreated
} from '@/utils/offscreen-manager';
async function computeBatchSimilarity(vectors: number[][]) {
if (!isOffscreenDocumentCreated()) {
await ensureOffscreenDocument();
}
// Delegate to offscreen page
const response = await chrome.runtime.sendMessage({
type: 'BATCH_SIMILARITY',
payload: vectors
});
return response.similarities as number[][];
}
// Clean up when idle
chrome.idle.onStateChanged.addListener(state => {
if (state === 'idle') {
closeOffscreenDocument();
}
});
3.3 Native Host Server
The native server (app/native-server/src/index.ts
) sets up Chrome’s native messaging protocol, binds host and server, and handles OS signals for graceful shutdown.
Lifecycle Management
import NativeMessagingServer from './native-messaging-server';
import NativeHost from './native-host';
const server = new NativeMessagingServer();
const host = new NativeHost(server);
// Inject mutual references
server.setHost(host);
host.setServer(server);
// Start listening to Chrome
host.start();
process.on('SIGINT', async () => {
await host.shutdown();
process.exit(0);
});
process.on('uncaughtException', err => {
console.error('Fatal error:', err);
process.exit(1);
});
3.4 Semantic Engine & WASM SIMD Acceleration
The semantic engine uses a WebAssembly module compiled from Rust (packages/wasm-simd/src/lib.rs
) for vector operations (cosine similarity, batch comparisons, similarity matrices).
Engine Initialization
import { loadWasmModule } from '@/utils/wasm-loader';
import { indexCachedVectors } from '@/utils/vector-storage';
export async function initializeEngine() {
// Load the SIMD-optimized wasm
await loadWasmModule('/wasm/wasm_simd_bg.wasm');
// Preload embeddings from IndexedDB
const cached = await indexCachedVectors();
console.log(`Loaded ${cached.count} vectors`);
}
Example: Cosine and Batch Similarity
import {
cosine_similarity,
batch_similarity,
similarity_matrix
} from 'wasm-simd';
// Single pair similarity
const score = cosine_similarity([0.1, 0.2, 0.3], [0.2, 0.1, 0.4]);
console.log(`Score: ${score}`);
// Batch mode for multiple queries
const queries = [[...], [...]];
const documents = [[...], [...], [...]];
const scores = batch_similarity(queries.flat(), documents.flat(), 2, 3);
// Arguments: flatQueryArray, flatDocArray, queryCount, docCount
console.table(scores);
// Full similarity matrix
const matrix = similarity_matrix(documents.flat(), 3);
// Computes 3×3 matrix in one call
Together, these components form a modular, high-throughput system: the background script orchestrates requests, offscreen pages offload heavy work, the native host executes platform-level tasks, and the semantic engine leverages SIMD-accelerated WebAssembly for rapid vector comparisons.
4. Browser Automation Tools Reference
Practical, task-oriented reference for all available browser automation tools. Grouped by capability, each entry links to its schema in packages/shared/src/tools.ts
and shows a realistic invocation via the background callTool
utility.
4.1 Tab Management
chrome_tabs_query
Purpose
List tabs matching query filters.
SchematoolSchemas.chrome_tabs_query
in packages/shared/src/tools.ts
.
Parameters
windowId
(number, optional)active
(boolean, optional)url
(string|string[], optional)
Responsecontent
array of Chrome Tab
objects.
Example
import { callTool } from '@/chrome-extension/entrypoints/background/tools';
const result = await callTool('chrome_tabs_query', { active: true });
const activeTabs = result.content[0]; // Array of active Tab objects
console.log(activeTabs.map(t => t.url));
chrome_tabs_create
Purpose
Open a new tab with specified URL and properties.
SchematoolSchemas.chrome_tabs_create
.
Parameters
url
(string, required)active
(boolean, optional, default: true)index
(number, optional)
Response
Single Tab
object in content[0]
.
Example
const { content } = await callTool('chrome_tabs_create', {
url: 'https://example.com',
active: false
});
const newTab = content[0];
console.log('New tab ID:', newTab.id);
chrome_tabs_update
Purpose
Modify properties of an existing tab (URL, activation).
SchematoolSchemas.chrome_tabs_update
.
Parameters
tabId
(number, required)url
(string, optional)active
(boolean, optional)
Response
Updated Tab
object.
Example
await callTool('chrome_tabs_update', {
tabId: 42,
active: true
});
chrome_tabs_remove
Purpose
Close one or more tabs.
SchematoolSchemas.chrome_tabs_remove
.
Parameters
tabIds
(number|number[], required)
Response
Empty content
on success.
Example
await callTool('chrome_tabs_remove', { tabIds: [42, 43] });
4.2 Navigation
chrome_navigate
Purpose
Navigate a tab to a new URL and await completion.
SchematoolSchemas.chrome_navigate
.
Parameters
tabId
(number, required)url
(string, required)timeoutMs
(number, optional, default: 30000)
Responsecontent[0]
holds final Tab
status.
Example
const { content } = await callTool('chrome_navigate', {
tabId: 5,
url: 'https://news.ycombinator.com'
});
console.log('Loaded:', content[0].status);
4.3 Screenshots
chrome_capture_screenshot
Purpose
Capture the entire page as a PNG data URL.
SchematoolSchemas.chrome_capture_screenshot
.
Parameters
tabId
(number, required)format
('png'|'jpeg', optional, default: 'png')quality
(number, optional, 0–100 for JPEG)
Responsecontent[0].text
contains the data:image/...;base64
string.
Example
const { content } = await callTool('chrome_capture_screenshot', {
tabId: 7,
format: 'jpeg',
quality: 80
});
const dataUrl = content[0].text;
console.log(dataUrl.slice(0,50)); // "data:image/jpeg;base64,/9j/4AAQ..."
chrome_capture_visible
Purpose
Capture only the currently visible viewport.
SchematoolSchemas.chrome_capture_visible
.
Parameters same as chrome_capture_screenshot
.
4.4 Network Monitoring
chrome_network_request
Purpose
Perform arbitrary HTTP requests from background.
SchematoolSchemas.chrome_network_request
.
Parameters
url
(string, required)method
(string, optional, default: 'GET')headers
(object, optional)body
(string, optional)
Responsecontent[0].text
is JSON with { status, headers, body }
.
Example
const raw = await callTool('chrome_network_request', {
url: 'https://api.example.com/users',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: 'Bob' })
});
const resp = JSON.parse(raw.content[0].text);
console.log(resp.status, resp.body);
chrome_capture_network_requests_debugger
Purpose
Record network traffic via the Debugger API.
SchematoolSchemas.chrome_capture_network_requests_debugger
.
Parameters
tabId
(number, required)includeStatic
(boolean, optional, default: false)maxRequests
(number, optional)
Response
Array of request entries in content[0]
.
chrome_capture_network_requests_webrequest
Purpose
Record network traffic via WebRequest listeners.
SchematoolSchemas.chrome_capture_network_requests_webrequest
.
Parameters same as debugger version.
4.5 Content Analysis
chrome_extract_text
Purpose
Extract all visible text from a page.
SchematoolSchemas.chrome_extract_text
.
Parameters
tabId
(number, required)
Responsecontent[0].text
holds page text.
Example
const { content } = await callTool('chrome_extract_text', { tabId: 3 });
console.log('Page text:', content[0].text.slice(0,200));
chrome_extract_links
Purpose
List all anchor href
values.
SchematoolSchemas.chrome_extract_links
.
Parameters same as above.
chrome_get_dom
Purpose
Return the full serialized DOM.
SchematoolSchemas.chrome_get_dom
.
chrome_count_elements
Purpose
Count elements matching a CSS selector.
SchematoolSchemas.chrome_count_elements
.
Parameters
tabId
(number, required)selector
(string, required)
Responsecontent[0].text
is the count.
4.6 Interaction
chrome_click
Purpose
Simulate a click on a page element.
SchematoolSchemas.chrome_click
.
Parameters
tabId
(number, required)selector
(string, required)timeoutMs
(number, optional)
Example
await callTool('chrome_click', { tabId: 3, selector: '#submit' });
chrome_type
Purpose
Type text into an input or textarea.
SchematoolSchemas.chrome_type
.
Parameters
tabId
(number)selector
(string)text
(string)delayMs
(number, optional)
chrome_scroll
Purpose
Scroll page by offset or to selector.
SchematoolSchemas.chrome_scroll
.
chrome_hover
Purpose
Move mouse over an element.
SchematoolSchemas.chrome_hover
.
4.7 Bookmark Management
chrome_bookmarks_get
Purpose
Retrieve bookmark node(s).
SchematoolSchemas.chrome_bookmarks_get
.
Parameters
id
(string, optional)url
(string, optional)
Response
Array of BookmarkTreeNode
.
chrome_bookmarks_create
Purpose
Create a new bookmark or folder.
SchematoolSchemas.chrome_bookmarks_create
.
Parameters
parentId
(string, optional)title
(string, required)url
(string, optional)
chrome_bookmarks_remove
Purpose
Delete a bookmark.
SchematoolSchemas.chrome_bookmarks_remove
.
Parameters
id
(string, required)
4.8 Script Injection
chrome_inject_script
Purpose
Inject JS or CSS into the page.
SchematoolSchemas.chrome_inject_script
.
Parameters
tabId
(number, required)files
(string[], required)world
('MAIN'|'ISOLATED', optional)
Example
await callTool('chrome_inject_script', {
tabId: 3,
files: ['scripts/content-helper.js'],
world: 'ISOLATED'
});
4.9 Console Capture
chrome_capture_console_logs
Purpose
Collect console.log
/warn
/error
events from a tab.
SchematoolSchemas.chrome_capture_console_logs
.
Parameters
tabId
(number, required)levels
(string[], optional, default: ['log','error','warn'])
Response
Array of log entries { level, message, timestamp }
.
Example
const logs = await callTool('chrome_capture_console_logs', {
tabId: 10,
levels: ['error','warn']
});
console.table(logs.content[0]);
Each tool integrates with the background handleCallTool
in app/chrome-extension/entrypoints/background/tools/index.ts
, returning a { content, isError }
envelope. Inspect its schema in packages/shared/src/tools.ts
under toolSchemas['<tool_name>']
before extending or customizing behaviors.
5. Configuration & Deployment
This section covers all tweakable values in the Chrome extension and native host, plus build, permissions, environment variables, and deployment steps.
5.1 Extension Constants (app/chrome-extension/common/constants.ts)
Centralized values for timeouts, storage keys, UI assets, network filters, error messages and execution modes. Always import rather than hard-coding:
import {
NATIVE_HOST_NAME, // e.g. "com.mcp.native"
STORAGE_KEYS, // { SESSION: "mcp_session", SETTINGS: "mcp_settings", … }
TIMEOUTS, // { DEFAULT: 5000, LONG: 30000 }
NETWORK_FILTERS, // { urls: ["https://api.mcp.com/*"] }
ERROR_MESSAGES, // { SESSION_EXPIRED: "...", … }
ASSETS, // { ICON_16: "icons/16.png", … }
EXECUTION_MODES // { BATCH: "batch", REALTIME: "realtime" }
} from "./common/constants";
// Example: read/write storage
chrome.storage.local.get(STORAGE_KEYS.SESSION).then(data => {
if (!data[STORAGE_KEYS.SESSION]) {
throw new Error(ERROR_MESSAGES.SESSION_EXPIRED);
}
});
// Example: use network filter in a declarativeNetRequest rule
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [{
id: 1,
priority: 1,
action: { type: "block" },
condition: NETWORK_FILTERS
}]
});
5.2 Build & Manifest Settings (app/chrome-extension/wxt.config.ts)
Configure extension metadata, permissions, host permissions, environment variables, static assets, and runner options.
import { defineConfig } from "wxt";
import { version, name } from "../package.json";
export default defineConfig({
manifest: {
name: process.env.EXTENSION_NAME || name,
version,
description: "MCP Chrome Extension",
icons: {
"16": ASSETS.ICON_16,
"128": ASSETS.ICON_128
},
permissions: [
"storage",
"nativeMessaging",
"declarativeNetRequest",
"notifications"
],
host_permissions: [
"https://api.mcp.com/*",
"http://localhost/*"
],
background: { service_worker: "src/background/index.js" },
content_scripts: [
{
matches: ["<all_urls>"],
js: ["src/content/index.js"],
run_at: "document_idle"
}
],
web_accessible_resources: [
{ resources: ["ui/*.css","ui/*.png"], matches: ["<all_urls>"] }
]
},
define: {
"process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV || "development"),
"process.env.API_BASE": JSON.stringify(process.env.API_BASE || "https://api.mcp.com")
},
staticAssets: [
{ source: "public", dest: "./dist" }
],
runner: {
disabled: true, // disable auto-launch; set false + chromiumArgs for CI
chromiumArgs: [
"--remote-debugging-port=9222",
`--user-data-dir=${process.env.HOME}/.mcp-profile`
]
}
});
Key points:
- Use
process.env.*
to customize name, API endpoints, logging. permissions
vs.host_permissions
control APIs and remote URLs.staticAssets
copies icons, HTML, CSS intodist
.- Toggle
runner.disabled
for manual attach (recommended) or automated CI.
5.3 Native Host & Shared Constants
Import and align ports, hostnames, message types, timeouts and error texts across extension and native host.
// In extension or native-server code
import {
NATIVE_MESSAGE_TYPE,
NATIVE_SERVER_PORT,
TIMEOUTS,
SERVER_CONFIG,
HTTP_STATUS,
ERROR_MESSAGES as NS_ERRORS
} from "app/native-server/src/constant";
import {
DEFAULT_SERVER_PORT,
HOST_NAME
} from "packages/shared/src/constants";
// Launch HTTP server matching port/host
import http from "http";
const server = http.createServer(app);
server.listen(NATIVE_SERVER_PORT || DEFAULT_SERVER_PORT, SERVER_CONFIG.HOST, () => {
console.log(`Native host "${HOST_NAME}" listening on ${SERVER_CONFIG.HOST}:${server.address().port}`);
});
// Send/receive IPC
socket.send({ type: NATIVE_MESSAGE_TYPE.INIT });
socket.on("message", msg => {
if (msg.type === NATIVE_MESSAGE_TYPE.READY) {
// …
}
});
Best practices:
- Always reference
HOST_NAME
in your native messaging manifest. - Use
TIMEOUTS.DEFAULT
for network calls; catch and surfaceNS_ERRORS.REQUEST_TIMEOUT
. - Return standardized HTTP codes via
HTTP_STATUS
andNS_ERRORS
in your API handlers.
5.4 Environment Variables
Supported env vars:
- EXTENSION_NAME: overrides
manifest.name
- NODE_ENV: “development” | “production”
- API_BASE: base URL for remote API
- HOME: used in runner’s
user-data-dir
- CI: toggle automated runner in CI pipelines
Define in .env
or your CI config; WXT injects via define
.
5.5 Packaging & Deployment
- Build production bundle
npm run build # runs wxt build --mode production
- Load unpacked in Chrome
- Open
chrome://extensions
, enable Developer Mode. - Click “Load unpacked” → select
app/chrome-extension/dist
.
- Open
- Publish to Chrome Web Store
- Zip the
dist
folder. - Upload in the Developer Dashboard, bump manifest.version.
- Zip the
- Install native messaging host
- Create
<HOST_NAME>.json
in your OS’s native messaging hosts directory:{ "name": "<HOST_NAME>", "description": "MCP Chrome Native Host", "path": "/full/path/to/native-server/executable", "type": "stdio", "allowed_origins": [ "chrome-extension://<YOUR_EXTENSION_ID>/" ] }
- On macOS:
~/Library/Application Support/Google/Chrome/NativeMessagingHosts/
- On Windows:
HKCU\Software\Google\Chrome\NativeMessagingHosts\<HOST_NAME>.json
- On Linux:
~/.config/google-chrome/NativeMessagingHosts/
- Create
With these settings and constants in place, you can tailor the extension build, permissions, native host integration, and deployment to your environment.
6. Development & Contribution Guide
This guide explains how to set up the repository, follow coding standards, add new tools, write tests, build the WASM-SIMD package, and submit pull requests.
6.1 Monorepo Setup
- Clone and install dependencies
git clone https://github.com/hangwin/mcp-chrome.git cd mcp-chrome pnpm install
- Enable Husky hooks on install
Ensure your rootpackage.json
has:{ "scripts": { "prepare": "husky install" } }
- Workspace structure
app/
– Chrome extension sourcepackages/wasm-simd/
– Rust → WebAssembly module- Shared configs:
.eslintrc.js
,eslint.config.js
6.2 Git Hooks & Commit Standards
We use Husky, Commitlint and lint-staged to enforce Conventional Commits and code quality.
Install dev dependencies
pnpm add -D husky @commitlint/cli @commitlint/config-conventional lint-staged
Initialize Husky
npx husky install
Commit Message Hook (.husky/commit-msg
)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx --no -- commitlint --edit "$1"
Pre-commit Hook (.husky/pre-commit
)
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged
lint-staged config (in package.json
)
"lint-staged": {
"*.ts": ["eslint --fix", "prettier --write"],
"*.vue": ["eslint --fix", "prettier --write"],
"*.rs": ["rustfmt --emit=files"]
}
6.3 Linting & Formatting
Root ESLint (eslint.config.js
)
- Applies to
.ts
,.js
,.vue
underapp/
andpackages/
- Integrates TypeScript, Vue, Prettier
Run manually:
pnpm run lint
pnpm run format
Chrome-extension ESLint (app/chrome-extension/eslint.config.js
)
- Extends Vue recommended rules
- Ignores test fixtures and build outputs
6.4 Adding a New Background Tool
- Create
MyToolExecutor.ts
inapp/chrome-extension/entrypoints/background/tools/browser/
:import { ToolExecutor } from "../types"; import { z } from "zod"; export const MyToolName = "my_tool"; export class MyToolExecutor implements ToolExecutor { name = MyToolName; inputSchema = z.object({ foo: z.string() }); async execute(args: z.infer<typeof this.inputSchema>) { // business logic return { success: true, data: args.foo.toUpperCase() }; } }
- Export from
app/chrome-extension/entrypoints/background/tools/browser/index.ts
:export * from "./MyToolExecutor";
- Update
TOOL_NAMES.BROWSER
to include"my_tool"
. - Add a unit test (optional) under
app/chrome-extension/__tests__/
.
6.5 Writing Tests
TypeScript (Chrome extension)
- Jest is preconfigured
- Place tests in
__tests__
alongside code - Run:
pnpm test
Rust (WASM-SIMD)
- Navigate to
packages/wasm-simd
- Run unit tests:
cd packages/wasm-simd cargo test
- Ensure
rustfmt
passes on changed files:rustfmt --edition 2021 src/lib.rs
6.6 Building the WASM-SIMD Package
Follow packages/wasm-simd/BUILD.md
:
cd packages/wasm-simd
# 1. Install Rust target
rustup target add wasm32-unknown-unknown
# 2. Build with release flags
cargo build --target wasm32-unknown-unknown --release
# 3. Generate JS bindings
wasm-bindgen \
--out-dir pkg \
--target web \
target/wasm32-unknown-unknown/release/wasm_simd.wasm
# 4. Copy artifacts to extension
cp pkg/wasm_simd_bg.wasm ../../app/chrome-extension/workers/
cp pkg/wasm_simd.js ../../app/chrome-extension/workers/
Import and use in your extension:
const wasmUrl = chrome.runtime.getURL("workers/wasm_simd.js");
const simd = await import(wasmUrl) as typeof import("@chrome-mcp/wasm-simd");
const result = simd.cosine_similarity(vecA, vecB);
6.7 Pull Request Workflow
- Create a feature branch:
git checkout -b feature/awesome-tool
- Write code, run tests & lint:
pnpm run lint && pnpm test
- Commit using Conventional Commits:
git commit -m "feat(browser): add my_tool executor"
- Push and open PR against
main
:- Describe purpose, testing steps, and link related issues.
- Ensure CI checks (lint, tests, WASM build) pass.
- Address review comments and rebase/squash as needed.
By following these steps, you maintain consistent code quality and ensure smooth integration of your contributions.
7. Troubleshooting & FAQ
This section covers common errors, quick fixes and links to detailed platform-specific guides for the Native Messaging host and logging utility.
7.1 Native Host Connection Failures
Symptoms: Chrome extension cannot reach the native host, or messages time out.
Steps to resolve:
Verify installation & manifest placement
- On macOS/Linux, manifest must live in one of Chrome’s native messaging directories (see docs/TROUBLESHOOTING.md).
- Check Chrome’s Policies → NativeMessagingHosts for your
com.tencent.mcp_chrome_bridge.json
.
Check execution permissions
chmod +x app/native-server/debug.sh chmod +x app/native-server/index.js
Run the debug wrapper manually
cd app/native-server ./debug.sh tail -F dist/logs/native_host_wrapper_*.log tail -n50 dist/logs/native_host_stderr_*.log
•
wrapper
log shows whichnode
binary and script path Chrome will use.
•stderr
log captures runtime errors fromindex.js
.Inspect error codes
- Exit code
1
+ “Node.js executable not found” → ensure Node is installed and inCOMMON_NODE_PATHS
(see section below). - Permission denied → fix with
chmod
.
- Exit code
Verify Chrome-side messaging
- Use
chrome.runtime.sendNativeMessage
in DevTools console to send a test payload and observe response.
- Use
For full platform-specific guidance, see:
- docs/TROUBLESHOOTING.md
- docs/TROUBLESHOOTING_zh.md
7.2 Node.js Resolution Issues
If the wrapper can’t find your Node.js binary:
- Inspect
COMMON_NODE_PATHS
indebug.sh
:COMMON_NODE_PATHS=( "/usr/local/bin/node" "/opt/homebrew/bin/node" "$HOME/.nvm/versions/node/$(ls -t $HOME/.nvm/versions/node | head -1)/bin/node" "/your/custom/path/to/node" # <— Add your path here )
- Confirm which
node
is visible to Chrome’s environment:/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --headless \ --disable-gpu --dump-dom about:blank \ --enable-logging=stderr --v=1 2>&1 | grep node
- If you rely on NVM, source it at the top of
debug.sh
:export NVM_DIR="$HOME/.nvm" [ -s "$NVM_DIR/nvm.sh" ] && . "$NVM_DIR/nvm.sh"
7.3 Enabling & Using the Logger Utility
The file app/native-server/src/util/logger.ts
provides debug
, info
, warn
, error
methods (currently commented out). Enable it to record runtime events:
- Uncomment implementations in
logger.ts
and setLOG_FILE_PATH
:// src/util/logger.ts const LOG_FILE_PATH = path.resolve(__dirname, '../../dist/logs/native_host.log') export function info(msg: string) { const entry = `[INFO] ${new Date().toISOString()} ${msg}\n` fs.appendFileSync(LOG_FILE_PATH, entry) console.error(entry) // mirror to stderr }
- Import and invoke in your code:
import { debug, error } from './util/logger' debug('Native host starting up') try { // your initialization } catch (e) { error(`Startup failure: ${e.message}`) process.exit(1) }
- Tail the log alongside
debug.sh
logs:tail -F dist/logs/native_host.log
7.4 FAQ
Q: “No wrapper log appears after invoking debug.sh
.”
A: Ensure LOG_DIR
exists (default app/native-server/dist/logs
) and is writable:
mkdir -p app/native-server/dist/logs
chmod u+rwx app/native-server/dist/logs
Q: “Chrome reports ‘Failed to connect to native app’.”
A:
- Confirm manifest’s
"path"
field points to the absolutedebug.sh
orindex.js
wrapper. - Reload the extension and restart Chrome.
Q: “I see stale entries in logs or cache.”
A: Use the manual cleanup API in your extension’s background script:
import ModelCacheManager from '...';
const cacheMgr = ModelCacheManager.getInstance();
await cacheMgr.manualCleanup();
For extended troubleshooting, refer to the full guides:
- English: docs/TROUBLESHOOTING.md
- 简体中文: docs/TROUBLESHOOTING_zh.md