Chat about this codebase

AI-powered code exploration

Online

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 (default http://127.0.0.1:12306/mcp)
    • WXT support for multi-browser packaging

  • Native Messaging Host
    • Node.js CLI for registering/unregistering the host
    • JSON-RPC server handling MCP protocol
    • Cross-platform builds, tests, and linting

  • Shared TypeScript Package
    • Zod-based tool schemas and type definitions
    • Common utilities and protocol layers
    • Builds both ESM and CommonJS artifacts

  • WASM-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

  1. Open chrome://extensions.
  2. Enable “Developer mode”.
  3. Click “Get more extensions” → search “Chrome MCP Server” → Install.

2.2.2 Load Unpacked (Dev)

  1. Clone https://github.com/hangwin/mcp-chrome.git
  2. cd mcp-chrome/extension/dist
  3. 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

  1. Open the extension popup → click “Connect Native”.
  2. 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 use sudo.
  • 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:

  1. Cache cleanup
  2. Semantic engine warm-start from indexedDB
  3. Native messaging host connection
  4. 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.
Schema
toolSchemas.chrome_tabs_query in packages/shared/src/tools.ts.

Parameters

  • windowId (number, optional)
  • active (boolean, optional)
  • url (string|string[], optional)

Response
content 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.
Schema
toolSchemas.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).
Schema
toolSchemas.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.
Schema
toolSchemas.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.
Schema
toolSchemas.chrome_navigate.

Parameters

  • tabId (number, required)
  • url (string, required)
  • timeoutMs (number, optional, default: 30000)

Response
content[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.
Schema
toolSchemas.chrome_capture_screenshot.

Parameters

  • tabId (number, required)
  • format ('png'|'jpeg', optional, default: 'png')
  • quality (number, optional, 0–100 for JPEG)

Response
content[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.
Schema
toolSchemas.chrome_capture_visible.

Parameters same as chrome_capture_screenshot.


4.4 Network Monitoring

chrome_network_request

Purpose
Perform arbitrary HTTP requests from background.
Schema
toolSchemas.chrome_network_request.

Parameters

  • url (string, required)
  • method (string, optional, default: 'GET')
  • headers (object, optional)
  • body (string, optional)

Response
content[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.
Schema
toolSchemas.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.
Schema
toolSchemas.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.
Schema
toolSchemas.chrome_extract_text.

Parameters

  • tabId (number, required)

Response
content[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.
Schema
toolSchemas.chrome_extract_links.

Parameters same as above.

chrome_get_dom

Purpose
Return the full serialized DOM.
Schema
toolSchemas.chrome_get_dom.

chrome_count_elements

Purpose
Count elements matching a CSS selector.
Schema
toolSchemas.chrome_count_elements.

Parameters

  • tabId (number, required)
  • selector (string, required)

Response
content[0].text is the count.


4.6 Interaction

chrome_click

Purpose
Simulate a click on a page element.
Schema
toolSchemas.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.
Schema
toolSchemas.chrome_type.

Parameters

  • tabId (number)
  • selector (string)
  • text (string)
  • delayMs (number, optional)

chrome_scroll

Purpose
Scroll page by offset or to selector.
Schema
toolSchemas.chrome_scroll.

chrome_hover

Purpose
Move mouse over an element.
Schema
toolSchemas.chrome_hover.


4.7 Bookmark Management

chrome_bookmarks_get

Purpose
Retrieve bookmark node(s).
Schema
toolSchemas.chrome_bookmarks_get.

Parameters

  • id (string, optional)
  • url (string, optional)

Response
Array of BookmarkTreeNode.

chrome_bookmarks_create

Purpose
Create a new bookmark or folder.
Schema
toolSchemas.chrome_bookmarks_create.

Parameters

  • parentId (string, optional)
  • title (string, required)
  • url (string, optional)

chrome_bookmarks_remove

Purpose
Delete a bookmark.
Schema
toolSchemas.chrome_bookmarks_remove.

Parameters

  • id (string, required)

4.8 Script Injection

chrome_inject_script

Purpose
Inject JS or CSS into the page.
Schema
toolSchemas.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.
Schema
toolSchemas.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 into dist.
  • 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 surface NS_ERRORS.REQUEST_TIMEOUT.
  • Return standardized HTTP codes via HTTP_STATUS and NS_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

  1. Build production bundle
    npm run build   # runs wxt build --mode production
    
  2. Load unpacked in Chrome
    • Open chrome://extensions, enable Developer Mode.
    • Click “Load unpacked” → select app/chrome-extension/dist.
  3. Publish to Chrome Web Store
    • Zip the dist folder.
    • Upload in the Developer Dashboard, bump manifest.version.
  4. 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/

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

  1. Clone and install dependencies
    git clone https://github.com/hangwin/mcp-chrome.git
    cd mcp-chrome
    pnpm install
    
  2. Enable Husky hooks on install
    Ensure your root package.json has:
    {
      "scripts": {
        "prepare": "husky install"
      }
    }
    
  3. Workspace structure
    • app/ – Chrome extension source
    • packages/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 under app/ and packages/
  • 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

  1. Create MyToolExecutor.ts in
    app/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() };
      }
    }
    
  2. Export from app/chrome-extension/entrypoints/background/tools/browser/index.ts:
    export * from "./MyToolExecutor";
    
  3. Update TOOL_NAMES.BROWSER to include "my_tool".
  4. 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)

  1. Navigate to packages/wasm-simd
  2. Run unit tests:
    cd packages/wasm-simd
    cargo test
    
  3. 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

  1. Create a feature branch:
    git checkout -b feature/awesome-tool
  2. Write code, run tests & lint:
    pnpm run lint && pnpm test
    
  3. Commit using Conventional Commits:
    git commit -m "feat(browser): add my_tool executor"
  4. Push and open PR against main:
    • Describe purpose, testing steps, and link related issues.
    • Ensure CI checks (lint, tests, WASM build) pass.
  5. 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:

  1. 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.
  2. Check execution permissions

    chmod +x app/native-server/debug.sh
    chmod +x app/native-server/index.js
    
  3. 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 which node binary and script path Chrome will use.
    stderr log captures runtime errors from index.js.

  4. Inspect error codes

    • Exit code 1 + “Node.js executable not found” → ensure Node is installed and in COMMON_NODE_PATHS (see section below).
    • Permission denied → fix with chmod.
  5. Verify Chrome-side messaging

    • Use chrome.runtime.sendNativeMessage in DevTools console to send a test payload and observe response.

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:

  1. Inspect COMMON_NODE_PATHS in debug.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
    )
    
  2. 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
    
  3. 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:

  1. Uncomment implementations in logger.ts and set LOG_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
    }
    
  2. 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)
    }
    
  3. 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 absolute debug.sh or index.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