This is page 1 of 10. Use http://codebase.md/hangwin/mcp-chrome?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .gitattributes
├── .github
│ └── workflows
│ └── build-release.yml
├── .gitignore
├── .husky
│ ├── commit-msg
│ └── pre-commit
├── .prettierignore
├── .prettierrc.json
├── .vscode
│ └── extensions.json
├── app
│ ├── chrome-extension
│ │ ├── _locales
│ │ │ ├── de
│ │ │ │ └── messages.json
│ │ │ ├── en
│ │ │ │ └── messages.json
│ │ │ ├── ja
│ │ │ │ └── messages.json
│ │ │ ├── ko
│ │ │ │ └── messages.json
│ │ │ ├── zh_CN
│ │ │ │ └── messages.json
│ │ │ └── zh_TW
│ │ │ └── messages.json
│ │ ├── .env.example
│ │ ├── assets
│ │ │ └── vue.svg
│ │ ├── common
│ │ │ ├── constants.ts
│ │ │ ├── message-types.ts
│ │ │ └── tool-handler.ts
│ │ ├── entrypoints
│ │ │ ├── background
│ │ │ │ ├── index.ts
│ │ │ │ ├── native-host.ts
│ │ │ │ ├── semantic-similarity.ts
│ │ │ │ ├── storage-manager.ts
│ │ │ │ └── tools
│ │ │ │ ├── base-browser.ts
│ │ │ │ ├── browser
│ │ │ │ │ ├── bookmark.ts
│ │ │ │ │ ├── common.ts
│ │ │ │ │ ├── console.ts
│ │ │ │ │ ├── file-upload.ts
│ │ │ │ │ ├── history.ts
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── inject-script.ts
│ │ │ │ │ ├── interaction.ts
│ │ │ │ │ ├── keyboard.ts
│ │ │ │ │ ├── network-capture-debugger.ts
│ │ │ │ │ ├── network-capture-web-request.ts
│ │ │ │ │ ├── network-request.ts
│ │ │ │ │ ├── screenshot.ts
│ │ │ │ │ ├── vector-search.ts
│ │ │ │ │ ├── web-fetcher.ts
│ │ │ │ │ └── window.ts
│ │ │ │ └── index.ts
│ │ │ ├── content.ts
│ │ │ ├── offscreen
│ │ │ │ ├── index.html
│ │ │ │ └── main.ts
│ │ │ └── popup
│ │ │ ├── App.vue
│ │ │ ├── components
│ │ │ │ ├── ConfirmDialog.vue
│ │ │ │ ├── icons
│ │ │ │ │ ├── BoltIcon.vue
│ │ │ │ │ ├── CheckIcon.vue
│ │ │ │ │ ├── DatabaseIcon.vue
│ │ │ │ │ ├── DocumentIcon.vue
│ │ │ │ │ ├── index.ts
│ │ │ │ │ ├── TabIcon.vue
│ │ │ │ │ ├── TrashIcon.vue
│ │ │ │ │ └── VectorIcon.vue
│ │ │ │ ├── ModelCacheManagement.vue
│ │ │ │ └── ProgressIndicator.vue
│ │ │ ├── index.html
│ │ │ ├── main.ts
│ │ │ └── style.css
│ │ ├── eslint.config.js
│ │ ├── inject-scripts
│ │ │ ├── click-helper.js
│ │ │ ├── fill-helper.js
│ │ │ ├── inject-bridge.js
│ │ │ ├── interactive-elements-helper.js
│ │ │ ├── keyboard-helper.js
│ │ │ ├── network-helper.js
│ │ │ ├── screenshot-helper.js
│ │ │ └── web-fetcher-helper.js
│ │ ├── LICENSE
│ │ ├── package.json
│ │ ├── public
│ │ │ ├── icon
│ │ │ │ ├── 128.png
│ │ │ │ ├── 16.png
│ │ │ │ ├── 32.png
│ │ │ │ ├── 48.png
│ │ │ │ └── 96.png
│ │ │ ├── libs
│ │ │ │ └── ort.min.js
│ │ │ └── wxt.svg
│ │ ├── README.md
│ │ ├── tsconfig.json
│ │ ├── utils
│ │ │ ├── content-indexer.ts
│ │ │ ├── i18n.ts
│ │ │ ├── image-utils.ts
│ │ │ ├── lru-cache.ts
│ │ │ ├── model-cache-manager.ts
│ │ │ ├── offscreen-manager.ts
│ │ │ ├── semantic-similarity-engine.ts
│ │ │ ├── simd-math-engine.ts
│ │ │ ├── text-chunker.ts
│ │ │ └── vector-database.ts
│ │ ├── workers
│ │ │ ├── ort-wasm-simd-threaded.jsep.mjs
│ │ │ ├── ort-wasm-simd-threaded.jsep.wasm
│ │ │ ├── ort-wasm-simd-threaded.mjs
│ │ │ ├── ort-wasm-simd-threaded.wasm
│ │ │ ├── simd_math_bg.wasm
│ │ │ ├── simd_math.js
│ │ │ └── similarity.worker.js
│ │ └── wxt.config.ts
│ └── native-server
│ ├── debug.sh
│ ├── install.md
│ ├── jest.config.js
│ ├── package.json
│ ├── README.md
│ ├── src
│ │ ├── cli.ts
│ │ ├── constant
│ │ │ └── index.ts
│ │ ├── file-handler.ts
│ │ ├── index.ts
│ │ ├── mcp
│ │ │ ├── mcp-server-stdio.ts
│ │ │ ├── mcp-server.ts
│ │ │ ├── register-tools.ts
│ │ │ └── stdio-config.json
│ │ ├── native-messaging-host.ts
│ │ ├── scripts
│ │ │ ├── browser-config.ts
│ │ │ ├── build.ts
│ │ │ ├── constant.ts
│ │ │ ├── postinstall.ts
│ │ │ ├── register-dev.ts
│ │ │ ├── register.ts
│ │ │ ├── run_host.bat
│ │ │ ├── run_host.sh
│ │ │ └── utils.ts
│ │ ├── server
│ │ │ ├── index.ts
│ │ │ └── server.test.ts
│ │ └── util
│ │ └── logger.ts
│ └── tsconfig.json
├── commitlint.config.cjs
├── docs
│ ├── ARCHITECTURE_zh.md
│ ├── ARCHITECTURE.md
│ ├── CHANGELOG.md
│ ├── CONTRIBUTING_zh.md
│ ├── CONTRIBUTING.md
│ ├── TOOLS_zh.md
│ ├── TOOLS.md
│ ├── TROUBLESHOOTING_zh.md
│ ├── TROUBLESHOOTING.md
│ └── WINDOWS_INSTALL_zh.md
├── eslint.config.js
├── LICENSE
├── package.json
├── packages
│ ├── shared
│ │ ├── package.json
│ │ ├── src
│ │ │ ├── constants.ts
│ │ │ ├── index.ts
│ │ │ ├── tools.ts
│ │ │ └── types.ts
│ │ └── tsconfig.json
│ └── wasm-simd
│ ├── .gitignore
│ ├── BUILD.md
│ ├── Cargo.toml
│ ├── package.json
│ ├── README.md
│ └── src
│ └── lib.rs
├── pnpm-lock.yaml
├── pnpm-workspace.yaml
├── prompt
│ ├── content-analize.md
│ ├── excalidraw-prompt.md
│ └── modify-web.md
├── README_zh.md
├── README.md
├── releases
│ ├── chrome-extension
│ │ └── latest
│ │ └── chrome-mcp-server-lastest.zip
│ └── README.md
└── test-inject-script.js
```
# Files
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
```
1 | *.onnx filter=lfs diff=lfs merge=lfs -text
2 |
```
--------------------------------------------------------------------------------
/.prettierrc.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "semi": true,
3 | "singleQuote": true,
4 | "tabWidth": 2,
5 | "printWidth": 100,
6 | "endOfLine": "auto",
7 | "proseWrap": "preserve",
8 | "htmlWhitespaceSensitivity": "strict"
9 | }
10 |
```
--------------------------------------------------------------------------------
/packages/wasm-simd/.gitignore:
--------------------------------------------------------------------------------
```
1 | # WASM build outputs
2 | /pkg/
3 | /target/
4 |
5 | # Rust
6 | Cargo.lock
7 |
8 | # Node.js
9 | node_modules/
10 | npm-debug.log*
11 | yarn-debug.log*
12 | yarn-error.log*
13 |
14 | # IDE
15 | .vscode/
16 | .idea/
17 | *.swp
18 | *.swo
19 |
20 | # OS
21 | .DS_Store
22 | Thumbs.db
23 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/.env.example:
--------------------------------------------------------------------------------
```
1 | # Chrome Extension Private Key
2 | # Copy this file to .env and replace with your actual private key
3 | # This key is used for Chrome extension packaging and should be kept secure
4 | CHROME_EXTENSION_KEY=YOUR_PRIVATE_KEY_HERE
5 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | node_modules
11 | .output
12 | stats.html
13 | stats-*.json
14 | .wxt
15 | web-ext.config.ts
16 | dist
17 |
18 | # Editor directories and files
19 | .vscode/*
20 | !.vscode/extensions.json
21 | .idea
22 | .DS_Store
23 | *.suo
24 | *.ntvs*
25 | *.njsproj
26 | *.sln
27 | *.sw?
28 | *.onnx
29 |
30 | # Environment variables
31 | .env
32 | .env.local
33 | .env.*.local
34 |
35 | # Prevent npm metadata pollution
36 | false/
37 | metadata-v1.3/
38 | registry.npmmirror.com/
39 | registry.npmjs.com/
```
--------------------------------------------------------------------------------
/app/chrome-extension/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # WXT + Vue 3
2 |
3 | This template should help get you started developing with Vue 3 in WXT.
4 |
5 | ## Recommended IDE Setup
6 |
7 | - [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar).
8 |
```
--------------------------------------------------------------------------------
/packages/wasm-simd/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # @chrome-mcp/wasm-simd
2 |
3 | SIMD-optimized WebAssembly math functions for high-performance vector operations.
4 |
5 | ## Features
6 |
7 | - 🚀 **SIMD Acceleration**: Uses WebAssembly SIMD instructions for 4-8x performance boost
8 | - 🧮 **Vector Operations**: Optimized cosine similarity, batch processing, and matrix operations
9 | - 🔧 **Memory Efficient**: Smart memory pooling and aligned buffer management
10 | - 🌐 **Browser Compatible**: Works in all modern browsers with WebAssembly SIMD support
11 |
12 | ## Performance
13 |
14 | | Operation | JavaScript | SIMD WASM | Speedup |
15 | | ------------------------------ | ---------- | --------- | ------- |
16 | | Cosine Similarity (768d) | 100ms | 18ms | 5.6x |
17 | | Batch Similarity (100x768d) | 850ms | 95ms | 8.9x |
18 | | Similarity Matrix (50x50x384d) | 2.1s | 180ms | 11.7x |
19 |
20 | ## Usage
21 |
22 | ```rust
23 | // The Rust implementation provides SIMD-optimized functions
24 | use wasm_bindgen::prelude::*;
25 |
26 | #[wasm_bindgen]
27 | pub struct SIMDMath;
28 |
29 | #[wasm_bindgen]
30 | impl SIMDMath {
31 | #[wasm_bindgen(constructor)]
32 | pub fn new() -> SIMDMath { SIMDMath }
33 |
34 | #[wasm_bindgen]
35 | pub fn cosine_similarity(&self, vec_a: &[f32], vec_b: &[f32]) -> f32 {
36 | // SIMD-optimized implementation
37 | }
38 | }
39 | ```
40 |
41 | ## Building
42 |
43 | ```bash
44 | # Install dependencies
45 | cargo install wasm-pack
46 |
47 | # Build for release
48 | npm run build
49 |
50 | # Build for development
51 | npm run build:dev
52 | ```
53 |
54 | ## Browser Support
55 |
56 | - Chrome 91+
57 | - Firefox 89+
58 | - Safari 16.4+
59 | - Edge 91+
60 |
61 | Older browsers automatically fall back to JavaScript implementations.
62 |
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # Chrome MCP Server 🚀
2 |
3 | [](https://img.shields.io/github/stars/hangwin/mcp-chrome)
4 | [](https://opensource.org/licenses/MIT)
5 | [](https://www.typescriptlang.org/)
6 | [](https://developer.chrome.com/docs/extensions/)
7 | [](https://img.shields.io/github/v/release/hangwin/mcp-chrome.svg)
8 |
9 |
10 | > 🌟 **Turn your Chrome browser into your intelligent assistant** - Let AI take control of your browser, transforming it into a powerful AI-controlled automation tool.
11 |
12 | **📖 Documentation**: [English](README.md) | [中文](README_zh.md)
13 |
14 | > The project is still in its early stages and is under intensive development. More features, stability improvements, and other enhancements will follow.
15 |
16 | ---
17 |
18 | ## 🎯 What is Chrome MCP Server?
19 |
20 | Chrome MCP Server is a Chrome extension-based **Model Context Protocol (MCP) server** that exposes your Chrome browser functionality to AI assistants like Claude, enabling complex browser automation, content analysis, and semantic search. Unlike traditional browser automation tools (like Playwright), **Chrome MCP Server** directly uses your daily Chrome browser, leveraging existing user habits, configurations, and login states, allowing various large models or chatbots to take control of your browser and truly become your everyday assistant.
21 |
22 | ## ✨ Core Features
23 |
24 | - 😁 **Chatbot/Model Agnostic**: Let any LLM or chatbot client or agent you prefer automate your browser
25 | - ⭐️ **Use Your Original Browser**: Seamlessly integrate with your existing browser environment (your configurations, login states, etc.)
26 | - 💻 **Fully Local**: Pure local MCP server ensuring user privacy
27 | - 🚄 **Streamable HTTP**: Streamable HTTP connection method
28 | - 🏎 **Cross-Tab**: Cross-tab context
29 | - 🧠 **Semantic Search**: Built-in vector database for intelligent browser tab content discovery
30 | - 🔍 **Smart Content Analysis**: AI-powered text extraction and similarity matching
31 | - 🌐 **20+ Tools**: Support for screenshots, network monitoring, interactive operations, bookmark management, browsing history, and 20+ other tools
32 | - 🚀 **SIMD-Accelerated AI**: Custom WebAssembly SIMD optimization for 4-8x faster vector operations
33 |
34 | ## 🆚 Comparison with Similar Projects
35 |
36 | | Comparison Dimension | Playwright-based MCP Server | Chrome Extension-based MCP Server |
37 | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
38 | | **Resource Usage** | ❌ Requires launching independent browser process, installing Playwright dependencies, downloading browser binaries, etc. | ✅ No need to launch independent browser process, directly utilizes user's already open Chrome browser |
39 | | **User Session Reuse** | ❌ Requires re-login | ✅ Automatically uses existing login state |
40 | | **Browser Environment** | ❌ Clean environment lacks user settings | ✅ Fully preserves user environment |
41 | | **API Access** | ⚠️ Limited to Playwright API | ✅ Full access to Chrome native APIs |
42 | | **Startup Speed** | ❌ Requires launching browser process | ✅ Only needs to activate extension |
43 | | **Response Speed** | 50-200ms inter-process communication | ✅ Faster |
44 |
45 | ## 🚀 Quick Start
46 |
47 | ### Prerequisites
48 |
49 | - Node.js >= 18.19.0 and pnpm/npm
50 | - Chrome/Chromium browser
51 |
52 | ### Installation Steps
53 |
54 | 1. **Download the latest Chrome extension from GitHub**
55 |
56 | Download link: https://github.com/hangwin/mcp-chrome/releases
57 |
58 | 2. **Install mcp-chrome-bridge globally**
59 |
60 | npm
61 |
62 | ```bash
63 | npm install -g mcp-chrome-bridge
64 | ```
65 |
66 | pnpm
67 |
68 | ```bash
69 | # Method 1: Enable scripts globally (recommended)
70 | pnpm config set enable-pre-post-scripts true
71 | pnpm install -g mcp-chrome-bridge
72 |
73 | # Method 2: Manual registration (if postinstall doesn't run)
74 | pnpm install -g mcp-chrome-bridge
75 | mcp-chrome-bridge register
76 | ```
77 |
78 | > Note: pnpm v7+ disables postinstall scripts by default for security. The `enable-pre-post-scripts` setting controls whether pre/post install scripts run. If automatic registration fails, use the manual registration command above.
79 |
80 | 3. **Load Chrome Extension**
81 | - Open Chrome and go to `chrome://extensions/`
82 | - Enable "Developer mode"
83 | - Click "Load unpacked" and select `your/dowloaded/extension/folder`
84 | - Click the extension icon to open the plugin, then click connect to see the MCP configuration
85 | <img width="475" alt="Screenshot 2025-06-09 15 52 06" src="https://github.com/user-attachments/assets/241e57b8-c55f-41a4-9188-0367293dc5bc" />
86 |
87 | ### Usage with MCP Protocol Clients
88 |
89 | #### Using Streamable HTTP Connection (👍🏻 Recommended)
90 |
91 | Add the following configuration to your MCP client configuration (using CherryStudio as an example):
92 |
93 | > Streamable HTTP connection method is recommended
94 |
95 | ```json
96 | {
97 | "mcpServers": {
98 | "chrome-mcp-server": {
99 | "type": "streamableHttp",
100 | "url": "http://127.0.0.1:12306/mcp"
101 | }
102 | }
103 | }
104 | ```
105 |
106 | #### Using STDIO Connection (Alternative)
107 |
108 | If your client only supports stdio connection method, please use the following approach:
109 |
110 | 1. First, check the installation location of the npm package you just installed
111 |
112 | ```sh
113 | # npm check method
114 | npm list -g mcp-chrome-bridge
115 | # pnpm check method
116 | pnpm list -g mcp-chrome-bridge
117 | ```
118 |
119 | Assuming the command above outputs the path: /Users/xxx/Library/pnpm/global/5
120 | Then your final path would be: /Users/xxx/Library/pnpm/global/5/node_modules/mcp-chrome-bridge/dist/mcp/mcp-server-stdio.js
121 |
122 | 2. Replace the configuration below with the final path you just obtained
123 |
124 | ```json
125 | {
126 | "mcpServers": {
127 | "chrome-mcp-stdio": {
128 | "command": "npx",
129 | "args": [
130 | "node",
131 | "/Users/xxx/Library/pnpm/global/5/node_modules/mcp-chrome-bridge/dist/mcp/mcp-server-stdio.js"
132 | ]
133 | }
134 | }
135 | }
136 | ```
137 |
138 | eg:config in augment:
139 |
140 | <img width="494" alt="截屏2025-06-22 22 11 25" src="https://github.com/user-attachments/assets/48eefc0c-a257-4d3b-8bbe-d7ff716de2bf" />
141 |
142 | ## 🛠️ Available Tools
143 |
144 | Complete tool list: [Complete Tool List](docs/TOOLS.md)
145 |
146 | <details>
147 | <summary><strong>📊 Browser Management (6 tools)</strong></summary>
148 |
149 | - `get_windows_and_tabs` - List all browser windows and tabs
150 | - `chrome_navigate` - Navigate to URLs and control viewport
151 | - `chrome_switch_tab` - Switch the current active tab
152 | - `chrome_close_tabs` - Close specific tabs or windows
153 | - `chrome_go_back_or_forward` - Browser navigation control
154 | - `chrome_inject_script` - Inject content scripts into web pages
155 | - `chrome_send_command_to_inject_script` - Send commands to injected content scripts
156 | </details>
157 |
158 | <details>
159 | <summary><strong>📸 Screenshots & Visual (1 tool)</strong></summary>
160 |
161 | - `chrome_screenshot` - Advanced screenshot capture with element targeting, full-page support, and custom dimensions
162 | </details>
163 |
164 | <details>
165 | <summary><strong>🌐 Network Monitoring (4 tools)</strong></summary>
166 |
167 | - `chrome_network_capture_start/stop` - webRequest API network capture
168 | - `chrome_network_debugger_start/stop` - Debugger API with response bodies
169 | - `chrome_network_request` - Send custom HTTP requests
170 | </details>
171 |
172 | <details>
173 | <summary><strong>🔍 Content Analysis (4 tools)</strong></summary>
174 |
175 | - `search_tabs_content` - AI-powered semantic search across browser tabs
176 | - `chrome_get_web_content` - Extract HTML/text content from pages
177 | - `chrome_get_interactive_elements` - Find clickable elements
178 | - `chrome_console` - Capture and retrieve console output from browser tabs
179 | </details>
180 |
181 | <details>
182 | <summary><strong>🎯 Interaction (3 tools)</strong></summary>
183 |
184 | - `chrome_click_element` - Click elements using CSS selectors
185 | - `chrome_fill_or_select` - Fill forms and select options
186 | - `chrome_keyboard` - Simulate keyboard input and shortcuts
187 | </details>
188 |
189 | <details>
190 | <summary><strong>📚 Data Management (5 tools)</strong></summary>
191 |
192 | - `chrome_history` - Search browser history with time filters
193 | - `chrome_bookmark_search` - Find bookmarks by keywords
194 | - `chrome_bookmark_add` - Add new bookmarks with folder support
195 | - `chrome_bookmark_delete` - Delete bookmarks
196 | </details>
197 |
198 | ## 🧪 Usage Examples
199 |
200 | ### AI helps you summarize webpage content and automatically control Excalidraw for drawing
201 |
202 | prompt: [excalidraw-prompt](prompt/excalidraw-prompt.md)
203 | Instruction: Help me summarize the current page content, then draw a diagram to aid my understanding.
204 | https://www.youtube.com/watch?v=3fBPdUBWVz0
205 |
206 | https://github.com/user-attachments/assets/fd17209b-303d-48db-9e5e-3717141df183
207 |
208 | ### After analyzing the content of the image, the LLM automatically controls Excalidraw to replicate the image
209 |
210 | prompt: [excalidraw-prompt](prompt/excalidraw-prompt.md)|[content-analize](prompt/content-analize.md)
211 | Instruction: First, analyze the content of the image, and then replicate the image by combining the analysis with the content of the image.
212 | https://www.youtube.com/watch?v=tEPdHZBzbZk
213 |
214 | https://github.com/user-attachments/assets/60d12b1a-9b74-40f4-994c-95e8fa1fc8d3
215 |
216 | ### AI automatically injects scripts and modifies webpage styles
217 |
218 | prompt: [modify-web-prompt](prompt/modify-web.md)
219 | Instruction: Help me modify the current page's style and remove advertisements.
220 | https://youtu.be/twI6apRKHsk
221 |
222 | https://github.com/user-attachments/assets/69cb561c-2e1e-4665-9411-4a3185f9643e
223 |
224 | ### AI automatically captures network requests for you
225 |
226 | query: I want to know what the search API for Xiaohongshu is and what the response structure looks like
227 |
228 | https://youtu.be/1hHKr7XKqnQ
229 |
230 | https://github.com/user-attachments/assets/dc7e5cab-b9af-4b9a-97ce-18e4837318d9
231 |
232 | ### AI helps analyze your browsing history
233 |
234 | query: Analyze my browsing history from the past month
235 |
236 | https://youtu.be/jf2UZfrR2Vk
237 |
238 | https://github.com/user-attachments/assets/31b2e064-88c6-4adb-96d7-50748b826eae
239 |
240 | ### Web page conversation
241 |
242 | query: Translate and summarize the current web page
243 | https://youtu.be/FlJKS9UQyC8
244 |
245 | https://github.com/user-attachments/assets/aa8ef2a1-2310-47e6-897a-769d85489396
246 |
247 | ### AI automatically takes screenshots for you (web page screenshots)
248 |
249 | query: Take a screenshot of Hugging Face's homepage
250 | https://youtu.be/7ycK6iksWi4
251 |
252 | https://github.com/user-attachments/assets/65c6eee2-6366-493d-a3bd-2b27529ff5b3
253 |
254 | ### AI automatically takes screenshots for you (element screenshots)
255 |
256 | query: Capture the icon from Hugging Face's homepage
257 | https://youtu.be/ev8VivANIrk
258 |
259 | https://github.com/user-attachments/assets/d0cf9785-c2fe-4729-a3c5-7f2b8b96fe0c
260 |
261 | ### AI helps manage bookmarks
262 |
263 | query: Add the current page to bookmarks and put it in an appropriate folder
264 |
265 | https://youtu.be/R_83arKmFTo
266 |
267 | https://github.com/user-attachments/assets/15a7d04c-0196-4b40-84c2-bafb5c26dfe0
268 |
269 | ### Automatically close web pages
270 |
271 | query: Close all shadcn-related web pages
272 |
273 | https://youtu.be/2wzUT6eNVg4
274 |
275 | https://github.com/user-attachments/assets/83de4008-bb7e-494d-9b0f-98325cfea592
276 |
277 | ## 🤝 Contributing
278 |
279 | We welcome contributions! Please see [CONTRIBUTING.md](docs/CONTRIBUTING.md) for detailed guidelines.
280 |
281 | ## 🚧 Future Roadmap
282 |
283 | We have exciting plans for the future development of Chrome MCP Server:
284 |
285 | - [ ] Authentication
286 | - [ ] Recording and Playback
287 | - [ ] Workflow Automation
288 | - [ ] Enhanced Browser Support (Firefox Extension)
289 |
290 | ---
291 |
292 | **Want to contribute to any of these features?** Check out our [Contributing Guide](docs/CONTRIBUTING.md) and join our development community!
293 |
294 | ## 📄 License
295 |
296 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
297 |
298 | ## 📚 More Documentation
299 |
300 | - [Architecture Design](docs/ARCHITECTURE.md) - Detailed technical architecture documentation
301 | - [TOOLS API](docs/TOOLS.md) - Complete tool API documentation
302 | - [Troubleshooting](docs/TROUBLESHOOTING.md) - Common issue solutions
303 |
```
--------------------------------------------------------------------------------
/docs/CONTRIBUTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # Contributing Guide 🤝
2 |
3 | Thank you for your interest in contributing to Chrome MCP Server! This document provides guidelines and information for contributors.
4 |
5 | ## 🎯 How to Contribute
6 |
7 | We welcome contributions in many forms:
8 |
9 | - 🐛 Bug reports and fixes
10 | - ✨ New features and tools
11 | - 📚 Documentation improvements
12 | - 🧪 Tests and performance optimizations
13 | - 🌐 Translations and internationalization
14 | - 💡 Ideas and suggestions
15 |
16 | ## 🚀 Getting Started
17 |
18 | ### Prerequisites
19 |
20 | - **Node.js 18.19.0+** and **pnpm or npm** (latest version)
21 | - **Chrome/Chromium** browser for testing
22 | - **Git** for version control
23 | - **Rust** (for WASM development, optional)
24 | - **TypeScript** knowledge
25 |
26 | ### Development Setup
27 |
28 | 1. **Fork and clone the repository**
29 |
30 | ```bash
31 | git clone https://github.com/YOUR_USERNAME/chrome-mcp-server.git
32 | cd chrome-mcp-server
33 | ```
34 |
35 | 2. **Install dependencies**
36 |
37 | ```bash
38 | pnpm install
39 | ```
40 |
41 | 3. **Start the project**
42 |
43 | ```bash
44 | npm run dev
45 | ```
46 |
47 | 4. **Load the extension in Chrome**
48 | - Open `chrome://extensions/`
49 | - Enable "Developer mode"
50 | - Click "Load unpacked" and select `your/extension/dist`
51 |
52 | ## 🏗️ Project Structure
53 |
54 | ```
55 | chrome-mcp-server/
56 | ├── app/
57 | │ ├── chrome-extension/ # Chrome extension (WXT + Vue 3)
58 | │ │ ├── entrypoints/ # Background scripts, popup, content scripts
59 | │ │ ├── utils/ # AI models, vector database, utilities
60 | │ │ └── workers/ # Web Workers for AI processing
61 | │ └── native-server/ # Native messaging server (Fastify + TypeScript)
62 | │ ├── src/mcp/ # MCP protocol implementation
63 | │ └── src/server/ # HTTP server and native messaging
64 | ├── packages/
65 | │ ├── shared/ # Shared types and utilities
66 | │ └── wasm-simd/ # SIMD-optimized WebAssembly math functions
67 | └── docs/ # Documentation
68 | ```
69 |
70 | ## 🛠️ Development Workflow
71 |
72 | ### Adding New Tools
73 |
74 | 1. **Define the tool schema in `packages/shared/src/tools.ts`**:
75 |
76 | ```typescript
77 | {
78 | name: 'your_new_tool',
79 | description: 'Description of what your tool does',
80 | inputSchema: {
81 | type: 'object',
82 | properties: {
83 | // Define parameters
84 | },
85 | required: ['param1']
86 | }
87 | }
88 | ```
89 |
90 | 2. **Implement the tool in `app/chrome-extension/entrypoints/background/tools/browser/`**:
91 |
92 | ```typescript
93 | class YourNewTool extends BaseBrowserToolExecutor {
94 | name = TOOL_NAMES.BROWSER.YOUR_NEW_TOOL;
95 |
96 | async execute(args: YourToolParams): Promise<ToolResult> {
97 | // Implementation
98 | }
99 | }
100 | ```
101 |
102 | 3. **Export the tool in `app/chrome-extension/entrypoints/background/tools/browser/index.ts`**
103 |
104 | 4. **Add tests in the appropriate test directory**
105 |
106 | ### Code Style Guidelines
107 |
108 | - **TypeScript**: Use strict TypeScript with proper typing
109 | - **ESLint**: Follow the configured ESLint rules (`pnpm lint`)
110 | - **Prettier**: Format code with Prettier (`pnpm format`)
111 | - **Naming**: Use descriptive names and follow existing patterns
112 | - **Comments**: Add JSDoc comments for public APIs
113 | - **Error Handling**: Always handle errors gracefully
114 |
115 | ## 📝 Pull Request Process
116 |
117 | 1. **Create a feature branch**
118 |
119 | ```bash
120 | git checkout -b feature/your-feature-name
121 | ```
122 |
123 | 2. **Make your changes**
124 |
125 | - Follow the code style guidelines
126 | - Add tests for new functionality
127 | - Update documentation if needed
128 |
129 | 3. **Test your changes**
130 |
131 | - Ensure all existing tests pass
132 | - Test the Chrome extension manually
133 | - Verify MCP protocol compatibility
134 |
135 | 4. **Commit your changes**
136 |
137 | ```bash
138 | git add .
139 | git commit -m "feat: add your feature description"
140 | ```
141 |
142 | We use [Conventional Commits](https://www.conventionalcommits.org/):
143 |
144 | - `feat:` for new features
145 | - `fix:` for bug fixes
146 | - `docs:` for documentation changes
147 | - `test:` for adding tests
148 | - `refactor:` for code refactoring
149 |
150 | 5. **Push and create a Pull Request**
151 |
152 | ```bash
153 | git push origin feature/your-feature-name
154 | ```
155 |
156 | ## 🐛 Bug Reports
157 |
158 | When reporting bugs, please include:
159 |
160 | - **Environment**: OS, Chrome version, Node.js version
161 | - **Steps to reproduce**: Clear, step-by-step instructions
162 | - **Expected behavior**: What should happen
163 | - **Actual behavior**: What actually happens
164 | - **Screenshots/logs**: If applicable
165 | - **MCP client**: Which MCP client you're using (Claude Desktop, etc.)
166 |
167 | ## 💡 Feature Requests
168 |
169 | For feature requests, please provide:
170 |
171 | - **Use case**: Why is this feature needed?
172 | - **Proposed solution**: How should it work?
173 | - **Alternatives**: Any alternative solutions considered?
174 | - **Additional context**: Screenshots, examples, etc.
175 |
176 | ## 🔧 Development Tips
177 |
178 | ### Using WASM SIMD
179 |
180 | If you're contributing to the WASM SIMD package:
181 |
182 | ```bash
183 | cd packages/wasm-simd
184 | # Install Rust and wasm-pack if not already installed
185 | curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
186 | cargo install wasm-pack
187 |
188 | # Build WASM package
189 | pnpm build
190 |
191 | # The built files will be copied to app/chrome-extension/workers/
192 | ```
193 |
194 | ### Debugging Chrome Extension
195 |
196 | - Use Chrome DevTools for debugging extension popup and background scripts
197 | - Check `chrome://extensions/` for extension errors
198 | - Use `console.log` statements for debugging
199 | - Monitor the native messaging connection in the background script
200 |
201 | ### Testing MCP Protocol
202 |
203 | - Use MCP Inspector for protocol debugging
204 | - Test with different MCP clients (Claude Desktop, custom clients)
205 | - Verify tool schemas and responses match MCP specifications
206 |
207 | ## 📚 Resources
208 |
209 | - [Model Context Protocol Specification](https://modelcontextprotocol.io/)
210 | - [Chrome Extension Development](https://developer.chrome.com/docs/extensions/)
211 | - [WXT Framework Documentation](https://wxt.dev/)
212 | - [TypeScript Handbook](https://www.typescriptlang.org/docs/)
213 |
214 | ## 🤝 Community
215 |
216 | - **GitHub Issues**: For bug reports and feature requests
217 | - **GitHub Discussions**: For questions and general discussion
218 | - **Pull Requests**: For code contributions
219 |
220 | ## 📄 License
221 |
222 | By contributing to Chrome MCP Server, you agree that your contributions will be licensed under the MIT License.
223 |
224 | ## 🎯 Contributor Guidelines
225 |
226 | ### New Contributors
227 |
228 | If you're contributing to an open source project for the first time:
229 |
230 | 1. **Start small**: Look for issues labeled "good first issue"
231 | 2. **Read the code**: Familiarize yourself with the project structure and coding style
232 | 3. **Ask questions**: Ask questions in GitHub Discussions
233 | 4. **Learn the tools**: Get familiar with Git, GitHub, TypeScript, and other tools
234 |
235 | ### Experienced Contributors
236 |
237 | - **Architecture improvements**: Propose system-level improvements
238 | - **Performance optimization**: Identify and fix performance bottlenecks
239 | - **New features**: Design and implement complex new features
240 | - **Mentor newcomers**: Help new contributors get started
241 |
242 | ### Documentation Contributions
243 |
244 | - **API documentation**: Improve tool documentation and examples
245 | - **Tutorials**: Create usage guides and best practices
246 | - **Translations**: Help translate documentation to other languages
247 | - **Video content**: Create demo videos and tutorials
248 |
249 | ### Testing Contributions
250 |
251 | - **Unit tests**: Write tests for new features
252 | - **Integration tests**: Test interactions between components
253 | - **Performance tests**: Benchmark testing and performance regression detection
254 | - **User testing**: Functional testing in real-world scenarios
255 |
256 | ## 🏆 Contributor Recognition
257 |
258 | We value every contribution, no matter how big or small. Contributors will be recognized in the following ways:
259 |
260 | - **README acknowledgments**: Contributors listed in the project README
261 | - **Release notes**: Contributors thanked in version release notes
262 | - **Contributor badges**: Contributor badges on GitHub profiles
263 | - **Community recognition**: Special thanks in community discussions
264 |
265 | Thank you for considering contributing to Chrome MCP Server! Your participation makes this project better.
266 |
```
--------------------------------------------------------------------------------
/pnpm-workspace.yaml:
--------------------------------------------------------------------------------
```yaml
1 | packages:
2 | - 'app/*'
3 | - 'packages/*'
```
--------------------------------------------------------------------------------
/.vscode/extensions.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "recommendations": ["Vue.volar"]
3 | }
4 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "extends": "./.wxt/tsconfig.json"
3 | }
4 |
```
--------------------------------------------------------------------------------
/app/native-server/src/mcp/stdio-config.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "url": "http://127.0.0.1:12306/mcp"
3 | }
4 |
```
--------------------------------------------------------------------------------
/commitlint.config.cjs:
--------------------------------------------------------------------------------
```
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
```
--------------------------------------------------------------------------------
/packages/shared/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export * from './constants';
2 | export * from './types';
3 | export * from './tools';
4 |
```
--------------------------------------------------------------------------------
/app/native-server/src/scripts/register-dev.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { tryRegisterUserLevelHost } from './utils';
2 |
3 | tryRegisterUserLevelHost();
4 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/content.ts:
--------------------------------------------------------------------------------
```typescript
1 | export default defineContentScript({
2 | matches: ['*://*.google.com/*'],
3 | main() {},
4 | });
5 |
```
--------------------------------------------------------------------------------
/packages/shared/src/constants.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const DEFAULT_SERVER_PORT = 56889;
2 | export const HOST_NAME = 'com.chrome_mcp.native_host';
3 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/main.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createApp } from 'vue';
2 | import './style.css';
3 | import App from './App.vue';
4 |
5 | createApp(App).mount('#app');
6 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/offscreen/index.html:
--------------------------------------------------------------------------------
```html
1 | <!DOCTYPE html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8" />
5 | </head>
6 | <body>
7 | <script type="module" src="./main.ts"></script>
8 | </body>
9 | </html>
```
--------------------------------------------------------------------------------
/app/native-server/src/scripts/constant.ts:
--------------------------------------------------------------------------------
```typescript
1 | export const COMMAND_NAME = 'chrome-mcp-bridge';
2 | export const EXTENSION_ID = 'hbdgbgagpkpjffpklnamcljpakneikee';
3 | export const HOST_NAME = 'com.chromemcp.nativehost';
4 | export const DESCRIPTION = 'Node.js Host for Browser Bridge Extension';
5 |
```
--------------------------------------------------------------------------------
/packages/wasm-simd/Cargo.toml:
--------------------------------------------------------------------------------
```toml
1 | [package]
2 | name = "simd-math"
3 | version = "0.1.0"
4 | edition = "2021"
5 |
6 | [lib]
7 | crate-type = ["cdylib"]
8 |
9 | [dependencies]
10 | wasm-bindgen = "0.2"
11 | wide = "0.7"
12 | console_error_panic_hook = "0.1"
13 |
14 | [dependencies.web-sys]
15 | version = "0.3"
16 | features = [
17 | "console",
18 | ]
19 |
20 | [profile.release]
21 | opt-level = 3
22 | lto = true
23 | codegen-units = 1
24 | panic = "abort"
25 |
```
--------------------------------------------------------------------------------
/packages/shared/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "esModuleInterop": true,
7 | "declaration": true,
8 | "outDir": "./dist",
9 | "strict": true,
10 | "skipLibCheck": true
11 | },
12 | "include": ["src/**/*"],
13 | "exclude": ["node_modules", "dist"]
14 | }
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/index.html:
--------------------------------------------------------------------------------
```html
1 | <!doctype html>
2 | <html lang="en">
3 | <head>
4 | <meta charset="UTF-8" />
5 | <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6 | <title>Default Popup Title</title>
7 | <meta name="manifest.type" content="browser_action" />
8 | </head>
9 | <body>
10 | <div id="app"></div>
11 | <script type="module" src="./main.ts"></script>
12 | </body>
13 | </html>
14 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { default as DocumentIcon } from './DocumentIcon.vue';
2 | export { default as DatabaseIcon } from './DatabaseIcon.vue';
3 | export { default as BoltIcon } from './BoltIcon.vue';
4 | export { default as TrashIcon } from './TrashIcon.vue';
5 | export { default as CheckIcon } from './CheckIcon.vue';
6 | export { default as TabIcon } from './TabIcon.vue';
7 | export { default as VectorIcon } from './VectorIcon.vue';
8 |
```
--------------------------------------------------------------------------------
/app/native-server/jest.config.js:
--------------------------------------------------------------------------------
```javascript
1 | module.exports = {
2 | preset: 'ts-jest',
3 | testEnvironment: 'node',
4 | roots: ['<rootDir>/src'],
5 | testMatch: ['**/__tests__/**/*.ts', '**/?(*.)+(spec|test).ts'],
6 | collectCoverage: true,
7 | collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/scripts/**/*'],
8 | coverageDirectory: 'coverage',
9 | coverageThreshold: {
10 | global: {
11 | branches: 70,
12 | functions: 80,
13 | lines: 80,
14 | statements: 80,
15 | },
16 | },
17 | };
18 |
```
--------------------------------------------------------------------------------
/app/native-server/src/mcp/mcp-server.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2 | import { setupTools } from './register-tools';
3 |
4 | export let mcpServer: Server | null = null;
5 |
6 | export const getMcpServer = () => {
7 | if (mcpServer) {
8 | return mcpServer;
9 | }
10 | mcpServer = new Server(
11 | {
12 | name: 'ChromeMcpServer',
13 | version: '1.0.0',
14 | },
15 | {
16 | capabilities: {
17 | tools: {},
18 | },
19 | },
20 | );
21 |
22 | setupTools(mcpServer);
23 | return mcpServer;
24 | };
25 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/BoltIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | fill="none"
5 | viewBox="0 0 24 24"
6 | stroke-width="1.5"
7 | stroke="currentColor"
8 | :class="className"
9 | >
10 | <path
11 | stroke-linecap="round"
12 | stroke-linejoin="round"
13 | d="m3.75 13.5 10.5-11.25L12 10.5h8.25L9.75 21.75 12 13.5H3.75Z"
14 | />
15 | </svg>
16 | </template>
17 |
18 | <script lang="ts" setup>
19 | interface Props {
20 | className?: string;
21 | }
22 |
23 | withDefaults(defineProps<Props>(), {
24 | className: 'icon-default',
25 | });
26 | </script>
27 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/assets/vue.svg:
--------------------------------------------------------------------------------
```
1 | <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
2 |
```
--------------------------------------------------------------------------------
/app/native-server/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2018",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "lib": ["ES2018", "DOM"],
7 | "outDir": "dist",
8 | "rootDir": "src",
9 | "strict": true,
10 | "esModuleInterop": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "skipLibCheck": true,
13 | "declaration": true,
14 | "sourceMap": true,
15 | "resolveJsonModule": true
16 | },
17 | "include": ["src/**/*"],
18 | "exclude": ["node_modules", "dist", "**/*.spec.ts", "**/*.test.ts"]
19 | }
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/CheckIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | viewBox="0 0 20 20"
5 | fill="currentColor"
6 | :class="className"
7 | >
8 | <path
9 | fill-rule="evenodd"
10 | d="M16.704 4.153a.75.75 0 0 1 .143 1.052l-8 10.5a.75.75 0 0 1-1.127.075l-4.5-4.5a.75.75 0 0 1 1.06-1.06l3.894 3.893 7.48-9.817a.75.75 0 0 1 1.052-.143Z"
11 | clip-rule="evenodd"
12 | />
13 | </svg>
14 | </template>
15 |
16 | <script lang="ts" setup>
17 | interface Props {
18 | className?: string;
19 | }
20 |
21 | withDefaults(defineProps<Props>(), {
22 | className: 'icon-small',
23 | });
24 | </script>
25 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/common/tool-handler.ts:
--------------------------------------------------------------------------------
```typescript
1 | import type { CallToolResult, TextContent, ImageContent } from '@modelcontextprotocol/sdk/types.js';
2 |
3 | export interface ToolResult extends CallToolResult {
4 | content: (TextContent | ImageContent)[];
5 | isError: boolean;
6 | }
7 |
8 | export interface ToolExecutor {
9 | execute(args: any): Promise<ToolResult>;
10 | }
11 |
12 | export const createErrorResponse = (
13 | message: string = 'Unknown error, please try again',
14 | ): ToolResult => {
15 | return {
16 | content: [
17 | {
18 | type: 'text',
19 | text: message,
20 | },
21 | ],
22 | isError: true,
23 | };
24 | };
25 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/DatabaseIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | fill="none"
5 | viewBox="0 0 24 24"
6 | stroke-width="2"
7 | stroke="currentColor"
8 | :class="className"
9 | >
10 | <path
11 | stroke-linecap="round"
12 | stroke-linejoin="round"
13 | d="M20.25 6.375c0 2.278-3.694 4.125-8.25 4.125S3.75 8.653 3.75 6.375m16.5 0c0-2.278-3.694-4.125-8.25-4.125S3.75 4.097 3.75 6.375m16.5 0v11.25c0 2.278-3.694 4.125-8.25 4.125s-8.25-1.847-8.25-4.125V6.375"
14 | />
15 | </svg>
16 | </template>
17 |
18 | <script lang="ts" setup>
19 | interface Props {
20 | className?: string;
21 | }
22 |
23 | withDefaults(defineProps<Props>(), {
24 | className: 'icon-default',
25 | });
26 | </script>
27 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/VectorIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | fill="none"
5 | viewBox="0 0 24 24"
6 | stroke-width="2"
7 | stroke="currentColor"
8 | :class="className"
9 | >
10 | <path
11 | stroke-linecap="round"
12 | stroke-linejoin="round"
13 | d="M9 4.5a4.5 4.5 0 0 1 6 0M9 4.5V3a1.5 1.5 0 0 1 1.5-1.5h3A1.5 1.5 0 0 1 15 3v1.5M9 4.5a4.5 4.5 0 0 0-4.5 4.5v7.5A1.5 1.5 0 0 0 6 18h12a1.5 1.5 0 0 0 1.5-1.5V9a4.5 4.5 0 0 0-4.5-4.5M12 12l2.25 2.25M12 12l-2.25-2.25M12 12v6"
14 | />
15 | </svg>
16 | </template>
17 |
18 | <script lang="ts" setup>
19 | interface Props {
20 | className?: string;
21 | }
22 |
23 | withDefaults(defineProps<Props>(), {
24 | className: 'icon-default',
25 | });
26 | </script>
27 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/TabIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | fill="none"
5 | viewBox="0 0 24 24"
6 | stroke-width="2"
7 | stroke="currentColor"
8 | :class="className"
9 | >
10 | <path
11 | stroke-linecap="round"
12 | stroke-linejoin="round"
13 | d="M8.25 3v1.5M4.5 8.25H3m18 0h-1.5M4.5 12H3m18 0h-1.5m-16.5 3.75H3m18 0h-1.5M8.25 19.5V21M12 3v1.5m0 15V21m3.75-18v1.5m0 15V21m-9-1.5h10.5a2.25 2.25 0 0 0 2.25-2.25V6.75a2.25 2.25 0 0 0-2.25-2.25H6.75A2.25 2.25 0 0 0 4.5 6.75v10.5a2.25 2.25 0 0 0 2.25 2.25Z"
14 | />
15 | </svg>
16 | </template>
17 |
18 | <script lang="ts" setup>
19 | interface Props {
20 | className?: string;
21 | }
22 |
23 | withDefaults(defineProps<Props>(), {
24 | className: 'icon-default',
25 | });
26 | </script>
27 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/DocumentIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | fill="none"
5 | viewBox="0 0 24 24"
6 | stroke-width="2"
7 | stroke="currentColor"
8 | :class="className"
9 | >
10 | <path
11 | stroke-linecap="round"
12 | stroke-linejoin="round"
13 | d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"
14 | />
15 | </svg>
16 | </template>
17 |
18 | <script lang="ts" setup>
19 | interface Props {
20 | className?: string;
21 | }
22 |
23 | withDefaults(defineProps<Props>(), {
24 | className: 'icon-default',
25 | });
26 | </script>
27 |
```
--------------------------------------------------------------------------------
/packages/shared/src/types.ts:
--------------------------------------------------------------------------------
```typescript
1 | export enum NativeMessageType {
2 | START = 'start',
3 | STARTED = 'started',
4 | STOP = 'stop',
5 | STOPPED = 'stopped',
6 | PING = 'ping',
7 | PONG = 'pong',
8 | ERROR = 'error',
9 | PROCESS_DATA = 'process_data',
10 | PROCESS_DATA_RESPONSE = 'process_data_response',
11 | CALL_TOOL = 'call_tool',
12 | CALL_TOOL_RESPONSE = 'call_tool_response',
13 | // Additional message types used in Chrome extension
14 | SERVER_STARTED = 'server_started',
15 | SERVER_STOPPED = 'server_stopped',
16 | ERROR_FROM_NATIVE_HOST = 'error_from_native_host',
17 | CONNECT_NATIVE = 'connectNative',
18 | PING_NATIVE = 'ping_native',
19 | DISCONNECT_NATIVE = 'disconnect_native',
20 | }
21 |
22 | export interface NativeMessage<P = any, E = any> {
23 | type?: NativeMessageType;
24 | responseToRequestId?: string;
25 | payload?: P;
26 | error?: E;
27 | }
28 |
```
--------------------------------------------------------------------------------
/packages/wasm-simd/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@chrome-mcp/wasm-simd",
3 | "version": "0.1.0",
4 | "description": "SIMD-optimized WebAssembly math functions for Chrome MCP",
5 | "main": "pkg/simd_math.js",
6 | "types": "pkg/simd_math.d.ts",
7 | "files": [
8 | "pkg/"
9 | ],
10 | "scripts": {
11 | "build": "wasm-pack build --target web --out-dir pkg --release",
12 | "build:dev": "wasm-pack build --target web --out-dir pkg --dev",
13 | "clean": "rimraf pkg/",
14 | "test": "wasm-pack test --headless --firefox"
15 | },
16 | "keywords": [
17 | "wasm",
18 | "simd",
19 | "webassembly",
20 | "math",
21 | "cosine-similarity",
22 | "vector-operations"
23 | ],
24 | "author": "hangye",
25 | "license": "MIT",
26 | "devDependencies": {
27 | "rimraf": "^5.0.0"
28 | },
29 | "repository": {
30 | "type": "git",
31 | "url": "git+https://github.com/your-repo/chrome-mcp-server.git",
32 | "directory": "packages/wasm-simd"
33 | }
34 | }
35 |
```
--------------------------------------------------------------------------------
/app/native-server/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import serverInstance from './server';
3 | import nativeMessagingHostInstance from './native-messaging-host';
4 |
5 | try {
6 | serverInstance.setNativeHost(nativeMessagingHostInstance); // Server needs setNativeHost method
7 | nativeMessagingHostInstance.setServer(serverInstance); // NativeHost needs setServer method
8 | nativeMessagingHostInstance.start();
9 | } catch (error) {
10 | process.exit(1);
11 | }
12 |
13 | process.on('error', (error) => {
14 | process.exit(1);
15 | });
16 |
17 | // Handle process signals and uncaught exceptions
18 | process.on('SIGINT', () => {
19 | process.exit(0);
20 | });
21 |
22 | process.on('SIGTERM', () => {
23 | process.exit(0);
24 | });
25 |
26 | process.on('exit', (code) => {
27 | });
28 |
29 | process.on('uncaughtException', (error) => {
30 | process.exit(1);
31 | });
32 |
33 | process.on('unhandledRejection', (reason) => {
34 | // Don't exit immediately, let the program continue running
35 | });
36 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/icons/TrashIcon.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <svg
3 | xmlns="http://www.w3.org/2000/svg"
4 | fill="none"
5 | viewBox="0 0 24 24"
6 | stroke-width="1.5"
7 | stroke="currentColor"
8 | :class="className"
9 | >
10 | <path
11 | stroke-linecap="round"
12 | stroke-linejoin="round"
13 | d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0"
14 | />
15 | </svg>
16 | </template>
17 |
18 | <script lang="ts" setup>
19 | interface Props {
20 | className?: string;
21 | }
22 |
23 | withDefaults(defineProps<Props>(), {
24 | className: 'icon-default',
25 | });
26 | </script>
27 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createErrorResponse } from '@/common/tool-handler';
2 | import { ERROR_MESSAGES } from '@/common/constants';
3 | import * as browserTools from './browser';
4 |
5 | const tools = { ...browserTools };
6 | const toolsMap = new Map(Object.values(tools).map((tool) => [tool.name, tool]));
7 |
8 | /**
9 | * Tool call parameter interface
10 | */
11 | export interface ToolCallParam {
12 | name: string;
13 | args: any;
14 | }
15 |
16 | /**
17 | * Handle tool execution
18 | */
19 | export const handleCallTool = async (param: ToolCallParam) => {
20 | const tool = toolsMap.get(param.name);
21 | if (!tool) {
22 | return createErrorResponse(`Tool ${param.name} not found`);
23 | }
24 |
25 | try {
26 | return await tool.execute(param.args);
27 | } catch (error) {
28 | console.error(`Tool execution failed for ${param.name}:`, error);
29 | return createErrorResponse(
30 | error instanceof Error ? error.message : ERROR_MESSAGES.TOOL_EXECUTION_FAILED,
31 | );
32 | }
33 | };
34 |
```
--------------------------------------------------------------------------------
/packages/shared/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "chrome-mcp-shared",
3 | "version": "1.0.1",
4 | "author": "hangye",
5 | "main": "dist/index.js",
6 | "module": "./dist/index.mjs",
7 | "types": "dist/index.d.ts",
8 | "exports": {
9 | ".": {
10 | "import": {
11 | "types": "./dist/index.d.ts",
12 | "default": "./dist/index.mjs"
13 | },
14 | "require": {
15 | "types": "./dist/index.d.ts",
16 | "default": "./dist/index.js"
17 | }
18 | }
19 | },
20 | "scripts": {
21 | "build": "tsup src/index.ts --format cjs,esm --dts --clean",
22 | "dev": "tsup src/index.ts --format cjs,esm --dts --watch",
23 | "lint": "npx eslint 'src/**/*.{js,ts}'",
24 | "lint:fix": "npx eslint 'src/**/*.{js,ts}' --fix",
25 | "format": "prettier --write 'src/**/*.{js,ts,json}'"
26 | },
27 | "files": [
28 | "dist"
29 | ],
30 | "devDependencies": {
31 | "@types/node": "^18.0.0",
32 | "@typescript-eslint/parser": "^8.32.0",
33 | "tsup": "^8.4.0"
34 | },
35 | "dependencies": {
36 | "@modelcontextprotocol/sdk": "^1.11.0",
37 | "zod": "^3.24.4"
38 | }
39 | }
40 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export { navigateTool, closeTabsTool, goBackOrForwardTool, switchTabTool } from './common';
2 | export { windowTool } from './window';
3 | export { vectorSearchTabsContentTool as searchTabsContentTool } from './vector-search';
4 | export { screenshotTool } from './screenshot';
5 | export { webFetcherTool, getInteractiveElementsTool } from './web-fetcher';
6 | export { clickTool, fillTool } from './interaction';
7 | export { networkRequestTool } from './network-request';
8 | export { networkDebuggerStartTool, networkDebuggerStopTool } from './network-capture-debugger';
9 | export { networkCaptureStartTool, networkCaptureStopTool } from './network-capture-web-request';
10 | export { keyboardTool } from './keyboard';
11 | export { historyTool } from './history';
12 | export { bookmarkSearchTool, bookmarkAddTool, bookmarkDeleteTool } from './bookmark';
13 | export { injectScriptTool, sendCommandToInjectScriptTool } from './inject-script';
14 | export { consoleTool } from './console';
15 | export { fileUploadTool } from './file-upload';
16 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "chrome-mcp-server",
3 | "description": "a chrome extension to use your own chrome as a mcp server",
4 | "author": "hangye",
5 | "private": true,
6 | "version": "0.0.6",
7 | "type": "module",
8 | "scripts": {
9 | "dev": "wxt",
10 | "dev:firefox": "wxt -b firefox",
11 | "build": "wxt build",
12 | "build:firefox": "wxt build -b firefox",
13 | "zip": "wxt zip",
14 | "zip:firefox": "wxt zip -b firefox",
15 | "compile": "vue-tsc --noEmit",
16 | "postinstall": "wxt prepare",
17 | "lint": "npx eslint .",
18 | "lint:fix": "npx eslint . --fix",
19 | "format": "npx prettier --write .",
20 | "format:check": "npx prettier --check ."
21 | },
22 | "dependencies": {
23 | "@modelcontextprotocol/sdk": "^1.11.0",
24 | "@xenova/transformers": "^2.17.2",
25 | "chrome-mcp-shared": "workspace:*",
26 | "date-fns": "^4.1.0",
27 | "hnswlib-wasm-static": "0.8.5",
28 | "vue": "^3.5.13",
29 | "zod": "^3.24.4"
30 | },
31 | "devDependencies": {
32 | "@types/chrome": "^0.0.318",
33 | "@wxt-dev/module-vue": "^1.0.2",
34 | "dotenv": "^16.5.0",
35 | "vite-plugin-static-copy": "^3.0.0",
36 | "vue-tsc": "^2.2.8",
37 | "wxt": "^0.20.0"
38 | }
39 | }
40 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { initNativeHostListener } from './native-host';
2 | import {
3 | initSemanticSimilarityListener,
4 | initializeSemanticEngineIfCached,
5 | } from './semantic-similarity';
6 | import { initStorageManagerListener } from './storage-manager';
7 | import { cleanupModelCache } from '@/utils/semantic-similarity-engine';
8 |
9 | /**
10 | * Background script entry point
11 | * Initializes all background services and listeners
12 | */
13 | export default defineBackground(() => {
14 | // Initialize core services
15 | initNativeHostListener();
16 | initSemanticSimilarityListener();
17 | initStorageManagerListener();
18 |
19 | // Conditionally initialize semantic similarity engine if model cache exists
20 | initializeSemanticEngineIfCached()
21 | .then((initialized) => {
22 | if (initialized) {
23 | console.log('Background: Semantic similarity engine initialized from cache');
24 | } else {
25 | console.log(
26 | 'Background: Semantic similarity engine initialization skipped (no cache found)',
27 | );
28 | }
29 | })
30 | .catch((error) => {
31 | console.warn('Background: Failed to conditionally initialize semantic engine:', error);
32 | });
33 |
34 | // Initial cleanup on startup
35 | cleanupModelCache().catch((error) => {
36 | console.warn('Background: Initial cache cleanup failed:', error);
37 | });
38 | });
39 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import js from '@eslint/js';
2 | import globals from 'globals';
3 | import tseslint from 'typescript-eslint';
4 | import pluginVue from 'eslint-plugin-vue';
5 | import { defineConfig } from 'eslint/config';
6 | import prettierConfig from 'eslint-config-prettier';
7 |
8 | export default defineConfig([
9 | // Global ignores - these apply to all configurations
10 | {
11 | ignores: [
12 | 'dist/**',
13 | '.output/**',
14 | '.wxt/**',
15 | 'node_modules/**',
16 | 'logs/**',
17 | '*.log',
18 | '.cache/**',
19 | '.temp/**',
20 | '.vscode/**',
21 | '!.vscode/extensions.json',
22 | '.idea/**',
23 | '.DS_Store',
24 | 'Thumbs.db',
25 | '*.zip',
26 | '*.tar.gz',
27 | 'stats.html',
28 | 'stats-*.json',
29 | 'libs/**',
30 | 'workers/**',
31 | 'public/libs/**',
32 | ],
33 | },
34 | js.configs.recommended,
35 | {
36 | files: ['**/*.{js,mjs,cjs,ts,vue}'],
37 | languageOptions: { globals: globals.browser },
38 | },
39 | ...tseslint.configs.recommended,
40 | {
41 | rules: {
42 | '@typescript-eslint/no-explicit-any': 'off',
43 | '@typescript-eslint/no-unused-vars': 'off',
44 | },
45 | },
46 | pluginVue.configs['flat/essential'],
47 | { files: ['**/*.vue'], languageOptions: { parserOptions: { parser: tseslint.parser } } },
48 | // Prettier configuration - must be placed last to override previous rules
49 | prettierConfig,
50 | ]);
51 |
```
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
```javascript
1 | import globals from 'globals';
2 | import js from '@eslint/js';
3 | import tseslint from 'typescript-eslint';
4 | import eslintConfigPrettier from 'eslint-config-prettier';
5 |
6 | export default tseslint.config(
7 | // Global ignores first - these apply to all configurations
8 | {
9 | ignores: [
10 | 'node_modules/',
11 | 'dist/',
12 | '.output/',
13 | '.wxt/',
14 | 'logs/',
15 | '*.log',
16 | '.cache/',
17 | '.temp/',
18 | '.idea/',
19 | '.DS_Store',
20 | 'Thumbs.db',
21 | '*.zip',
22 | '*.tar.gz',
23 | 'stats.html',
24 | 'stats-*.json',
25 | 'pnpm-lock.yaml',
26 | '**/workers/**',
27 | 'app/**/workers/**',
28 | 'packages/**/workers/**',
29 | 'test-inject-script.js',
30 | ],
31 | },
32 |
33 | js.configs.recommended,
34 | ...tseslint.configs.recommended,
35 | {
36 | files: ['app/**/*.{js,jsx,ts,tsx}', 'packages/**/*.{js,jsx,ts,tsx}'],
37 | ignores: ['**/workers/**'], // Additional ignores for this specific config
38 | languageOptions: {
39 | ecmaVersion: 2021,
40 | sourceType: 'module',
41 | parser: tseslint.parser,
42 | globals: {
43 | ...globals.node,
44 | ...globals.es2021,
45 | },
46 | },
47 |
48 | rules: {
49 | '@typescript-eslint/no-explicit-any': 'off',
50 | '@typescript-eslint/no-require-imports': 'off',
51 | '@typescript-eslint/no-unused-vars': 'off',
52 | },
53 | },
54 | eslintConfigPrettier,
55 | );
56 |
```
--------------------------------------------------------------------------------
/app/native-server/src/constant/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | export enum NATIVE_MESSAGE_TYPE {
2 | START = 'start',
3 | STARTED = 'started',
4 | STOP = 'stop',
5 | STOPPED = 'stopped',
6 | PING = 'ping',
7 | PONG = 'pong',
8 | ERROR = 'error',
9 | }
10 |
11 | export const NATIVE_SERVER_PORT = 56889;
12 |
13 | // Timeout constants (in milliseconds)
14 | export const TIMEOUTS = {
15 | DEFAULT_REQUEST_TIMEOUT: 15000,
16 | EXTENSION_REQUEST_TIMEOUT: 20000,
17 | PROCESS_DATA_TIMEOUT: 20000,
18 | } as const;
19 |
20 | // Server configuration
21 | export const SERVER_CONFIG = {
22 | HOST: '127.0.0.1',
23 | CORS_ORIGIN: true,
24 | LOGGER_ENABLED: false,
25 | } as const;
26 |
27 | // HTTP Status codes
28 | export const HTTP_STATUS = {
29 | OK: 200,
30 | NO_CONTENT: 204,
31 | BAD_REQUEST: 400,
32 | INTERNAL_SERVER_ERROR: 500,
33 | GATEWAY_TIMEOUT: 504,
34 | } as const;
35 |
36 | // Error messages
37 | export const ERROR_MESSAGES = {
38 | NATIVE_HOST_NOT_AVAILABLE: 'Native host connection not established.',
39 | SERVER_NOT_RUNNING: 'Server is not actively running.',
40 | REQUEST_TIMEOUT: 'Request to extension timed out.',
41 | INVALID_MCP_REQUEST: 'Invalid MCP request or session.',
42 | INVALID_SESSION_ID: 'Invalid or missing MCP session ID.',
43 | INTERNAL_SERVER_ERROR: 'Internal Server Error',
44 | MCP_SESSION_DELETION_ERROR: 'Internal server error during MCP session deletion.',
45 | MCP_REQUEST_PROCESSING_ERROR: 'Internal server error during MCP request processing.',
46 | INVALID_SSE_SESSION: 'Invalid or missing MCP session ID for SSE.',
47 | } as const;
48 |
```
--------------------------------------------------------------------------------
/app/native-server/src/mcp/register-tools.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2 | import {
3 | CallToolRequestSchema,
4 | CallToolResult,
5 | ListToolsRequestSchema,
6 | } from '@modelcontextprotocol/sdk/types.js';
7 | import nativeMessagingHostInstance from '../native-messaging-host';
8 | import { NativeMessageType, TOOL_SCHEMAS } from 'chrome-mcp-shared';
9 |
10 | export const setupTools = (server: Server) => {
11 | // List tools handler
12 | server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }));
13 |
14 | // Call tool handler
15 | server.setRequestHandler(CallToolRequestSchema, async (request) =>
16 | handleToolCall(request.params.name, request.params.arguments || {}),
17 | );
18 | };
19 |
20 | const handleToolCall = async (name: string, args: any): Promise<CallToolResult> => {
21 | try {
22 | // 发送请求到Chrome扩展并等待响应
23 | const response = await nativeMessagingHostInstance.sendRequestToExtensionAndWait(
24 | {
25 | name,
26 | args,
27 | },
28 | NativeMessageType.CALL_TOOL,
29 | 30000, // 30秒超时
30 | );
31 | if (response.status === 'success') {
32 | return response.data;
33 | } else {
34 | return {
35 | content: [
36 | {
37 | type: 'text',
38 | text: `Error calling tool: ${response.error}`,
39 | },
40 | ],
41 | isError: true,
42 | };
43 | }
44 | } catch (error: any) {
45 | return {
46 | content: [
47 | {
48 | type: 'text',
49 | text: `Error calling tool: ${error.message}`,
50 | },
51 | ],
52 | isError: true,
53 | };
54 | }
55 | };
56 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/window.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createErrorResponse, ToolResult } from '@/common/tool-handler';
2 | import { BaseBrowserToolExecutor } from '../base-browser';
3 | import { TOOL_NAMES } from 'chrome-mcp-shared';
4 |
5 | class WindowTool extends BaseBrowserToolExecutor {
6 | name = TOOL_NAMES.BROWSER.GET_WINDOWS_AND_TABS;
7 | async execute(): Promise<ToolResult> {
8 | try {
9 | const windows = await chrome.windows.getAll({ populate: true });
10 | let tabCount = 0;
11 |
12 | const structuredWindows = windows.map((window) => {
13 | const tabs =
14 | window.tabs?.map((tab) => {
15 | tabCount++;
16 | return {
17 | tabId: tab.id || 0,
18 | url: tab.url || '',
19 | title: tab.title || '',
20 | active: tab.active || false,
21 | };
22 | }) || [];
23 |
24 | return {
25 | windowId: window.id || 0,
26 | tabs: tabs,
27 | };
28 | });
29 |
30 | const result = {
31 | windowCount: windows.length,
32 | tabCount: tabCount,
33 | windows: structuredWindows,
34 | };
35 |
36 | return {
37 | content: [
38 | {
39 | type: 'text',
40 | text: JSON.stringify(result),
41 | },
42 | ],
43 | isError: false,
44 | };
45 | } catch (error) {
46 | console.error('Error in WindowTool.execute:', error);
47 | return createErrorResponse(
48 | `Error getting windows and tabs information: ${error instanceof Error ? error.message : String(error)}`,
49 | );
50 | }
51 | }
52 | }
53 |
54 | export const windowTool = new WindowTool();
55 |
```
--------------------------------------------------------------------------------
/docs/TROUBLESHOOTING.md:
--------------------------------------------------------------------------------
```markdown
1 | # 🚀 Installation and Connection Issues
2 |
3 | ### If Connection Fails After Clicking the Connect Button on the Extension
4 |
5 | 1. **Check if mcp-chrome-bridge is installed successfully**, ensure it's globally installed
6 |
7 | ```bash
8 | mcp-chrome-bridge -V
9 | ```
10 |
11 | <img width="612" alt="Screenshot 2025-06-11 15 09 57" src="https://github.com/user-attachments/assets/59458532-e6e1-457c-8c82-3756a5dbb28e" />
12 |
13 | 2. **Check if the manifest file is in the correct directory**
14 |
15 | Windows path: C:\Users\xxx\AppData\Roaming\Google\Chrome\NativeMessagingHosts
16 |
17 | Mac path: /Users/xxx/Library/Application\ Support/Google/Chrome/NativeMessagingHosts
18 |
19 | If the npm package is installed correctly, a file named `com.chromemcp.nativehost.json` should be generated in this directory
20 |
21 | 3. **Check if there are logs in the npm package installation directory**
22 | You need to check your installation path (if unclear, open the manifest file in step 2, the path field shows the installation directory). For example, if the installation path is as follows, check the log contents:
23 |
24 | C:\Users\admin\AppData\Local\nvm\v20.19.2\node_modules\mcp-chrome-bridge\dist\logs
25 |
26 | <img width="804" alt="Screenshot 2025-06-11 15 09 41" src="https://github.com/user-attachments/assets/ce7b7c94-7c84-409a-8210-c9317823aae1" />
27 |
28 | 4. **Check if you have execution permissions**
29 | You need to check your installation path (if unclear, open the manifest file in step 2, the path field shows the installation directory). For example, if the Mac installation path is as follows:
30 |
31 | `xxx/node_modules/mcp-chrome-bridge/dist/run_host.sh`
32 |
33 | Check if this script has execution permissions
34 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/ProgressIndicator.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <div v-if="visible" class="progress-section">
3 | <div class="progress-indicator">
4 | <div class="spinner" v-if="showSpinner"></div>
5 | <span class="progress-text">{{ text }}</span>
6 | </div>
7 | </div>
8 | </template>
9 |
10 | <script lang="ts" setup>
11 | interface Props {
12 | visible?: boolean;
13 | text: string;
14 | showSpinner?: boolean;
15 | }
16 |
17 | withDefaults(defineProps<Props>(), {
18 | visible: true,
19 | showSpinner: true,
20 | });
21 | </script>
22 |
23 | <style scoped>
24 | .progress-section {
25 | margin-top: 16px;
26 | animation: slideIn 0.3s ease-out;
27 | }
28 |
29 | .progress-indicator {
30 | display: flex;
31 | align-items: center;
32 | gap: 12px;
33 | padding: 16px;
34 | background: linear-gradient(135deg, rgba(102, 126, 234, 0.1), rgba(118, 75, 162, 0.1));
35 | border-radius: 8px;
36 | border-left: 4px solid #667eea;
37 | backdrop-filter: blur(10px);
38 | border: 1px solid rgba(102, 126, 234, 0.2);
39 | }
40 |
41 | .spinner {
42 | width: 20px;
43 | height: 20px;
44 | border: 3px solid rgba(102, 126, 234, 0.2);
45 | border-top: 3px solid #667eea;
46 | border-radius: 50%;
47 | animation: spin 1s linear infinite;
48 | flex-shrink: 0;
49 | }
50 |
51 | @keyframes spin {
52 | 0% {
53 | transform: rotate(0deg);
54 | }
55 | 100% {
56 | transform: rotate(360deg);
57 | }
58 | }
59 |
60 | .progress-text {
61 | font-size: 14px;
62 | color: #4a5568;
63 | font-weight: 500;
64 | line-height: 1.4;
65 | }
66 |
67 | @keyframes slideIn {
68 | from {
69 | opacity: 0;
70 | transform: translateY(10px);
71 | }
72 | to {
73 | opacity: 1;
74 | transform: translateY(0);
75 | }
76 | }
77 |
78 | /* 响应式设计 */
79 | @media (max-width: 420px) {
80 | .progress-indicator {
81 | padding: 12px;
82 | gap: 8px;
83 | }
84 |
85 | .spinner {
86 | width: 16px;
87 | height: 16px;
88 | border-width: 2px;
89 | }
90 |
91 | .progress-text {
92 | font-size: 13px;
93 | }
94 | }
95 | </style>
96 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-chrome-bridge-monorepo",
3 | "version": "1.0.0",
4 | "private": true,
5 | "author": "hangye",
6 | "type": "module",
7 | "scripts": {
8 | "build:shared": "pnpm --filter chrome-mcp-shared build",
9 | "build:native": "pnpm --filter mcp-chrome-bridge build",
10 | "build:extension": "pnpm --filter chrome-mcp-server build",
11 | "build:wasm": "pnpm --filter @chrome-mcp/wasm-simd build && pnpm run copy:wasm",
12 | "build": "pnpm -r --filter='!@chrome-mcp/wasm-simd' build",
13 | "copy:wasm": "cp ./packages/wasm-simd/pkg/simd_math.js ./packages/wasm-simd/pkg/simd_math_bg.wasm ./app/chrome-extension/workers/",
14 | "dev:shared": "pnpm --filter chrome-mcp-shared dev",
15 | "dev:native": "pnpm --filter mcp-chrome-bridge dev",
16 | "dev:extension": "pnpm --filter chrome-mcp-server dev",
17 | "dev": "pnpm --filter chrome-mcp-shared build && pnpm -r --parallel dev",
18 | "lint": "pnpm -r lint",
19 | "lint:fix": "pnpm -r lint:fix",
20 | "format": "pnpm -r format",
21 | "clean:dist": "pnpm -r exec rm -rf dist .turbo",
22 | "clean:modules": "pnpm -r exec rm -rf node_modules && rm -rf node_modules",
23 | "clean": "npm run clean:dist && npm run clean:modules",
24 | "typecheck": "pnpm -r exec tsc --noEmit",
25 | "prepare": "husky"
26 | },
27 | "devDependencies": {
28 | "@commitlint/cli": "^19.8.1",
29 | "@commitlint/config-conventional": "^19.8.1",
30 | "@eslint/js": "^9.25.1",
31 | "@typescript-eslint/eslint-plugin": "^8.32.0",
32 | "@typescript-eslint/parser": "^8.32.0",
33 | "eslint": "^9.26.0",
34 | "eslint-config-prettier": "^10.1.5",
35 | "eslint-plugin-vue": "^10.0.0",
36 | "globals": "^16.1.0",
37 | "husky": "^9.1.7",
38 | "lint-staged": "^15.5.1",
39 | "prettier": "^3.5.3",
40 | "typescript": "^5.8.3",
41 | "typescript-eslint": "^8.32.0",
42 | "vue-eslint-parser": "^10.1.3"
43 | },
44 | "lint-staged": {
45 | "**/*.{js,jsx,ts,tsx,vue}": [
46 | "eslint --fix",
47 | "prettier --write"
48 | ],
49 | "**/*.{json,md,yaml,html,css}": [
50 | "prettier --write"
51 | ]
52 | }
53 | }
54 |
```
--------------------------------------------------------------------------------
/app/native-server/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "mcp-chrome-bridge",
3 | "version": "1.0.29",
4 | "description": "Chrome Native-Messaging host (Node)",
5 | "main": "dist/index.js",
6 | "bin": {
7 | "mcp-chrome-bridge": "./dist/cli.js",
8 | "mcp-chrome-stdio": "./dist/mcp/mcp-server-stdio.js"
9 | },
10 | "scripts": {
11 | "dev": "nodemon --watch src --ext ts,js,json --ignore dist/ --exec \"npm run build && npm run register:dev\"",
12 | "build": "ts-node src/scripts/build.ts",
13 | "test": "jest",
14 | "test:watch": "jest --watch",
15 | "lint": "eslint 'src/**/*.{js,ts}'",
16 | "lint:fix": "eslint 'src/**/*.{js,ts}' --fix",
17 | "format": "prettier --write 'src/**/*.{js,ts,json}'",
18 | "register:dev": "node dist/scripts/register-dev.js",
19 | "postinstall": "node dist/scripts/postinstall.js"
20 | },
21 | "files": [
22 | "dist"
23 | ],
24 | "engines": {
25 | "node": ">=14.0.0"
26 | },
27 | "preferGlobal": true,
28 | "keywords": [
29 | "mcp",
30 | "chrome",
31 | "browser"
32 | ],
33 | "author": "hangye",
34 | "license": "MIT",
35 | "dependencies": {
36 | "@fastify/cors": "^11.0.1",
37 | "@modelcontextprotocol/sdk": "^1.11.0",
38 | "@types/node-fetch": "^2.6.13",
39 | "chalk": "^5.4.1",
40 | "chrome-mcp-shared": "workspace:*",
41 | "commander": "^13.1.0",
42 | "fastify": "^5.3.2",
43 | "is-admin": "^4.0.0",
44 | "node-fetch": "^2.7.0",
45 | "pino": "^9.6.0",
46 | "uuid": "^11.1.0"
47 | },
48 | "devDependencies": {
49 | "@jest/globals": "^29.7.0",
50 | "@types/chrome": "^0.0.318",
51 | "@types/jest": "^29.5.14",
52 | "@types/node": "^22.15.3",
53 | "@types/supertest": "^6.0.3",
54 | "@typescript-eslint/parser": "^8.31.1",
55 | "cross-env": "^7.0.3",
56 | "husky": "^9.1.7",
57 | "jest": "^29.7.0",
58 | "lint-staged": "^15.5.1",
59 | "nodemon": "^3.1.10",
60 | "pino-pretty": "^13.0.0",
61 | "rimraf": "^6.0.1",
62 | "supertest": "^7.1.0",
63 | "ts-jest": "^29.3.2",
64 | "ts-node": "^10.9.2"
65 | },
66 | "husky": {
67 | "hooks": {
68 | "pre-commit": "lint-staged"
69 | }
70 | },
71 | "lint-staged": {
72 | "*.{js,ts}": [
73 | "eslint --fix",
74 | "prettier --write"
75 | ],
76 | "*.{json,md}": [
77 | "prettier --write"
78 | ]
79 | }
80 | }
81 |
```
--------------------------------------------------------------------------------
/.github/workflows/build-release.yml:
--------------------------------------------------------------------------------
```yaml
1 | # name: Build and Release Chrome Extension
2 |
3 | # on:
4 | # push:
5 | # branches: [ master, develop ]
6 | # paths:
7 | # - 'app/chrome-extension/**'
8 | # pull_request:
9 | # branches: [ master ]
10 | # paths:
11 | # - 'app/chrome-extension/**'
12 | # workflow_dispatch:
13 |
14 | # jobs:
15 | # build-extension:
16 | # runs-on: ubuntu-latest
17 |
18 | # steps:
19 | # - name: Checkout code
20 | # uses: actions/checkout@v4
21 |
22 | # - name: Setup Node.js
23 | # uses: actions/setup-node@v4
24 | # with:
25 | # node-version: '18'
26 | # cache: 'npm'
27 | # cache-dependency-path: 'app/chrome-extension/package-lock.json'
28 |
29 | # - name: Install dependencies
30 | # run: |
31 | # cd app/chrome-extension
32 | # npm ci
33 |
34 | # - name: Build extension
35 | # run: |
36 | # cd app/chrome-extension
37 | # npm run build
38 |
39 | # - name: Create zip package
40 | # run: |
41 | # cd app/chrome-extension
42 | # npm run zip
43 |
44 | # - name: Prepare release directory
45 | # run: |
46 | # mkdir -p releases/chrome-extension/latest
47 | # mkdir -p releases/chrome-extension/$(date +%Y%m%d-%H%M%S)
48 |
49 | # - name: Copy release files
50 | # run: |
51 | # # Copy to latest
52 | # cp app/chrome-extension/.output/chrome-mv3-prod.zip releases/chrome-extension/latest/chrome-mcp-server-latest.zip
53 |
54 | # # Copy to timestamped version
55 | # TIMESTAMP=$(date +%Y%m%d-%H%M%S)
56 | # cp app/chrome-extension/.output/chrome-mv3-prod.zip releases/chrome-extension/$TIMESTAMP/chrome-mcp-server-$TIMESTAMP.zip
57 |
58 | # - name: Upload build artifacts
59 | # uses: actions/upload-artifact@v4
60 | # with:
61 | # name: chrome-extension-build
62 | # path: releases/chrome-extension/
63 | # retention-days: 30
64 |
65 | # - name: Commit and push releases (if on main branch)
66 | # if: github.ref == 'refs/heads/main'
67 | # run: |
68 | # git config --local user.email "[email protected]"
69 | # git config --local user.name "GitHub Action"
70 | # git add releases/
71 | # git diff --staged --quiet || git commit -m "Auto-build: Update Chrome extension release [skip ci]"
72 | # git push
73 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/inject-scripts/inject-bridge.js:
--------------------------------------------------------------------------------
```javascript
1 | /* eslint-disable */
2 |
3 | (() => {
4 | // Prevent duplicate injection of the bridge itself.
5 | if (window.__INJECT_SCRIPT_TOOL_UNIVERSAL_BRIDGE_LOADED__) return;
6 | window.__INJECT_SCRIPT_TOOL_UNIVERSAL_BRIDGE_LOADED__ = true;
7 | const EVENT_NAME = {
8 | RESPONSE: 'chrome-mcp:response',
9 | CLEANUP: 'chrome-mcp:cleanup',
10 | EXECUTE: 'chrome-mcp:execute',
11 | };
12 | const pendingRequests = new Map();
13 |
14 | const messageHandler = (request, _sender, sendResponse) => {
15 | // --- Lifecycle Command ---
16 | if (request.type === EVENT_NAME.CLEANUP) {
17 | window.dispatchEvent(new CustomEvent(EVENT_NAME.CLEANUP));
18 | // Acknowledge cleanup signal received, but don't hold the connection.
19 | sendResponse({ success: true });
20 | return true;
21 | }
22 |
23 | // --- Execution Command for MAIN world ---
24 | if (request.targetWorld === 'MAIN') {
25 | const requestId = `req-${Date.now()}-${Math.random()}`;
26 | pendingRequests.set(requestId, sendResponse);
27 |
28 | window.dispatchEvent(
29 | new CustomEvent(EVENT_NAME.EXECUTE, {
30 | detail: {
31 | action: request.action,
32 | payload: request.payload,
33 | requestId: requestId,
34 | },
35 | }),
36 | );
37 | return true; // Async response is expected.
38 | }
39 | // Note: Requests for ISOLATED world are handled by the user's isolatedWorldCode script directly.
40 | // This listener won't process them unless it's the only script in ISOLATED world.
41 | };
42 |
43 | chrome.runtime.onMessage.addListener(messageHandler);
44 |
45 | // Listen for responses coming back from the MAIN world.
46 | const responseHandler = (event) => {
47 | const { requestId, data, error } = event.detail;
48 | if (pendingRequests.has(requestId)) {
49 | const sendResponse = pendingRequests.get(requestId);
50 | sendResponse({ data, error });
51 | pendingRequests.delete(requestId);
52 | }
53 | };
54 | window.addEventListener(EVENT_NAME.RESPONSE, responseHandler);
55 |
56 | // --- Self Cleanup ---
57 | // When the cleanup signal arrives, this bridge must also clean itself up.
58 | const cleanupHandler = () => {
59 | chrome.runtime.onMessage.removeListener(messageHandler);
60 | window.removeEventListener(EVENT_NAME.RESPONSE, responseHandler);
61 | window.removeEventListener(EVENT_NAME.CLEANUP, cleanupHandler);
62 | delete window.__INJECT_SCRIPT_TOOL_UNIVERSAL_BRIDGE_LOADED__;
63 | };
64 | window.addEventListener(EVENT_NAME.CLEANUP, cleanupHandler);
65 | })();
66 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/keyboard.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createErrorResponse, ToolResult } from '@/common/tool-handler';
2 | import { BaseBrowserToolExecutor } from '../base-browser';
3 | import { TOOL_NAMES } from 'chrome-mcp-shared';
4 | import { TOOL_MESSAGE_TYPES } from '@/common/message-types';
5 | import { TIMEOUTS, ERROR_MESSAGES } from '@/common/constants';
6 |
7 | interface KeyboardToolParams {
8 | keys: string; // Required: string representing keys or key combinations to simulate (e.g., "Enter", "Ctrl+C")
9 | selector?: string; // Optional: CSS selector for target element to send keyboard events to
10 | delay?: number; // Optional: delay between keystrokes in milliseconds
11 | }
12 |
13 | /**
14 | * Tool for simulating keyboard input on web pages
15 | */
16 | class KeyboardTool extends BaseBrowserToolExecutor {
17 | name = TOOL_NAMES.BROWSER.KEYBOARD;
18 |
19 | /**
20 | * Execute keyboard operation
21 | */
22 | async execute(args: KeyboardToolParams): Promise<ToolResult> {
23 | const { keys, selector, delay = TIMEOUTS.KEYBOARD_DELAY } = args;
24 |
25 | console.log(`Starting keyboard operation with options:`, args);
26 |
27 | if (!keys) {
28 | return createErrorResponse(
29 | ERROR_MESSAGES.INVALID_PARAMETERS + ': Keys parameter must be provided',
30 | );
31 | }
32 |
33 | try {
34 | // Get current tab
35 | const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
36 | if (!tabs[0]) {
37 | return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND);
38 | }
39 |
40 | const tab = tabs[0];
41 | if (!tab.id) {
42 | return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND + ': Active tab has no ID');
43 | }
44 |
45 | await this.injectContentScript(tab.id, ['inject-scripts/keyboard-helper.js']);
46 |
47 | // Send keyboard simulation message to content script
48 | const result = await this.sendMessageToTab(tab.id, {
49 | action: TOOL_MESSAGE_TYPES.SIMULATE_KEYBOARD,
50 | keys,
51 | selector,
52 | delay,
53 | });
54 |
55 | if (result.error) {
56 | return createErrorResponse(result.error);
57 | }
58 |
59 | return {
60 | content: [
61 | {
62 | type: 'text',
63 | text: JSON.stringify({
64 | success: true,
65 | message: result.message || 'Keyboard operation successful',
66 | targetElement: result.targetElement,
67 | results: result.results,
68 | }),
69 | },
70 | ],
71 | isError: false,
72 | };
73 | } catch (error) {
74 | console.error('Error in keyboard operation:', error);
75 | return createErrorResponse(
76 | `Error simulating keyboard events: ${error instanceof Error ? error.message : String(error)}`,
77 | );
78 | }
79 | }
80 | }
81 |
82 | export const keyboardTool = new KeyboardTool();
83 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/network-request.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createErrorResponse, ToolResult } from '@/common/tool-handler';
2 | import { BaseBrowserToolExecutor } from '../base-browser';
3 | import { TOOL_NAMES } from 'chrome-mcp-shared';
4 | import { TOOL_MESSAGE_TYPES } from '@/common/message-types';
5 |
6 | const DEFAULT_NETWORK_REQUEST_TIMEOUT = 30000; // For sending a single request via content script
7 |
8 | interface NetworkRequestToolParams {
9 | url: string; // URL is always required
10 | method?: string; // Defaults to GET
11 | headers?: Record<string, string>; // User-provided headers
12 | body?: any; // User-provided body
13 | timeout?: number; // Timeout for the network request itself
14 | }
15 |
16 | /**
17 | * NetworkRequestTool - Sends network requests based on provided parameters.
18 | */
19 | class NetworkRequestTool extends BaseBrowserToolExecutor {
20 | name = TOOL_NAMES.BROWSER.NETWORK_REQUEST;
21 |
22 | async execute(args: NetworkRequestToolParams): Promise<ToolResult> {
23 | const {
24 | url,
25 | method = 'GET',
26 | headers = {},
27 | body,
28 | timeout = DEFAULT_NETWORK_REQUEST_TIMEOUT,
29 | } = args;
30 |
31 | console.log(`NetworkRequestTool: Executing with options:`, args);
32 |
33 | if (!url) {
34 | return createErrorResponse('URL parameter is required.');
35 | }
36 |
37 | try {
38 | const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
39 | if (!tabs[0]?.id) {
40 | return createErrorResponse('No active tab found or tab has no ID.');
41 | }
42 | const activeTabId = tabs[0].id;
43 |
44 | // Ensure content script is available in the target tab
45 | await this.injectContentScript(activeTabId, ['inject-scripts/network-helper.js']);
46 |
47 | console.log(
48 | `NetworkRequestTool: Sending to content script: URL=${url}, Method=${method}, Headers=${Object.keys(headers).join(',')}, BodyType=${typeof body}`,
49 | );
50 |
51 | const resultFromContentScript = await this.sendMessageToTab(activeTabId, {
52 | action: TOOL_MESSAGE_TYPES.NETWORK_SEND_REQUEST,
53 | url: url,
54 | method: method,
55 | headers: headers,
56 | body: body,
57 | timeout: timeout,
58 | });
59 |
60 | console.log(`NetworkRequestTool: Response from content script:`, resultFromContentScript);
61 |
62 | return {
63 | content: [
64 | {
65 | type: 'text',
66 | text: JSON.stringify(resultFromContentScript),
67 | },
68 | ],
69 | isError: !resultFromContentScript?.success,
70 | };
71 | } catch (error: any) {
72 | console.error('NetworkRequestTool: Error sending network request:', error);
73 | return createErrorResponse(
74 | `Error sending network request: ${error.message || String(error)}`,
75 | );
76 | }
77 | }
78 | }
79 |
80 | export const networkRequestTool = new NetworkRequestTool();
81 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/utils/offscreen-manager.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Offscreen Document manager
3 | * Ensures only one offscreen document is created across the entire extension to avoid conflicts
4 | */
5 |
6 | export class OffscreenManager {
7 | private static instance: OffscreenManager | null = null;
8 | private isCreated = false;
9 | private isCreating = false;
10 | private createPromise: Promise<void> | null = null;
11 |
12 | private constructor() {}
13 |
14 | /**
15 | * Get singleton instance
16 | */
17 | public static getInstance(): OffscreenManager {
18 | if (!OffscreenManager.instance) {
19 | OffscreenManager.instance = new OffscreenManager();
20 | }
21 | return OffscreenManager.instance;
22 | }
23 |
24 | /**
25 | * Ensure offscreen document exists
26 | */
27 | public async ensureOffscreenDocument(): Promise<void> {
28 | if (this.isCreated) {
29 | return;
30 | }
31 |
32 | if (this.isCreating && this.createPromise) {
33 | return this.createPromise;
34 | }
35 |
36 | this.isCreating = true;
37 | this.createPromise = this._doCreateOffscreenDocument().finally(() => {
38 | this.isCreating = false;
39 | });
40 |
41 | return this.createPromise;
42 | }
43 |
44 | private async _doCreateOffscreenDocument(): Promise<void> {
45 | try {
46 | if (!chrome.offscreen) {
47 | throw new Error('Offscreen API not available. Chrome 109+ required.');
48 | }
49 |
50 | const existingContexts = await (chrome.runtime as any).getContexts({
51 | contextTypes: ['OFFSCREEN_DOCUMENT'],
52 | });
53 |
54 | if (existingContexts && existingContexts.length > 0) {
55 | console.log('OffscreenManager: Offscreen document already exists');
56 | this.isCreated = true;
57 | return;
58 | }
59 |
60 | await chrome.offscreen.createDocument({
61 | url: 'offscreen.html',
62 | reasons: ['WORKERS'],
63 | justification: 'Need to run semantic similarity engine with workers',
64 | });
65 |
66 | this.isCreated = true;
67 | console.log('OffscreenManager: Offscreen document created successfully');
68 | } catch (error) {
69 | console.error('OffscreenManager: Failed to create offscreen document:', error);
70 | this.isCreated = false;
71 | throw error;
72 | }
73 | }
74 |
75 | /**
76 | * Check if offscreen document is created
77 | */
78 | public isOffscreenDocumentCreated(): boolean {
79 | return this.isCreated;
80 | }
81 |
82 | /**
83 | * Close offscreen document
84 | */
85 | public async closeOffscreenDocument(): Promise<void> {
86 | try {
87 | if (chrome.offscreen && this.isCreated) {
88 | await chrome.offscreen.closeDocument();
89 | this.isCreated = false;
90 | console.log('OffscreenManager: Offscreen document closed');
91 | }
92 | } catch (error) {
93 | console.error('OffscreenManager: Failed to close offscreen document:', error);
94 | }
95 | }
96 |
97 | /**
98 | * Reset state (for testing)
99 | */
100 | public reset(): void {
101 | this.isCreated = false;
102 | this.isCreating = false;
103 | this.createPromise = null;
104 | }
105 | }
106 |
107 |
108 | export const offscreenManager = OffscreenManager.getInstance();
109 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/base-browser.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { ToolExecutor } from '@/common/tool-handler';
2 | import type { ToolResult } from '@/common/tool-handler';
3 | import { TIMEOUTS, ERROR_MESSAGES } from '@/common/constants';
4 |
5 | const PING_TIMEOUT_MS = 300;
6 |
7 | /**
8 | * Base class for browser tool executors
9 | */
10 | export abstract class BaseBrowserToolExecutor implements ToolExecutor {
11 | abstract name: string;
12 | abstract execute(args: any): Promise<ToolResult>;
13 |
14 | /**
15 | * Inject content script into tab
16 | */
17 | protected async injectContentScript(
18 | tabId: number,
19 | files: string[],
20 | injectImmediately = false,
21 | world: 'MAIN' | 'ISOLATED' = 'ISOLATED',
22 | ): Promise<void> {
23 | console.log(`Injecting ${files.join(', ')} into tab ${tabId}`);
24 |
25 | // check if script is already injected
26 | try {
27 | const response = await Promise.race([
28 | chrome.tabs.sendMessage(tabId, { action: `${this.name}_ping` }),
29 | new Promise((_, reject) =>
30 | setTimeout(
31 | () => reject(new Error(`${this.name} Ping action to tab ${tabId} timed out`)),
32 | PING_TIMEOUT_MS,
33 | ),
34 | ),
35 | ]);
36 |
37 | if (response && response.status === 'pong') {
38 | console.log(
39 | `pong received for action '${this.name}' in tab ${tabId}. Assuming script is active.`,
40 | );
41 | return;
42 | } else {
43 | console.warn(`Unexpected ping response in tab ${tabId}:`, response);
44 | }
45 | } catch (error) {
46 | console.error(
47 | `ping content script failed: ${error instanceof Error ? error.message : String(error)}`,
48 | );
49 | }
50 |
51 | try {
52 | await chrome.scripting.executeScript({
53 | target: { tabId },
54 | files,
55 | injectImmediately,
56 | world,
57 | });
58 | console.log(`'${files.join(', ')}' injection successful for tab ${tabId}`);
59 | } catch (injectionError) {
60 | const errorMessage =
61 | injectionError instanceof Error ? injectionError.message : String(injectionError);
62 | console.error(
63 | `Content script '${files.join(', ')}' injection failed for tab ${tabId}: ${errorMessage}`,
64 | );
65 | throw new Error(
66 | `${ERROR_MESSAGES.TOOL_EXECUTION_FAILED}: Failed to inject content script in tab ${tabId}: ${errorMessage}`,
67 | );
68 | }
69 | }
70 |
71 | /**
72 | * Send message to tab
73 | */
74 | protected async sendMessageToTab(tabId: number, message: any): Promise<any> {
75 | try {
76 | const response = await chrome.tabs.sendMessage(tabId, message);
77 |
78 | if (response && response.error) {
79 | throw new Error(String(response.error));
80 | }
81 |
82 | return response;
83 | } catch (error) {
84 | const errorMessage = error instanceof Error ? error.message : String(error);
85 | console.error(
86 | `Error sending message to tab ${tabId} for action ${message?.action || 'unknown'}: ${errorMessage}`,
87 | );
88 |
89 | if (error instanceof Error) {
90 | throw error;
91 | }
92 | throw new Error(errorMessage);
93 | }
94 | }
95 | }
96 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/common/constants.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Chrome Extension Constants
3 | * Centralized configuration values and magic constants
4 | */
5 |
6 | // Native Host Configuration
7 | export const NATIVE_HOST = {
8 | NAME: 'com.chromemcp.nativehost',
9 | DEFAULT_PORT: 12306,
10 | } as const;
11 |
12 | // Chrome Extension Icons
13 | export const ICONS = {
14 | NOTIFICATION: 'icon/48.png',
15 | } as const;
16 |
17 | // Timeouts and Delays (in milliseconds)
18 | export const TIMEOUTS = {
19 | DEFAULT_WAIT: 1000,
20 | NETWORK_CAPTURE_MAX: 30000,
21 | NETWORK_CAPTURE_IDLE: 3000,
22 | SCREENSHOT_DELAY: 100,
23 | KEYBOARD_DELAY: 50,
24 | CLICK_DELAY: 100,
25 | } as const;
26 |
27 | // Limits and Thresholds
28 | export const LIMITS = {
29 | MAX_NETWORK_REQUESTS: 100,
30 | MAX_SEARCH_RESULTS: 50,
31 | MAX_BOOKMARK_RESULTS: 100,
32 | MAX_HISTORY_RESULTS: 100,
33 | SIMILARITY_THRESHOLD: 0.1,
34 | VECTOR_DIMENSIONS: 384,
35 | } as const;
36 |
37 | // Error Messages
38 | export const ERROR_MESSAGES = {
39 | NATIVE_CONNECTION_FAILED: 'Failed to connect to native host',
40 | NATIVE_DISCONNECTED: 'Native connection disconnected',
41 | SERVER_STATUS_LOAD_FAILED: 'Failed to load server status',
42 | SERVER_STATUS_SAVE_FAILED: 'Failed to save server status',
43 | TOOL_EXECUTION_FAILED: 'Tool execution failed',
44 | INVALID_PARAMETERS: 'Invalid parameters provided',
45 | PERMISSION_DENIED: 'Permission denied',
46 | TAB_NOT_FOUND: 'Tab not found',
47 | ELEMENT_NOT_FOUND: 'Element not found',
48 | NETWORK_ERROR: 'Network error occurred',
49 | } as const;
50 |
51 | // Success Messages
52 | export const SUCCESS_MESSAGES = {
53 | TOOL_EXECUTED: 'Tool executed successfully',
54 | CONNECTION_ESTABLISHED: 'Connection established',
55 | SERVER_STARTED: 'Server started successfully',
56 | SERVER_STOPPED: 'Server stopped successfully',
57 | } as const;
58 |
59 | // File Extensions and MIME Types
60 | export const FILE_TYPES = {
61 | STATIC_EXTENSIONS: [
62 | '.css',
63 | '.js',
64 | '.png',
65 | '.jpg',
66 | '.jpeg',
67 | '.gif',
68 | '.svg',
69 | '.ico',
70 | '.woff',
71 | '.woff2',
72 | '.ttf',
73 | ],
74 | FILTERED_MIME_TYPES: ['text/html', 'text/css', 'text/javascript', 'application/javascript'],
75 | IMAGE_FORMATS: ['png', 'jpeg', 'webp'] as const,
76 | } as const;
77 |
78 | // Network Filtering
79 | export const NETWORK_FILTERS = {
80 | EXCLUDED_DOMAINS: [
81 | 'google-analytics.com',
82 | 'googletagmanager.com',
83 | 'facebook.com',
84 | 'doubleclick.net',
85 | 'googlesyndication.com',
86 | ],
87 | STATIC_RESOURCE_TYPES: ['stylesheet', 'image', 'font', 'media', 'other'],
88 | } as const;
89 |
90 | // Semantic Similarity Configuration
91 | export const SEMANTIC_CONFIG = {
92 | DEFAULT_MODEL: 'sentence-transformers/all-MiniLM-L6-v2',
93 | CHUNK_SIZE: 512,
94 | CHUNK_OVERLAP: 50,
95 | BATCH_SIZE: 32,
96 | CACHE_SIZE: 1000,
97 | } as const;
98 |
99 | // Storage Keys
100 | export const STORAGE_KEYS = {
101 | SERVER_STATUS: 'serverStatus',
102 | SEMANTIC_MODEL: 'selectedModel',
103 | USER_PREFERENCES: 'userPreferences',
104 | VECTOR_INDEX: 'vectorIndex',
105 | } as const;
106 |
107 | // Notification Configuration
108 | export const NOTIFICATIONS = {
109 | PRIORITY: 2,
110 | TYPE: 'basic' as const,
111 | } as const;
112 |
113 | export enum ExecutionWorld {
114 | ISOLATED = 'ISOLATED',
115 | MAIN = 'MAIN',
116 | }
117 |
```
--------------------------------------------------------------------------------
/app/native-server/src/scripts/run_host.bat:
--------------------------------------------------------------------------------
```
1 | @echo off
2 | setlocal enabledelayedexpansion
3 |
4 | REM Setup paths
5 | set "SCRIPT_DIR=%~dp0"
6 | if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
7 | set "LOG_DIR=%SCRIPT_DIR%\logs"
8 | set "NODE_SCRIPT=%SCRIPT_DIR%\index.js"
9 |
10 | if not exist "%LOG_DIR%" md "%LOG_DIR%"
11 |
12 | REM Generate timestamp
13 | for /f %%i in ('powershell -NoProfile -Command "Get-Date -Format 'yyyyMMdd_HHmmss'"') do set "TIMESTAMP=%%i"
14 | set "WRAPPER_LOG=%LOG_DIR%\native_host_wrapper_windows_%TIMESTAMP%.log"
15 | set "STDERR_LOG=%LOG_DIR%\native_host_stderr_windows_%TIMESTAMP%.log"
16 |
17 | REM Initial logging
18 | echo Wrapper script called at %DATE% %TIME% > "%WRAPPER_LOG%"
19 | echo SCRIPT_DIR: %SCRIPT_DIR% >> "%WRAPPER_LOG%"
20 | echo LOG_DIR: %LOG_DIR% >> "%WRAPPER_LOG%"
21 | echo NODE_SCRIPT: %NODE_SCRIPT% >> "%WRAPPER_LOG%"
22 | echo Initial PATH: %PATH% >> "%WRAPPER_LOG%"
23 | echo User: %USERNAME% >> "%WRAPPER_LOG%"
24 | echo Current PWD: %CD% >> "%WRAPPER_LOG%"
25 |
26 | REM Node.js discovery
27 | set "NODE_EXEC="
28 |
29 | REM Priority 1: Installation-time node path
30 | set "NODE_PATH_FILE=%SCRIPT_DIR%\node_path.txt"
31 | echo Checking installation-time node path >> "%WRAPPER_LOG%"
32 | if exist "%NODE_PATH_FILE%" (
33 | set /p EXPECTED_NODE=<"%NODE_PATH_FILE%"
34 | if exist "!EXPECTED_NODE!" (
35 | set "NODE_EXEC=!EXPECTED_NODE!"
36 | echo Found installation-time node at !NODE_EXEC! >> "%WRAPPER_LOG%"
37 | )
38 | )
39 |
40 | REM Priority 1.5: Fallback to relative path
41 | if not defined NODE_EXEC (
42 | set "EXPECTED_NODE=%SCRIPT_DIR%\..\..\..\node.exe"
43 | echo Checking relative path >> "%WRAPPER_LOG%"
44 | if exist "%EXPECTED_NODE%" (
45 | set "NODE_EXEC=%EXPECTED_NODE%"
46 | echo Found node at relative path: !NODE_EXEC! >> "%WRAPPER_LOG%"
47 | )
48 | )
49 |
50 | REM Priority 2: where command
51 | if not defined NODE_EXEC (
52 | echo Trying 'where node.exe' >> "%WRAPPER_LOG%"
53 | for /f "delims=" %%i in ('where node.exe 2^>nul') do (
54 | if not defined NODE_EXEC (
55 | set "NODE_EXEC=%%i"
56 | echo Found node using 'where': !NODE_EXEC! >> "%WRAPPER_LOG%"
57 | )
58 | )
59 | )
60 |
61 | REM Priority 3: Common paths
62 | if not defined NODE_EXEC (
63 | if exist "%ProgramFiles%\nodejs\node.exe" (
64 | set "NODE_EXEC=%ProgramFiles%\nodejs\node.exe"
65 | echo Found node at !NODE_EXEC! >> "%WRAPPER_LOG%"
66 | ) else if exist "%ProgramFiles(x86)%\nodejs\node.exe" (
67 | set "NODE_EXEC=%ProgramFiles(x86)%\nodejs\node.exe"
68 | echo Found node at !NODE_EXEC! >> "%WRAPPER_LOG%"
69 | ) else if exist "%LOCALAPPDATA%\Programs\nodejs\node.exe" (
70 | set "NODE_EXEC=%LOCALAPPDATA%\Programs\nodejs\node.exe"
71 | echo Found node at !NODE_EXEC! >> "%WRAPPER_LOG%"
72 | )
73 | )
74 |
75 | REM Validation
76 | if not defined NODE_EXEC (
77 | echo ERROR: Node.js executable not found! >> "%WRAPPER_LOG%"
78 | exit /B 1
79 | )
80 |
81 | echo Using Node executable: %NODE_EXEC% >> "%WRAPPER_LOG%"
82 | call "%NODE_EXEC%" -v >> "%WRAPPER_LOG%" 2>>&1
83 |
84 | if not exist "%NODE_SCRIPT%" (
85 | echo ERROR: Node.js script not found at %NODE_SCRIPT% >> "%WRAPPER_LOG%"
86 | exit /B 1
87 | )
88 |
89 | echo Executing: "%NODE_EXEC%" "%NODE_SCRIPT%" >> "%WRAPPER_LOG%"
90 | call "%NODE_EXEC%" "%NODE_SCRIPT%" 2>> "%STDERR_LOG%"
91 | set "EXIT_CODE=%ERRORLEVEL%"
92 |
93 | echo Exit code: %EXIT_CODE% >> "%WRAPPER_LOG%"
94 | endlocal
95 | exit /B %EXIT_CODE%
```
--------------------------------------------------------------------------------
/app/chrome-extension/utils/lru-cache.ts:
--------------------------------------------------------------------------------
```typescript
1 | class LRUNode<K, V> {
2 | constructor(
3 | public key: K,
4 | public value: V,
5 | public prev: LRUNode<K, V> | null = null,
6 | public next: LRUNode<K, V> | null = null,
7 | public frequency: number = 1,
8 | public lastAccessed: number = Date.now(),
9 | ) {}
10 | }
11 |
12 | class LRUCache<K = string, V = any> {
13 | private capacity: number;
14 | private cache: Map<K, LRUNode<K, V>>;
15 | private head: LRUNode<K, V>;
16 | private tail: LRUNode<K, V>;
17 |
18 | constructor(capacity: number) {
19 | this.capacity = capacity > 0 ? capacity : 100;
20 | this.cache = new Map<K, LRUNode<K, V>>();
21 |
22 | this.head = new LRUNode<K, V>(null as any, null as any);
23 | this.tail = new LRUNode<K, V>(null as any, null as any);
24 | this.head.next = this.tail;
25 | this.tail.prev = this.head;
26 | }
27 |
28 | private addToHead(node: LRUNode<K, V>): void {
29 | node.prev = this.head;
30 | node.next = this.head.next;
31 | this.head.next!.prev = node;
32 | this.head.next = node;
33 | }
34 |
35 | private removeNode(node: LRUNode<K, V>): void {
36 | node.prev!.next = node.next;
37 | node.next!.prev = node.prev;
38 | }
39 |
40 | private moveToHead(node: LRUNode<K, V>): void {
41 | this.removeNode(node);
42 | this.addToHead(node);
43 | }
44 |
45 | private findVictimNode(): LRUNode<K, V> {
46 | let victim = this.tail.prev!;
47 | let minScore = this.calculateEvictionScore(victim);
48 |
49 | let current = this.tail.prev;
50 | let count = 0;
51 | const maxCheck = Math.min(5, this.cache.size);
52 |
53 | while (current && current !== this.head && count < maxCheck) {
54 | const score = this.calculateEvictionScore(current);
55 | if (score < minScore) {
56 | minScore = score;
57 | victim = current;
58 | }
59 | current = current.prev;
60 | count++;
61 | }
62 |
63 | return victim;
64 | }
65 |
66 | private calculateEvictionScore(node: LRUNode<K, V>): number {
67 | const now = Date.now();
68 | const timeSinceAccess = now - node.lastAccessed;
69 | const timeWeight = 1 / (1 + timeSinceAccess / (1000 * 60));
70 | const frequencyWeight = Math.log(node.frequency + 1);
71 |
72 | return frequencyWeight * timeWeight;
73 | }
74 |
75 | get(key: K): V | null {
76 | const node = this.cache.get(key);
77 | if (node) {
78 | node.frequency++;
79 | node.lastAccessed = Date.now();
80 | this.moveToHead(node);
81 | return node.value;
82 | }
83 | return null;
84 | }
85 |
86 | set(key: K, value: V): void {
87 | const existingNode = this.cache.get(key);
88 |
89 | if (existingNode) {
90 | existingNode.value = value;
91 | this.moveToHead(existingNode);
92 | } else {
93 | const newNode = new LRUNode(key, value);
94 |
95 | if (this.cache.size >= this.capacity) {
96 | const victimNode = this.findVictimNode();
97 | this.removeNode(victimNode);
98 | this.cache.delete(victimNode.key);
99 | }
100 |
101 | this.cache.set(key, newNode);
102 | this.addToHead(newNode);
103 | }
104 | }
105 |
106 | has(key: K): boolean {
107 | return this.cache.has(key);
108 | }
109 |
110 | clear(): void {
111 | this.cache.clear();
112 | this.head.next = this.tail;
113 | this.tail.prev = this.head;
114 | }
115 |
116 | get size(): number {
117 | return this.cache.size;
118 | }
119 |
120 | /**
121 | * Get cache statistics
122 | */
123 | getStats(): { size: number; capacity: number; usage: number } {
124 | return {
125 | size: this.cache.size,
126 | capacity: this.capacity,
127 | usage: this.cache.size / this.capacity,
128 | };
129 | }
130 | }
131 |
132 | export default LRUCache;
133 |
```
--------------------------------------------------------------------------------
/app/native-server/src/mcp/mcp-server-stdio.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 |
3 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4 | import { Client } from '@modelcontextprotocol/sdk/client/index.js';
5 | import {
6 | CallToolRequestSchema,
7 | CallToolResult,
8 | ListToolsRequestSchema,
9 | ListResourcesRequestSchema,
10 | ListPromptsRequestSchema,
11 | } from '@modelcontextprotocol/sdk/types.js';
12 | import { TOOL_SCHEMAS } from 'chrome-mcp-shared';
13 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
14 | import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
15 | import * as fs from 'fs';
16 | import * as path from 'path';
17 |
18 | let stdioMcpServer: Server | null = null;
19 | let mcpClient: Client | null = null;
20 |
21 | // Read configuration from stdio-config.json
22 | const loadConfig = () => {
23 | try {
24 | const configPath = path.join(__dirname, 'stdio-config.json');
25 | const configData = fs.readFileSync(configPath, 'utf8');
26 | return JSON.parse(configData);
27 | } catch (error) {
28 | console.error('Failed to load stdio-config.json:', error);
29 | throw new Error('Configuration file stdio-config.json not found or invalid');
30 | }
31 | };
32 |
33 | export const getStdioMcpServer = () => {
34 | if (stdioMcpServer) {
35 | return stdioMcpServer;
36 | }
37 | stdioMcpServer = new Server(
38 | {
39 | name: 'StdioChromeMcpServer',
40 | version: '1.0.0',
41 | },
42 | {
43 | capabilities: {
44 | tools: {},
45 | resources: {},
46 | prompts: {},
47 | },
48 | },
49 | );
50 |
51 | setupTools(stdioMcpServer);
52 | return stdioMcpServer;
53 | };
54 |
55 | export const ensureMcpClient = async () => {
56 | try {
57 | if (mcpClient) {
58 | const pingResult = await mcpClient.ping();
59 | if (pingResult) {
60 | return mcpClient;
61 | }
62 | }
63 |
64 | const config = loadConfig();
65 | mcpClient = new Client({ name: 'Mcp Chrome Proxy', version: '1.0.0' }, { capabilities: {} });
66 | const transport = new StreamableHTTPClientTransport(new URL(config.url), {});
67 | await mcpClient.connect(transport);
68 | return mcpClient;
69 | } catch (error) {
70 | mcpClient?.close();
71 | mcpClient = null;
72 | console.error('Failed to connect to MCP server:', error);
73 | }
74 | };
75 |
76 | export const setupTools = (server: Server) => {
77 | // List tools handler
78 | server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOL_SCHEMAS }));
79 |
80 | // Call tool handler
81 | server.setRequestHandler(CallToolRequestSchema, async (request) =>
82 | handleToolCall(request.params.name, request.params.arguments || {}),
83 | );
84 |
85 | // List resources handler - REQUIRED BY MCP PROTOCOL
86 | server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: [] }));
87 |
88 | // List prompts handler - REQUIRED BY MCP PROTOCOL
89 | server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [] }));
90 | };
91 |
92 | const handleToolCall = async (name: string, args: any): Promise<CallToolResult> => {
93 | try {
94 | const client = await ensureMcpClient();
95 | if (!client) {
96 | throw new Error('Failed to connect to MCP server');
97 | }
98 | const result = await client.callTool({ name, arguments: args }, undefined, {
99 | timeout: 2 * 6 * 1000, // Default timeout of 2 minute
100 | });
101 | return result as CallToolResult;
102 | } catch (error: any) {
103 | return {
104 | content: [
105 | {
106 | type: 'text',
107 | text: `Error calling tool: ${error.message}`,
108 | },
109 | ],
110 | isError: true,
111 | };
112 | }
113 | };
114 |
115 | async function main() {
116 | const transport = new StdioServerTransport();
117 | await getStdioMcpServer().connect(transport);
118 | }
119 |
120 | main().catch((error) => {
121 | console.error('Fatal error Chrome MCP Server main():', error);
122 | process.exit(1);
123 | });
124 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/inject-scripts/network-helper.js:
--------------------------------------------------------------------------------
```javascript
1 | /* eslint-disable */
2 | /**
3 | * Network Capture Helper
4 | *
5 | * This script helps replay network requests with the original cookies and headers.
6 | */
7 |
8 | // Prevent duplicate initialization
9 | if (window.__NETWORK_CAPTURE_HELPER_INITIALIZED__) {
10 | // Already initialized, skip
11 | } else {
12 | window.__NETWORK_CAPTURE_HELPER_INITIALIZED__ = true;
13 |
14 | /**
15 | * Replay a network request
16 | * @param {string} url - The URL to send the request to
17 | * @param {string} method - The HTTP method to use
18 | * @param {Object} headers - The headers to include in the request
19 | * @param {any} body - The body of the request
20 | * @param {number} timeout - Timeout in milliseconds (default: 30000)
21 | * @returns {Promise<Object>} - The response data
22 | */
23 | async function replayNetworkRequest(url, method, headers, body, timeout = 30000) {
24 | try {
25 | // Create fetch options
26 | const options = {
27 | method: method,
28 | headers: headers || {},
29 | credentials: 'include', // Include cookies
30 | mode: 'cors',
31 | cache: 'no-cache',
32 | };
33 |
34 | // Add body for non-GET requests
35 | if (method !== 'GET' && method !== 'HEAD' && body !== undefined) {
36 | options.body = body;
37 | }
38 |
39 | // 创建一个带超时的 fetch
40 | const fetchWithTimeout = async (url, options, timeout) => {
41 | const controller = new AbortController();
42 | const signal = controller.signal;
43 |
44 | // 设置超时
45 | const timeoutId = setTimeout(() => controller.abort(), timeout);
46 |
47 | try {
48 | const response = await fetch(url, { ...options, signal });
49 | clearTimeout(timeoutId);
50 | return response;
51 | } catch (error) {
52 | clearTimeout(timeoutId);
53 | throw error;
54 | }
55 | };
56 |
57 | // 发送带超时的请求
58 | const response = await fetchWithTimeout(url, options, timeout);
59 |
60 | // Process response
61 | const responseData = {
62 | status: response.status,
63 | statusText: response.statusText,
64 | headers: {},
65 | };
66 |
67 | // Get response headers
68 | response.headers.forEach((value, key) => {
69 | responseData.headers[key] = value;
70 | });
71 |
72 | // Try to get response body based on content type
73 | const contentType = response.headers.get('content-type') || '';
74 |
75 | try {
76 | if (contentType.includes('application/json')) {
77 | responseData.body = await response.json();
78 | } else if (
79 | contentType.includes('text/') ||
80 | contentType.includes('application/xml') ||
81 | contentType.includes('application/javascript')
82 | ) {
83 | responseData.body = await response.text();
84 | } else {
85 | // For binary data, just indicate it was received but not parsed
86 | responseData.body = '[Binary data not displayed]';
87 | }
88 | } catch (error) {
89 | responseData.body = `[Error parsing response body: ${error.message}]`;
90 | }
91 |
92 | return {
93 | success: true,
94 | response: responseData,
95 | };
96 | } catch (error) {
97 | console.error('Error replaying request:', error);
98 | return {
99 | success: false,
100 | error: `Error replaying request: ${error.message}`,
101 | };
102 | }
103 | }
104 |
105 | // Listen for messages from the extension
106 | chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
107 | // Respond to ping message
108 | if (request.action === 'chrome_network_request_ping') {
109 | sendResponse({ status: 'pong' });
110 | return false; // Synchronous response
111 | } else if (request.action === 'sendPureNetworkRequest') {
112 | replayNetworkRequest(
113 | request.url,
114 | request.method,
115 | request.headers,
116 | request.body,
117 | request.timeout,
118 | )
119 | .then(sendResponse)
120 | .catch((error) => {
121 | sendResponse({
122 | success: false,
123 | error: `Unexpected error: ${error.message}`,
124 | });
125 | });
126 | return true; // Indicates async response
127 | }
128 | });
129 | }
130 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/storage-manager.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { BACKGROUND_MESSAGE_TYPES } from '@/common/message-types';
2 |
3 | /**
4 | * Get storage statistics
5 | */
6 | export async function handleGetStorageStats(): Promise<{
7 | success: boolean;
8 | stats?: any;
9 | error?: string;
10 | }> {
11 | try {
12 | // Get ContentIndexer statistics
13 | const { getGlobalContentIndexer } = await import('@/utils/content-indexer');
14 | const contentIndexer = getGlobalContentIndexer();
15 |
16 | // Note: Semantic engine initialization is now user-controlled
17 | // ContentIndexer will be initialized when user manually triggers semantic engine initialization
18 |
19 | // Get statistics
20 | const stats = contentIndexer.getStats();
21 |
22 | return {
23 | success: true,
24 | stats: {
25 | indexedPages: stats.indexedPages || 0,
26 | totalDocuments: stats.totalDocuments || 0,
27 | totalTabs: stats.totalTabs || 0,
28 | indexSize: stats.indexSize || 0,
29 | isInitialized: stats.isInitialized || false,
30 | semanticEngineReady: stats.semanticEngineReady || false,
31 | semanticEngineInitializing: stats.semanticEngineInitializing || false,
32 | },
33 | };
34 | } catch (error: any) {
35 | console.error('Background: Failed to get storage stats:', error);
36 | return {
37 | success: false,
38 | error: error.message,
39 | stats: {
40 | indexedPages: 0,
41 | totalDocuments: 0,
42 | totalTabs: 0,
43 | indexSize: 0,
44 | isInitialized: false,
45 | semanticEngineReady: false,
46 | semanticEngineInitializing: false,
47 | },
48 | };
49 | }
50 | }
51 |
52 | /**
53 | * Clear all data
54 | */
55 | export async function handleClearAllData(): Promise<{ success: boolean; error?: string }> {
56 | try {
57 | // 1. Clear all ContentIndexer indexes
58 | try {
59 | const { getGlobalContentIndexer } = await import('@/utils/content-indexer');
60 | const contentIndexer = getGlobalContentIndexer();
61 |
62 | await contentIndexer.clearAllIndexes();
63 | console.log('Storage: ContentIndexer indexes cleared successfully');
64 | } catch (indexerError) {
65 | console.warn('Background: Failed to clear ContentIndexer indexes:', indexerError);
66 | // Continue with other cleanup operations
67 | }
68 |
69 | // 2. Clear all VectorDatabase data
70 | try {
71 | const { clearAllVectorData } = await import('@/utils/vector-database');
72 | await clearAllVectorData();
73 | console.log('Storage: Vector database data cleared successfully');
74 | } catch (vectorError) {
75 | console.warn('Background: Failed to clear vector data:', vectorError);
76 | // Continue with other cleanup operations
77 | }
78 |
79 | // 3. Clear related data in chrome.storage (preserve model preferences)
80 | try {
81 | const keysToRemove = ['vectorDatabaseStats', 'lastCleanupTime', 'contentIndexerStats'];
82 | await chrome.storage.local.remove(keysToRemove);
83 | console.log('Storage: Chrome storage data cleared successfully');
84 | } catch (storageError) {
85 | console.warn('Background: Failed to clear chrome storage data:', storageError);
86 | }
87 |
88 | return { success: true };
89 | } catch (error: any) {
90 | console.error('Background: Failed to clear all data:', error);
91 | return { success: false, error: error.message };
92 | }
93 | }
94 |
95 | /**
96 | * Initialize storage manager module message listeners
97 | */
98 | export const initStorageManagerListener = () => {
99 | chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
100 | if (message.type === BACKGROUND_MESSAGE_TYPES.GET_STORAGE_STATS) {
101 | handleGetStorageStats()
102 | .then((result: { success: boolean; stats?: any; error?: string }) => sendResponse(result))
103 | .catch((error: any) => sendResponse({ success: false, error: error.message }));
104 | return true;
105 | } else if (message.type === BACKGROUND_MESSAGE_TYPES.CLEAR_ALL_DATA) {
106 | handleClearAllData()
107 | .then((result: { success: boolean; error?: string }) => sendResponse(result))
108 | .catch((error: any) => sendResponse({ success: false, error: error.message }));
109 | return true;
110 | }
111 | });
112 | };
113 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/common/message-types.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Consolidated message type constants for Chrome extension communication
3 | * Note: Native message types are imported from the shared package
4 | */
5 |
6 | // Message targets for routing
7 | export enum MessageTarget {
8 | Offscreen = 'offscreen',
9 | ContentScript = 'content_script',
10 | Background = 'background',
11 | }
12 |
13 | // Background script message types
14 | export const BACKGROUND_MESSAGE_TYPES = {
15 | SWITCH_SEMANTIC_MODEL: 'switch_semantic_model',
16 | GET_MODEL_STATUS: 'get_model_status',
17 | UPDATE_MODEL_STATUS: 'update_model_status',
18 | GET_STORAGE_STATS: 'get_storage_stats',
19 | CLEAR_ALL_DATA: 'clear_all_data',
20 | GET_SERVER_STATUS: 'get_server_status',
21 | REFRESH_SERVER_STATUS: 'refresh_server_status',
22 | SERVER_STATUS_CHANGED: 'server_status_changed',
23 | INITIALIZE_SEMANTIC_ENGINE: 'initialize_semantic_engine',
24 | } as const;
25 |
26 | // Offscreen message types
27 | export const OFFSCREEN_MESSAGE_TYPES = {
28 | SIMILARITY_ENGINE_INIT: 'similarityEngineInit',
29 | SIMILARITY_ENGINE_COMPUTE: 'similarityEngineCompute',
30 | SIMILARITY_ENGINE_BATCH_COMPUTE: 'similarityEngineBatchCompute',
31 | SIMILARITY_ENGINE_STATUS: 'similarityEngineStatus',
32 | } as const;
33 |
34 | // Content script message types
35 | export const CONTENT_MESSAGE_TYPES = {
36 | WEB_FETCHER_GET_TEXT_CONTENT: 'webFetcherGetTextContent',
37 | WEB_FETCHER_GET_HTML_CONTENT: 'getHtmlContent',
38 | NETWORK_CAPTURE_PING: 'network_capture_ping',
39 | CLICK_HELPER_PING: 'click_helper_ping',
40 | FILL_HELPER_PING: 'fill_helper_ping',
41 | KEYBOARD_HELPER_PING: 'keyboard_helper_ping',
42 | SCREENSHOT_HELPER_PING: 'screenshot_helper_ping',
43 | INTERACTIVE_ELEMENTS_HELPER_PING: 'interactive_elements_helper_ping',
44 | } as const;
45 |
46 | // Tool action message types (for chrome.runtime.sendMessage)
47 | export const TOOL_MESSAGE_TYPES = {
48 | // Screenshot related
49 | SCREENSHOT_PREPARE_PAGE_FOR_CAPTURE: 'preparePageForCapture',
50 | SCREENSHOT_GET_PAGE_DETAILS: 'getPageDetails',
51 | SCREENSHOT_GET_ELEMENT_DETAILS: 'getElementDetails',
52 | SCREENSHOT_SCROLL_PAGE: 'scrollPage',
53 | SCREENSHOT_RESET_PAGE_AFTER_CAPTURE: 'resetPageAfterCapture',
54 |
55 | // Web content fetching
56 | WEB_FETCHER_GET_HTML_CONTENT: 'getHtmlContent',
57 | WEB_FETCHER_GET_TEXT_CONTENT: 'getTextContent',
58 |
59 | // User interactions
60 | CLICK_ELEMENT: 'clickElement',
61 | FILL_ELEMENT: 'fillElement',
62 | SIMULATE_KEYBOARD: 'simulateKeyboard',
63 |
64 | // Interactive elements
65 | GET_INTERACTIVE_ELEMENTS: 'getInteractiveElements',
66 |
67 | // Network requests
68 | NETWORK_SEND_REQUEST: 'sendPureNetworkRequest',
69 |
70 | // Semantic similarity engine
71 | SIMILARITY_ENGINE_INIT: 'similarityEngineInit',
72 | SIMILARITY_ENGINE_COMPUTE_BATCH: 'similarityEngineComputeBatch',
73 | } as const;
74 |
75 | // Type unions for type safety
76 | export type BackgroundMessageType =
77 | (typeof BACKGROUND_MESSAGE_TYPES)[keyof typeof BACKGROUND_MESSAGE_TYPES];
78 | export type OffscreenMessageType =
79 | (typeof OFFSCREEN_MESSAGE_TYPES)[keyof typeof OFFSCREEN_MESSAGE_TYPES];
80 | export type ContentMessageType = (typeof CONTENT_MESSAGE_TYPES)[keyof typeof CONTENT_MESSAGE_TYPES];
81 | export type ToolMessageType = (typeof TOOL_MESSAGE_TYPES)[keyof typeof TOOL_MESSAGE_TYPES];
82 |
83 | // Legacy enum for backward compatibility (will be deprecated)
84 | export enum SendMessageType {
85 | // Screenshot related message types
86 | ScreenshotPreparePageForCapture = 'preparePageForCapture',
87 | ScreenshotGetPageDetails = 'getPageDetails',
88 | ScreenshotGetElementDetails = 'getElementDetails',
89 | ScreenshotScrollPage = 'scrollPage',
90 | ScreenshotResetPageAfterCapture = 'resetPageAfterCapture',
91 |
92 | // Web content fetching related message types
93 | WebFetcherGetHtmlContent = 'getHtmlContent',
94 | WebFetcherGetTextContent = 'getTextContent',
95 |
96 | // Click related message types
97 | ClickElement = 'clickElement',
98 |
99 | // Input filling related message types
100 | FillElement = 'fillElement',
101 |
102 | // Interactive elements related message types
103 | GetInteractiveElements = 'getInteractiveElements',
104 |
105 | // Network request capture related message types
106 | NetworkSendRequest = 'sendPureNetworkRequest',
107 |
108 | // Keyboard event related message types
109 | SimulateKeyboard = 'simulateKeyboard',
110 |
111 | // Semantic similarity engine related message types
112 | SimilarityEngineInit = 'similarityEngineInit',
113 | SimilarityEngineComputeBatch = 'similarityEngineComputeBatch',
114 | }
115 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/style.css:
--------------------------------------------------------------------------------
```css
1 | /* 现代化全局样式 */
2 | :root {
3 | /* 字体系统 */
4 | font-family:
5 | -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
6 | line-height: 1.6;
7 | font-weight: 400;
8 |
9 | /* 颜色系统 */
10 | --primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
11 | --primary-color: #667eea;
12 | --primary-dark: #5a67d8;
13 | --secondary-color: #764ba2;
14 |
15 | --success-color: #48bb78;
16 | --warning-color: #ed8936;
17 | --error-color: #f56565;
18 | --info-color: #4299e1;
19 |
20 | --text-primary: #2d3748;
21 | --text-secondary: #4a5568;
22 | --text-muted: #718096;
23 | --text-light: #a0aec0;
24 |
25 | --bg-primary: #ffffff;
26 | --bg-secondary: #f7fafc;
27 | --bg-tertiary: #edf2f7;
28 | --bg-overlay: rgba(255, 255, 255, 0.95);
29 |
30 | --border-color: #e2e8f0;
31 | --border-light: #f1f5f9;
32 | --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1);
33 | --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1);
34 | --shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
35 | --shadow-xl: 0 20px 25px rgba(0, 0, 0, 0.1);
36 |
37 | /* 间距系统 */
38 | --spacing-xs: 4px;
39 | --spacing-sm: 8px;
40 | --spacing-md: 12px;
41 | --spacing-lg: 16px;
42 | --spacing-xl: 20px;
43 | --spacing-2xl: 24px;
44 | --spacing-3xl: 32px;
45 |
46 | /* 圆角系统 */
47 | --radius-sm: 4px;
48 | --radius-md: 6px;
49 | --radius-lg: 8px;
50 | --radius-xl: 12px;
51 | --radius-2xl: 16px;
52 |
53 | /* 动画 */
54 | --transition-fast: 0.15s ease;
55 | --transition-normal: 0.3s ease;
56 | --transition-slow: 0.5s ease;
57 |
58 | /* 字体渲染优化 */
59 | font-synthesis: none;
60 | text-rendering: optimizeLegibility;
61 | -webkit-font-smoothing: antialiased;
62 | -moz-osx-font-smoothing: grayscale;
63 | -webkit-text-size-adjust: 100%;
64 | }
65 |
66 | /* 重置样式 */
67 | * {
68 | box-sizing: border-box;
69 | margin: 0;
70 | padding: 0;
71 | }
72 |
73 | body {
74 | margin: 0;
75 | padding: 0;
76 | width: 400px;
77 | min-height: 500px;
78 | max-height: 600px;
79 | overflow: hidden;
80 | font-family: inherit;
81 | background: var(--bg-secondary);
82 | color: var(--text-primary);
83 | }
84 |
85 | #app {
86 | width: 100%;
87 | height: 100%;
88 | margin: 0;
89 | padding: 0;
90 | }
91 |
92 | /* 链接样式 */
93 | a {
94 | color: var(--primary-color);
95 | text-decoration: none;
96 | transition: color var(--transition-fast);
97 | }
98 |
99 | a:hover {
100 | color: var(--primary-dark);
101 | }
102 |
103 | /* 按钮基础样式重置 */
104 | button {
105 | font-family: inherit;
106 | font-size: inherit;
107 | line-height: inherit;
108 | border: none;
109 | background: none;
110 | cursor: pointer;
111 | transition: all var(--transition-normal);
112 | }
113 |
114 | button:disabled {
115 | cursor: not-allowed;
116 | opacity: 0.6;
117 | }
118 |
119 | /* 输入框基础样式 */
120 | input,
121 | textarea,
122 | select {
123 | font-family: inherit;
124 | font-size: inherit;
125 | line-height: inherit;
126 | border: 1px solid var(--border-color);
127 | border-radius: var(--radius-md);
128 | padding: var(--spacing-sm) var(--spacing-md);
129 | background: var(--bg-primary);
130 | color: var(--text-primary);
131 | transition: all var(--transition-fast);
132 | }
133 |
134 | input:focus,
135 | textarea:focus,
136 | select:focus {
137 | outline: none;
138 | border-color: var(--primary-color);
139 | box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
140 | }
141 |
142 | /* 滚动条样式 */
143 | ::-webkit-scrollbar {
144 | width: 8px;
145 | height: 8px;
146 | }
147 |
148 | ::-webkit-scrollbar-track {
149 | background: var(--bg-tertiary);
150 | border-radius: var(--radius-sm);
151 | }
152 |
153 | ::-webkit-scrollbar-thumb {
154 | background: var(--border-color);
155 | border-radius: var(--radius-sm);
156 | transition: background var(--transition-fast);
157 | }
158 |
159 | ::-webkit-scrollbar-thumb:hover {
160 | background: var(--text-muted);
161 | }
162 |
163 | /* 选择文本样式 */
164 | ::selection {
165 | background: rgba(102, 126, 234, 0.2);
166 | color: var(--text-primary);
167 | }
168 |
169 | /* 焦点可见性 */
170 | :focus-visible {
171 | outline: 2px solid var(--primary-color);
172 | outline-offset: 2px;
173 | }
174 |
175 | /* 动画关键帧 */
176 | @keyframes fadeIn {
177 | from {
178 | opacity: 0;
179 | }
180 | to {
181 | opacity: 1;
182 | }
183 | }
184 |
185 | @keyframes slideUp {
186 | from {
187 | opacity: 0;
188 | transform: translateY(10px);
189 | }
190 | to {
191 | opacity: 1;
192 | transform: translateY(0);
193 | }
194 | }
195 |
196 | @keyframes slideDown {
197 | from {
198 | opacity: 0;
199 | transform: translateY(-10px);
200 | }
201 | to {
202 | opacity: 1;
203 | transform: translateY(0);
204 | }
205 | }
206 |
207 | @keyframes scaleIn {
208 | from {
209 | opacity: 0;
210 | transform: scale(0.95);
211 | }
212 | to {
213 | opacity: 1;
214 | transform: scale(1);
215 | }
216 | }
217 |
218 | /* 响应式断点 */
219 | @media (max-width: 420px) {
220 | :root {
221 | --spacing-xs: 3px;
222 | --spacing-sm: 6px;
223 | --spacing-md: 10px;
224 | --spacing-lg: 14px;
225 | --spacing-xl: 18px;
226 | --spacing-2xl: 22px;
227 | --spacing-3xl: 28px;
228 | }
229 | }
230 |
231 | /* 高对比度模式支持 */
232 | @media (prefers-contrast: high) {
233 | :root {
234 | --border-color: #000000;
235 | --text-muted: #000000;
236 | }
237 | }
238 |
239 | /* 减少动画偏好 */
240 | @media (prefers-reduced-motion: reduce) {
241 | * {
242 | animation-duration: 0.01ms !important;
243 | animation-iteration-count: 1 !important;
244 | transition-duration: 0.01ms !important;
245 | }
246 | }
247 |
```
--------------------------------------------------------------------------------
/app/native-server/src/scripts/run_host.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/usr/bin/env bash
2 |
3 | # Configuration
4 | ENABLE_LOG_ROTATION="true"
5 | LOG_RETENTION_COUNT=5
6 |
7 | # Setup paths
8 | SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
9 | LOG_DIR="${SCRIPT_DIR}/logs"
10 | mkdir -p "${LOG_DIR}"
11 |
12 | # Log rotation
13 | if [ "${ENABLE_LOG_ROTATION}" = "true" ]; then
14 | ls -tp "${LOG_DIR}/native_host_wrapper_macos_"* 2>/dev/null | tail -n +$((LOG_RETENTION_COUNT + 1)) | xargs -I {} rm -- {}
15 | ls -tp "${LOG_DIR}/native_host_stderr_macos_"* 2>/dev/null | tail -n +$((LOG_RETENTION_COUNT + 1)) | xargs -I {} rm -- {}
16 | fi
17 |
18 | # Logging setup
19 | TIMESTAMP=$(date +"%Y%m%d_%H%M%S")
20 | WRAPPER_LOG="${LOG_DIR}/native_host_wrapper_macos_${TIMESTAMP}.log"
21 | STDERR_LOG="${LOG_DIR}/native_host_stderr_macos_${TIMESTAMP}.log"
22 | NODE_SCRIPT="${SCRIPT_DIR}/index.js"
23 |
24 | # Initial logging
25 | {
26 | echo "--- Wrapper script called at $(date) ---"
27 | echo "SCRIPT_DIR: ${SCRIPT_DIR}"
28 | echo "LOG_DIR: ${LOG_DIR}"
29 | echo "NODE_SCRIPT: ${NODE_SCRIPT}"
30 | echo "Initial PATH: ${PATH}"
31 | echo "User: $(whoami)"
32 | echo "Current PWD: $(pwd)"
33 | } > "${WRAPPER_LOG}"
34 |
35 | # Node.js discovery
36 | NODE_EXEC=""
37 |
38 | # Priority 1: Installation-time node path
39 | NODE_PATH_FILE="${SCRIPT_DIR}/node_path.txt"
40 | echo "Searching for Node.js..." >> "${WRAPPER_LOG}"
41 | echo "[Priority 1] Checking installation-time node path" >> "${WRAPPER_LOG}"
42 | if [ -f "${NODE_PATH_FILE}" ]; then
43 | EXPECTED_NODE=$(cat "${NODE_PATH_FILE}" 2>/dev/null | tr -d '\n\r')
44 | if [ -n "${EXPECTED_NODE}" ] && [ -x "${EXPECTED_NODE}" ]; then
45 | NODE_EXEC="${EXPECTED_NODE}"
46 | echo "Found installation-time node at ${NODE_EXEC}" >> "${WRAPPER_LOG}"
47 | fi
48 | fi
49 |
50 | # Priority 1.5: Fallback to relative path
51 | if [ -z "${NODE_EXEC}" ]; then
52 | EXPECTED_NODE="${SCRIPT_DIR}/../../../bin/node"
53 | echo "[Priority 1.5] Checking relative path" >> "${WRAPPER_LOG}"
54 | if [ -x "${EXPECTED_NODE}" ]; then
55 | NODE_EXEC="${EXPECTED_NODE}"
56 | echo "Found node at relative path: ${NODE_EXEC}" >> "${WRAPPER_LOG}"
57 | fi
58 | fi
59 |
60 | # Priority 2: NVM
61 | if [ -z "${NODE_EXEC}" ]; then
62 | echo "[Priority 2] Checking NVM" >> "${WRAPPER_LOG}"
63 | NVM_DIR="$HOME/.nvm"
64 | if [ -d "${NVM_DIR}" ]; then
65 | # Try default version first
66 | if [ -L "${NVM_DIR}/alias/default" ]; then
67 | NVM_DEFAULT_VERSION=$(readlink "${NVM_DIR}/alias/default")
68 | NVM_DEFAULT_NODE="${NVM_DIR}/versions/node/${NVM_DEFAULT_VERSION}/bin/node"
69 | if [ -x "${NVM_DEFAULT_NODE}" ]; then
70 | NODE_EXEC="${NVM_DEFAULT_NODE}"
71 | echo "Found NVM default node: ${NODE_EXEC}" >> "${WRAPPER_LOG}"
72 | fi
73 | fi
74 |
75 | # Fallback to latest version
76 | if [ -z "${NODE_EXEC}" ]; then
77 | LATEST_NVM_VERSION_PATH=$(ls -d ${NVM_DIR}/versions/node/v* 2>/dev/null | sort -V | tail -n 1)
78 | if [ -n "${LATEST_NVM_VERSION_PATH}" ] && [ -x "${LATEST_NVM_VERSION_PATH}/bin/node" ]; then
79 | NODE_EXEC="${LATEST_NVM_VERSION_PATH}/bin/node"
80 | echo "Found NVM latest node: ${NODE_EXEC}" >> "${WRAPPER_LOG}"
81 | fi
82 | fi
83 | fi
84 | fi
85 |
86 | # Priority 3: Common paths
87 | if [ -z "${NODE_EXEC}" ]; then
88 | echo "[Priority 3] Checking common paths" >> "${WRAPPER_LOG}"
89 | COMMON_NODE_PATHS=(
90 | "/opt/homebrew/bin/node"
91 | "/usr/local/bin/node"
92 | )
93 | for path_to_node in "${COMMON_NODE_PATHS[@]}"; do
94 | if [ -x "${path_to_node}" ]; then
95 | NODE_EXEC="${path_to_node}"
96 | echo "Found node at: ${NODE_EXEC}" >> "${WRAPPER_LOG}"
97 | break
98 | fi
99 | done
100 | fi
101 |
102 | # Priority 4: command -v
103 | if [ -z "${NODE_EXEC}" ]; then
104 | echo "[Priority 4] Trying 'command -v node'" >> "${WRAPPER_LOG}"
105 | if command -v node &>/dev/null; then
106 | NODE_EXEC=$(command -v node)
107 | echo "Found node using 'command -v': ${NODE_EXEC}" >> "${WRAPPER_LOG}"
108 | fi
109 | fi
110 |
111 | # Priority 5: PATH search
112 | if [ -z "${NODE_EXEC}" ]; then
113 | echo "[Priority 5] Searching PATH" >> "${WRAPPER_LOG}"
114 | OLD_IFS=$IFS
115 | IFS=:
116 | for path_in_env in $PATH; do
117 | if [ -x "${path_in_env}/node" ]; then
118 | NODE_EXEC="${path_in_env}/node"
119 | echo "Found node in PATH: ${NODE_EXEC}" >> "${WRAPPER_LOG}"
120 | break
121 | fi
122 | done
123 | IFS=$OLD_IFS
124 | fi
125 |
126 | # Execution
127 | if [ -z "${NODE_EXEC}" ]; then
128 | {
129 | echo "ERROR: Node.js executable not found!"
130 | echo "Searched: installation path, relative path, NVM, common paths, command -v, PATH"
131 | } >> "${WRAPPER_LOG}"
132 | exit 1
133 | fi
134 |
135 | {
136 | echo "Using Node executable: ${NODE_EXEC}"
137 | echo "Node version: $(${NODE_EXEC} -v)"
138 | echo "Executing: ${NODE_EXEC} ${NODE_SCRIPT}"
139 | } >> "${WRAPPER_LOG}"
140 |
141 | exec "${NODE_EXEC}" "${NODE_SCRIPT}" 2>> "${STDERR_LOG}"
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/background/tools/browser/interaction.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createErrorResponse, ToolResult } from '@/common/tool-handler';
2 | import { BaseBrowserToolExecutor } from '../base-browser';
3 | import { TOOL_NAMES } from 'chrome-mcp-shared';
4 | import { TOOL_MESSAGE_TYPES } from '@/common/message-types';
5 | import { TIMEOUTS, ERROR_MESSAGES } from '@/common/constants';
6 |
7 | interface Coordinates {
8 | x: number;
9 | y: number;
10 | }
11 |
12 | interface ClickToolParams {
13 | selector?: string; // CSS selector for the element to click
14 | coordinates?: Coordinates; // Coordinates to click at (x, y relative to viewport)
15 | waitForNavigation?: boolean; // Whether to wait for navigation to complete after click
16 | timeout?: number; // Timeout in milliseconds for waiting for the element or navigation
17 | }
18 |
19 | /**
20 | * Tool for clicking elements on web pages
21 | */
22 | class ClickTool extends BaseBrowserToolExecutor {
23 | name = TOOL_NAMES.BROWSER.CLICK;
24 |
25 | /**
26 | * Execute click operation
27 | */
28 | async execute(args: ClickToolParams): Promise<ToolResult> {
29 | const {
30 | selector,
31 | coordinates,
32 | waitForNavigation = false,
33 | timeout = TIMEOUTS.DEFAULT_WAIT * 5,
34 | } = args;
35 |
36 | console.log(`Starting click operation with options:`, args);
37 |
38 | if (!selector && !coordinates) {
39 | return createErrorResponse(
40 | ERROR_MESSAGES.INVALID_PARAMETERS + ': Either selector or coordinates must be provided',
41 | );
42 | }
43 |
44 | try {
45 | // Get current tab
46 | const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
47 | if (!tabs[0]) {
48 | return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND);
49 | }
50 |
51 | const tab = tabs[0];
52 | if (!tab.id) {
53 | return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND + ': Active tab has no ID');
54 | }
55 |
56 | await this.injectContentScript(tab.id, ['inject-scripts/click-helper.js']);
57 |
58 | // Send click message to content script
59 | const result = await this.sendMessageToTab(tab.id, {
60 | action: TOOL_MESSAGE_TYPES.CLICK_ELEMENT,
61 | selector,
62 | coordinates,
63 | waitForNavigation,
64 | timeout,
65 | });
66 |
67 | return {
68 | content: [
69 | {
70 | type: 'text',
71 | text: JSON.stringify({
72 | success: true,
73 | message: result.message || 'Click operation successful',
74 | elementInfo: result.elementInfo,
75 | navigationOccurred: result.navigationOccurred,
76 | clickMethod: coordinates ? 'coordinates' : 'selector',
77 | }),
78 | },
79 | ],
80 | isError: false,
81 | };
82 | } catch (error) {
83 | console.error('Error in click operation:', error);
84 | return createErrorResponse(
85 | `Error performing click: ${error instanceof Error ? error.message : String(error)}`,
86 | );
87 | }
88 | }
89 | }
90 |
91 | export const clickTool = new ClickTool();
92 |
93 | interface FillToolParams {
94 | selector: string;
95 | value: string;
96 | }
97 |
98 | /**
99 | * Tool for filling form elements on web pages
100 | */
101 | class FillTool extends BaseBrowserToolExecutor {
102 | name = TOOL_NAMES.BROWSER.FILL;
103 |
104 | /**
105 | * Execute fill operation
106 | */
107 | async execute(args: FillToolParams): Promise<ToolResult> {
108 | const { selector, value } = args;
109 |
110 | console.log(`Starting fill operation with options:`, args);
111 |
112 | if (!selector) {
113 | return createErrorResponse(ERROR_MESSAGES.INVALID_PARAMETERS + ': Selector must be provided');
114 | }
115 |
116 | if (value === undefined || value === null) {
117 | return createErrorResponse(ERROR_MESSAGES.INVALID_PARAMETERS + ': Value must be provided');
118 | }
119 |
120 | try {
121 | // Get current tab
122 | const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
123 | if (!tabs[0]) {
124 | return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND);
125 | }
126 |
127 | const tab = tabs[0];
128 | if (!tab.id) {
129 | return createErrorResponse(ERROR_MESSAGES.TAB_NOT_FOUND + ': Active tab has no ID');
130 | }
131 |
132 | await this.injectContentScript(tab.id, ['inject-scripts/fill-helper.js']);
133 |
134 | // Send fill message to content script
135 | const result = await this.sendMessageToTab(tab.id, {
136 | action: TOOL_MESSAGE_TYPES.FILL_ELEMENT,
137 | selector,
138 | value,
139 | });
140 |
141 | if (result.error) {
142 | return createErrorResponse(result.error);
143 | }
144 |
145 | return {
146 | content: [
147 | {
148 | type: 'text',
149 | text: JSON.stringify({
150 | success: true,
151 | message: result.message || 'Fill operation successful',
152 | elementInfo: result.elementInfo,
153 | }),
154 | },
155 | ],
156 | isError: false,
157 | };
158 | } catch (error) {
159 | console.error('Error in fill operation:', error);
160 | return createErrorResponse(
161 | `Error filling element: ${error instanceof Error ? error.message : String(error)}`,
162 | );
163 | }
164 | }
165 | }
166 |
167 | export const fillTool = new FillTool();
168 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/inject-scripts/screenshot-helper.js:
--------------------------------------------------------------------------------
```javascript
1 | /* eslint-disable */
2 | /**
3 | * Screenshot helper content script
4 | * Handles page preparation, scrolling, element positioning, etc.
5 | */
6 |
7 | if (window.__SCREENSHOT_HELPER_INITIALIZED__) {
8 | // Already initialized, skip
9 | } else {
10 | window.__SCREENSHOT_HELPER_INITIALIZED__ = true;
11 |
12 | // Save original styles
13 | let originalOverflowStyle = '';
14 | let hiddenFixedElements = [];
15 |
16 | /**
17 | * Get fixed/sticky positioned elements
18 | * @returns Array of fixed/sticky elements
19 | */
20 | function getFixedElements() {
21 | const fixed = [];
22 |
23 | document.querySelectorAll('*').forEach((el) => {
24 | const htmlEl = el;
25 | const style = window.getComputedStyle(htmlEl);
26 | if (style.position === 'fixed' || style.position === 'sticky') {
27 | // Filter out tiny or invisible elements, and elements that are part of the extension UI
28 | if (
29 | htmlEl.offsetWidth > 1 &&
30 | htmlEl.offsetHeight > 1 &&
31 | !htmlEl.id.startsWith('chrome-mcp-')
32 | ) {
33 | fixed.push({
34 | element: htmlEl,
35 | originalDisplay: htmlEl.style.display,
36 | originalVisibility: htmlEl.style.visibility,
37 | });
38 | }
39 | }
40 | });
41 | return fixed;
42 | }
43 |
44 | /**
45 | * Hide fixed/sticky elements
46 | */
47 | function hideFixedElements() {
48 | hiddenFixedElements = getFixedElements();
49 | hiddenFixedElements.forEach((item) => {
50 | item.element.style.display = 'none';
51 | });
52 | }
53 |
54 | /**
55 | * Restore fixed/sticky elements
56 | */
57 | function showFixedElements() {
58 | hiddenFixedElements.forEach((item) => {
59 | item.element.style.display = item.originalDisplay || '';
60 | });
61 | hiddenFixedElements = [];
62 | }
63 |
64 | // Listen for messages from the extension
65 | chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
66 | // Respond to ping message
67 | if (request.action === 'chrome_screenshot_ping') {
68 | sendResponse({ status: 'pong' });
69 | return false; // Synchronous response
70 | }
71 |
72 | // Prepare page for capture
73 | else if (request.action === 'preparePageForCapture') {
74 | originalOverflowStyle = document.documentElement.style.overflow;
75 | document.documentElement.style.overflow = 'hidden'; // Hide main scrollbar
76 | if (request.options?.fullPage) {
77 | // Only hide fixed elements for full page to avoid flicker
78 | hideFixedElements();
79 | }
80 | // Give styles a moment to apply
81 | setTimeout(() => {
82 | sendResponse({ success: true });
83 | }, 50);
84 | return true; // Async response
85 | }
86 |
87 | // Get page details
88 | else if (request.action === 'getPageDetails') {
89 | const body = document.body;
90 | const html = document.documentElement;
91 | sendResponse({
92 | totalWidth: Math.max(
93 | body.scrollWidth,
94 | body.offsetWidth,
95 | html.clientWidth,
96 | html.scrollWidth,
97 | html.offsetWidth,
98 | ),
99 | totalHeight: Math.max(
100 | body.scrollHeight,
101 | body.offsetHeight,
102 | html.clientHeight,
103 | html.scrollHeight,
104 | html.offsetHeight,
105 | ),
106 | viewportWidth: window.innerWidth,
107 | viewportHeight: window.innerHeight,
108 | devicePixelRatio: window.devicePixelRatio || 1,
109 | currentScrollX: window.scrollX,
110 | currentScrollY: window.scrollY,
111 | });
112 | }
113 |
114 | // Get element details
115 | else if (request.action === 'getElementDetails') {
116 | const element = document.querySelector(request.selector);
117 | if (element) {
118 | element.scrollIntoView({ behavior: 'instant', block: 'nearest', inline: 'nearest' });
119 | setTimeout(() => {
120 | // Wait for scroll
121 | const rect = element.getBoundingClientRect();
122 | sendResponse({
123 | rect: { x: rect.left, y: rect.top, width: rect.width, height: rect.height },
124 | devicePixelRatio: window.devicePixelRatio || 1,
125 | });
126 | }, 200); // Increased delay for scrollIntoView
127 | return true; // Async response
128 | } else {
129 | sendResponse({ error: `Element with selector "${request.selector}" not found.` });
130 | }
131 | return true; // Async response
132 | }
133 |
134 | // Scroll page
135 | else if (request.action === 'scrollPage') {
136 | window.scrollTo({ left: request.x, top: request.y, behavior: 'instant' });
137 | // Wait for scroll and potential reflows/lazy-loading
138 | setTimeout(() => {
139 | sendResponse({
140 | success: true,
141 | newScrollX: window.scrollX,
142 | newScrollY: window.scrollY,
143 | });
144 | }, request.scrollDelay || 300); // Configurable delay
145 | return true; // Async response
146 | }
147 |
148 | // Reset page
149 | else if (request.action === 'resetPageAfterCapture') {
150 | document.documentElement.style.overflow = originalOverflowStyle;
151 | showFixedElements();
152 | if (typeof request.scrollX !== 'undefined' && typeof request.scrollY !== 'undefined') {
153 | window.scrollTo({ left: request.scrollX, top: request.scrollY, behavior: 'instant' });
154 | }
155 | sendResponse({ success: true });
156 | }
157 |
158 | return false; // Synchronous response
159 | });
160 | }
161 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/entrypoints/popup/components/ConfirmDialog.vue:
--------------------------------------------------------------------------------
```vue
1 | <template>
2 | <div v-if="visible" class="confirmation-dialog" @click.self="$emit('cancel')">
3 | <div class="dialog-content">
4 | <div class="dialog-header">
5 | <span class="dialog-icon">{{ icon }}</span>
6 | <h3 class="dialog-title">{{ title }}</h3>
7 | </div>
8 |
9 | <div class="dialog-body">
10 | <p class="dialog-message">{{ message }}</p>
11 |
12 | <ul v-if="items && items.length > 0" class="dialog-list">
13 | <li v-for="item in items" :key="item">{{ item }}</li>
14 | </ul>
15 |
16 | <div v-if="warning" class="dialog-warning">
17 | <strong>{{ warning }}</strong>
18 | </div>
19 | </div>
20 |
21 | <div class="dialog-actions">
22 | <button class="dialog-button cancel-button" @click="$emit('cancel')">
23 | {{ cancelText }}
24 | </button>
25 | <button
26 | class="dialog-button confirm-button"
27 | :disabled="isConfirming"
28 | @click="$emit('confirm')"
29 | >
30 | {{ isConfirming ? confirmingText : confirmText }}
31 | </button>
32 | </div>
33 | </div>
34 | </div>
35 | </template>
36 |
37 | <script lang="ts" setup>
38 | import { getMessage } from '@/utils/i18n';
39 | interface Props {
40 | visible: boolean;
41 | title: string;
42 | message: string;
43 | items?: string[];
44 | warning?: string;
45 | icon?: string;
46 | confirmText?: string;
47 | cancelText?: string;
48 | confirmingText?: string;
49 | isConfirming?: boolean;
50 | }
51 |
52 | interface Emits {
53 | (e: 'confirm'): void;
54 | (e: 'cancel'): void;
55 | }
56 |
57 | withDefaults(defineProps<Props>(), {
58 | icon: '⚠️',
59 | confirmText: getMessage('confirmButton'),
60 | cancelText: getMessage('cancelButton'),
61 | confirmingText: getMessage('processingStatus'),
62 | isConfirming: false,
63 | });
64 |
65 | defineEmits<Emits>();
66 | </script>
67 |
68 | <style scoped>
69 | .confirmation-dialog {
70 | position: fixed;
71 | top: 0;
72 | left: 0;
73 | right: 0;
74 | bottom: 0;
75 | background: rgba(0, 0, 0, 0.6);
76 | display: flex;
77 | align-items: center;
78 | justify-content: center;
79 | z-index: 1000;
80 | backdrop-filter: blur(8px);
81 | animation: dialogFadeIn 0.3s ease-out;
82 | }
83 |
84 | @keyframes dialogFadeIn {
85 | from {
86 | opacity: 0;
87 | backdrop-filter: blur(0px);
88 | }
89 | to {
90 | opacity: 1;
91 | backdrop-filter: blur(8px);
92 | }
93 | }
94 |
95 | .dialog-content {
96 | background: white;
97 | border-radius: 12px;
98 | padding: 24px;
99 | max-width: 360px;
100 | width: 90%;
101 | box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
102 | animation: dialogSlideIn 0.3s ease-out;
103 | border: 1px solid rgba(255, 255, 255, 0.2);
104 | }
105 |
106 | @keyframes dialogSlideIn {
107 | from {
108 | opacity: 0;
109 | transform: translateY(-30px) scale(0.9);
110 | }
111 | to {
112 | opacity: 1;
113 | transform: translateY(0) scale(1);
114 | }
115 | }
116 |
117 | .dialog-header {
118 | display: flex;
119 | align-items: center;
120 | gap: 12px;
121 | margin-bottom: 20px;
122 | }
123 |
124 | .dialog-icon {
125 | font-size: 24px;
126 | filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.1));
127 | }
128 |
129 | .dialog-title {
130 | font-size: 18px;
131 | font-weight: 600;
132 | color: #2d3748;
133 | margin: 0;
134 | }
135 |
136 | .dialog-body {
137 | margin-bottom: 24px;
138 | }
139 |
140 | .dialog-message {
141 | font-size: 14px;
142 | color: #4a5568;
143 | margin: 0 0 16px 0;
144 | line-height: 1.6;
145 | }
146 |
147 | .dialog-list {
148 | margin: 16px 0;
149 | padding-left: 24px;
150 | background: linear-gradient(135deg, #f7fafc, #edf2f7);
151 | border-radius: 6px;
152 | padding: 12px 12px 12px 32px;
153 | border-left: 3px solid #667eea;
154 | }
155 |
156 | .dialog-list li {
157 | font-size: 13px;
158 | color: #718096;
159 | margin-bottom: 6px;
160 | line-height: 1.4;
161 | }
162 |
163 | .dialog-list li:last-child {
164 | margin-bottom: 0;
165 | }
166 |
167 | .dialog-warning {
168 | font-size: 13px;
169 | color: #e53e3e;
170 | margin: 16px 0 0 0;
171 | padding: 12px;
172 | background: linear-gradient(135deg, rgba(245, 101, 101, 0.1), rgba(229, 62, 62, 0.05));
173 | border-radius: 6px;
174 | border-left: 3px solid #e53e3e;
175 | border: 1px solid rgba(245, 101, 101, 0.2);
176 | }
177 |
178 | .dialog-actions {
179 | display: flex;
180 | gap: 12px;
181 | justify-content: flex-end;
182 | }
183 |
184 | .dialog-button {
185 | padding: 10px 20px;
186 | border: none;
187 | border-radius: 8px;
188 | font-size: 14px;
189 | font-weight: 500;
190 | cursor: pointer;
191 | transition: all 0.3s ease;
192 | min-width: 80px;
193 | }
194 |
195 | .cancel-button {
196 | background: linear-gradient(135deg, #e2e8f0, #cbd5e0);
197 | color: #4a5568;
198 | border: 1px solid #cbd5e0;
199 | }
200 |
201 | .cancel-button:hover {
202 | background: linear-gradient(135deg, #cbd5e0, #a0aec0);
203 | transform: translateY(-1px);
204 | box-shadow: 0 4px 12px rgba(160, 174, 192, 0.3);
205 | }
206 |
207 | .confirm-button {
208 | background: linear-gradient(135deg, #f56565, #e53e3e);
209 | color: white;
210 | border: 1px solid #e53e3e;
211 | }
212 |
213 | .confirm-button:hover:not(:disabled) {
214 | background: linear-gradient(135deg, #e53e3e, #c53030);
215 | transform: translateY(-1px);
216 | box-shadow: 0 4px 12px rgba(245, 101, 101, 0.4);
217 | }
218 |
219 | .confirm-button:disabled {
220 | opacity: 0.7;
221 | cursor: not-allowed;
222 | transform: none;
223 | box-shadow: none;
224 | }
225 |
226 | /* 响应式设计 */
227 | @media (max-width: 420px) {
228 | .dialog-content {
229 | padding: 20px;
230 | max-width: 320px;
231 | }
232 |
233 | .dialog-header {
234 | gap: 10px;
235 | margin-bottom: 16px;
236 | }
237 |
238 | .dialog-icon {
239 | font-size: 20px;
240 | }
241 |
242 | .dialog-title {
243 | font-size: 16px;
244 | }
245 |
246 | .dialog-message {
247 | font-size: 13px;
248 | }
249 |
250 | .dialog-list {
251 | padding: 10px 10px 10px 28px;
252 | }
253 |
254 | .dialog-list li {
255 | font-size: 12px;
256 | }
257 |
258 | .dialog-warning {
259 | font-size: 12px;
260 | padding: 10px;
261 | }
262 |
263 | .dialog-actions {
264 | gap: 8px;
265 | flex-direction: column-reverse;
266 | }
267 |
268 | .dialog-button {
269 | width: 100%;
270 | padding: 12px 16px;
271 | }
272 | }
273 |
274 | /* 焦点样式 */
275 | .dialog-button:focus {
276 | outline: none;
277 | box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
278 | }
279 |
280 | .cancel-button:focus {
281 | box-shadow: 0 0 0 3px rgba(160, 174, 192, 0.3);
282 | }
283 |
284 | .confirm-button:focus {
285 | box-shadow: 0 0 0 3px rgba(245, 101, 101, 0.3);
286 | }
287 | </style>
288 |
```
--------------------------------------------------------------------------------
/docs/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # Changelog
2 |
3 | All notable changes to this project will be documented in this file.
4 |
5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7 |
8 | ## [v0.0.5]
9 |
10 | ### Improved
11 |
12 | - **Image Compression**: Compress base64 images when using screenshot tool
13 | - **Interactive Elements Detection Optimization**: Enhanced interactive elements detection tool with expanded search scope, now supports finding interactive div elements
14 |
15 | ## [v0.0.4]
16 |
17 | ### Added
18 |
19 | - **STDIO Connection Support**: Added support for connecting to the MCP server via standard input/output (stdio) method
20 | - **Console Output Capture Tool**: New `chrome_console` tool for capturing browser console output
21 |
22 | ## [v0.0.3]
23 |
24 | ### Added
25 |
26 | - **Inject script tool**: For injecting content scripts into web page
27 | - **Send command to inject script tool**: For sending commands to the injected script
28 |
29 | ## [v0.0.2]
30 |
31 | ### Added
32 |
33 | - **Conditional Semantic Engine Initialization**: Smart cache-based initialization that only loads models when cached versions are available
34 | - **Enhanced Model Cache Management**: Comprehensive cache management system with automatic cleanup and size limits
35 | - **Windows Platform Compatibility**: Full support for Windows Chrome Native Messaging with registry-based manifest detection
36 | - **Cache Statistics and Manual Management**: User interface for viewing cache stats and manual cache cleanup
37 | - **Concurrent Initialization Protection**: Prevents duplicate initialization attempts across components
38 |
39 | ### Improved
40 |
41 | - **Startup Performance**: Dramatically reduced startup time when no model cache exists (from ~3s to ~0.5s)
42 | - **Memory Usage**: Optimized memory consumption through on-demand model loading
43 | - **Cache Expiration Logic**: Intelligent cache expiration (14 days) with automatic cleanup
44 | - **Error Handling**: Enhanced error handling for model initialization failures
45 | - **Component Coordination**: Simplified initialization flow between semantic engine and content indexer
46 |
47 | ### Fixed
48 |
49 | - **Windows Native Host Issues**: Resolved Node.js environment conflicts with multiple NVM installations
50 | - **Race Condition Prevention**: Eliminated concurrent initialization attempts that could cause conflicts
51 | - **Cache Size Management**: Automatic cleanup when cache exceeds 500MB limit
52 | - **Model Download Optimization**: Prevents unnecessary model downloads during plugin startup
53 |
54 | ### Technical Improvements
55 |
56 | - **ModelCacheManager**: Added `isModelCached()` and `hasAnyValidCache()` methods for cache detection
57 | - **SemanticSimilarityEngine**: Added cache checking functions and conditional initialization logic
58 | - **Background Script**: Implemented smart initialization based on cache availability
59 | - **VectorSearchTool**: Simplified to passive initialization model
60 | - **ContentIndexer**: Enhanced with semantic engine readiness checks
61 |
62 | ### Documentation
63 |
64 | - Added comprehensive conditional initialization documentation
65 | - Updated cache management system documentation
66 | - Created troubleshooting guides for Windows platform issues
67 |
68 | ## [v0.0.1]
69 |
70 | ### Added
71 |
72 | - **Core Browser Tools**: Complete set of browser automation tools for web interaction
73 |
74 | - **Click Tool**: Intelligent element clicking with coordinate and selector support
75 | - **Fill Tool**: Form filling with text input and selection capabilities
76 | - **Screenshot Tool**: Full page and element-specific screenshot capture
77 | - **Navigation Tools**: URL navigation and page interaction utilities
78 | - **Keyboard Tool**: Keyboard input simulation and hotkey support
79 |
80 | - **Vector Search Engine**: Advanced semantic search capabilities
81 |
82 | - **Content Indexing**: Automatic indexing of browser tab content
83 | - **Semantic Similarity**: AI-powered text similarity matching
84 | - **Vector Database**: Efficient storage and retrieval of embeddings
85 | - **Multi-language Support**: Comprehensive multilingual text processing
86 |
87 | - **Native Host Integration**: Seamless communication with external applications
88 |
89 | - **Chrome Native Messaging**: Bidirectional communication channel
90 | - **Cross-platform Support**: Windows, macOS, and Linux compatibility
91 | - **Message Protocol**: Structured messaging system for tool execution
92 |
93 | - **AI Model Integration**: State-of-the-art language models for semantic processing
94 |
95 | - **Transformer Models**: Support for multiple pre-trained models
96 | - **ONNX Runtime**: Optimized model inference with WebAssembly
97 | - **Model Management**: Dynamic model loading and switching
98 | - **Performance Optimization**: SIMD acceleration and memory pooling
99 |
100 | - **User Interface**: Intuitive popup interface for extension management
101 | - **Model Selection**: Easy switching between different AI models
102 | - **Status Monitoring**: Real-time initialization and download progress
103 | - **Settings Management**: User preferences and configuration options
104 | - **Cache Management**: Visual cache statistics and cleanup controls
105 |
106 | ### Technical Foundation
107 |
108 | - **Extension Architecture**: Robust Chrome extension with background scripts and content injection
109 | - **Worker-based Processing**: Offscreen document for heavy computational tasks
110 | - **Memory Management**: LRU caching and efficient resource utilization
111 | - **Error Handling**: Comprehensive error reporting and recovery mechanisms
112 | - **TypeScript Implementation**: Full type safety and modern JavaScript features
113 |
114 | ### Initial Features
115 |
116 | - Multi-tab content analysis and search
117 | - Real-time semantic similarity computation
118 | - Automated web page interaction
119 | - Cross-platform native messaging
120 | - Extensible tool framework for future enhancements
121 |
```
--------------------------------------------------------------------------------
/app/chrome-extension/inject-scripts/fill-helper.js:
--------------------------------------------------------------------------------
```javascript
1 | /* eslint-disable */
2 | // fill-helper.js
3 | // This script is injected into the page to handle form filling operations
4 |
5 | if (window.__FILL_HELPER_INITIALIZED__) {
6 | // Already initialized, skip
7 | } else {
8 | window.__FILL_HELPER_INITIALIZED__ = true;
9 | /**
10 | * Fill an input element with the specified value
11 | * @param {string} selector - CSS selector for the element to fill
12 | * @param {string} value - Value to fill into the element
13 | * @returns {Promise<Object>} - Result of the fill operation
14 | */
15 | async function fillElement(selector, value) {
16 | try {
17 | // Find the element
18 | const element = document.querySelector(selector);
19 | if (!element) {
20 | return {
21 | error: `Element with selector "${selector}" not found`,
22 | };
23 | }
24 |
25 | // Get element information
26 | const rect = element.getBoundingClientRect();
27 | const elementInfo = {
28 | tagName: element.tagName,
29 | id: element.id,
30 | className: element.className,
31 | type: element.type || null,
32 | isVisible: isElementVisible(element),
33 | rect: {
34 | x: rect.x,
35 | y: rect.y,
36 | width: rect.width,
37 | height: rect.height,
38 | top: rect.top,
39 | right: rect.right,
40 | bottom: rect.bottom,
41 | left: rect.left,
42 | },
43 | };
44 |
45 | // Check if element is visible
46 | if (!elementInfo.isVisible) {
47 | return {
48 | error: `Element with selector "${selector}" is not visible`,
49 | elementInfo,
50 | };
51 | }
52 |
53 | // Check if element is an input, textarea, or select
54 | const validTags = ['INPUT', 'TEXTAREA', 'SELECT'];
55 | const validInputTypes = [
56 | 'text',
57 | 'email',
58 | 'password',
59 | 'number',
60 | 'search',
61 | 'tel',
62 | 'url',
63 | 'date',
64 | 'datetime-local',
65 | 'month',
66 | 'time',
67 | 'week',
68 | 'color',
69 | ];
70 |
71 | if (!validTags.includes(element.tagName)) {
72 | return {
73 | error: `Element with selector "${selector}" is not a fillable element (must be INPUT, TEXTAREA, or SELECT)`,
74 | elementInfo,
75 | };
76 | }
77 |
78 | // For input elements, check if the type is valid
79 | if (
80 | element.tagName === 'INPUT' &&
81 | !validInputTypes.includes(element.type) &&
82 | element.type !== null
83 | ) {
84 | return {
85 | error: `Input element with selector "${selector}" has type "${element.type}" which is not fillable`,
86 | elementInfo,
87 | };
88 | }
89 |
90 | // Scroll element into view
91 | element.scrollIntoView({ behavior: 'auto', block: 'center', inline: 'center' });
92 | await new Promise((resolve) => setTimeout(resolve, 100));
93 |
94 | // Focus the element
95 | element.focus();
96 |
97 | // Fill the element based on its type
98 | if (element.tagName === 'SELECT') {
99 | // For select elements, find the option with matching value or text
100 | let optionFound = false;
101 | for (const option of element.options) {
102 | if (option.value === value || option.text === value) {
103 | element.value = option.value;
104 | optionFound = true;
105 | break;
106 | }
107 | }
108 |
109 | if (!optionFound) {
110 | return {
111 | error: `No option with value or text "${value}" found in select element`,
112 | elementInfo,
113 | };
114 | }
115 |
116 | // Trigger change event
117 | element.dispatchEvent(new Event('change', { bubbles: true }));
118 | } else {
119 | // For input and textarea elements
120 |
121 | // Clear the current value
122 | element.value = '';
123 | element.dispatchEvent(new Event('input', { bubbles: true }));
124 |
125 | // Set the new value
126 | element.value = value;
127 |
128 | // Trigger input and change events
129 | element.dispatchEvent(new Event('input', { bubbles: true }));
130 | element.dispatchEvent(new Event('change', { bubbles: true }));
131 | }
132 |
133 | // Blur the element
134 | element.blur();
135 |
136 | return {
137 | success: true,
138 | message: 'Element filled successfully',
139 | elementInfo: {
140 | ...elementInfo,
141 | value: element.value, // Include the final value in the response
142 | },
143 | };
144 | } catch (error) {
145 | return {
146 | error: `Error filling element: ${error.message}`,
147 | };
148 | }
149 | }
150 |
151 | /**
152 | * Check if an element is visible
153 | * @param {Element} element - The element to check
154 | * @returns {boolean} - Whether the element is visible
155 | */
156 | function isElementVisible(element) {
157 | if (!element) return false;
158 |
159 | const style = window.getComputedStyle(element);
160 | if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
161 | return false;
162 | }
163 |
164 | const rect = element.getBoundingClientRect();
165 | if (rect.width === 0 || rect.height === 0) {
166 | return false;
167 | }
168 |
169 | // Check if element is within viewport
170 | if (
171 | rect.bottom < 0 ||
172 | rect.top > window.innerHeight ||
173 | rect.right < 0 ||
174 | rect.left > window.innerWidth
175 | ) {
176 | return false;
177 | }
178 |
179 | // Check if element is actually visible at its center point
180 | const centerX = rect.left + rect.width / 2;
181 | const centerY = rect.top + rect.height / 2;
182 |
183 | const elementAtPoint = document.elementFromPoint(centerX, centerY);
184 | if (!elementAtPoint) return false;
185 |
186 | return element === elementAtPoint || element.contains(elementAtPoint);
187 | }
188 |
189 | // Listen for messages from the extension
190 | chrome.runtime.onMessage.addListener((request, _sender, sendResponse) => {
191 | if (request.action === 'fillElement') {
192 | fillElement(request.selector, request.value)
193 | .then(sendResponse)
194 | .catch((error) => {
195 | sendResponse({
196 | error: `Unexpected error: ${error.message}`,
197 | });
198 | });
199 | return true; // Indicates async response
200 | } else if (request.action === 'chrome_fill_or_select_ping') {
201 | sendResponse({ status: 'pong' });
202 | return false;
203 | }
204 | });
205 | }
206 |
```