chrome-devtools-mcp-for-extension 0.25.8 → 0.26.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -169,9 +169,12 @@ vim src/tools/extensions.ts
169
169
  chrome-devtools-mcp/
170
170
  ├── src/
171
171
  │ ├── tools/ # MCP tool definitions
172
- │ │ ├── extensions.ts # Extension management (list, reload, debug)
172
+ │ │ ├── core-tools.ts # Core tool exports (v0.26.0)
173
+ │ │ ├── optional-tools.ts # Web-LLM tool exports (v0.26.0)
173
174
  │ │ ├── chatgpt-web.ts # ChatGPT automation
175
+ │ │ ├── gemini-web.ts # Gemini automation
174
176
  │ │ └── ...
177
+ │ ├── plugin-api.ts # Plugin architecture (v0.26.0)
175
178
  │ ├── browser.ts # Browser/profile management
176
179
  │ ├── main.ts # MCP server entry point
177
180
  │ └── graceful.ts # Graceful shutdown
@@ -264,7 +267,8 @@ git push && git push --tags
264
267
  - 🔧 **Browser Testing**: Test extensions in real user environments
265
268
  - 🐛 **Advanced Debugging**: Service worker inspection, console monitoring
266
269
  - 📸 **Screenshot Generation**: Auto-create store listing images
267
- - 🤖 **ChatGPT Integration**: Automated ChatGPT interactions for research
270
+ - 🤖 **ChatGPT/Gemini Integration**: Automated AI interactions for research
271
+ - 🔌 **Plugin Architecture** (v0.26.0): Extensible tool system with external plugins
268
272
 
269
273
  ---
270
274
 
@@ -376,6 +380,85 @@ pkill -f mcp-wrapper
376
380
 
377
381
  ---
378
382
 
383
+ ## 🔌 Plugin Architecture (v0.26.0)
384
+
385
+ ### Tool Categories
386
+
387
+ **Core Tools (18)** - Stable, site-independent:
388
+ - Input: click, hover, fill, drag, fill_form, upload_file
389
+ - Navigation: pages, navigate, resize_page, handle_dialog
390
+ - Debugging: list_console_messages, take_screenshot, evaluate_script, take_snapshot, wait_for
391
+ - Analysis: emulate, network, performance
392
+
393
+ **Optional Tools (2)** - Web-LLM, site-dependent (may break with UI changes):
394
+ - `ask_chatgpt_web` - ChatGPT browser automation
395
+ - `ask_gemini_web` - Gemini browser automation
396
+
397
+ ### Disable Web-LLM Tools
398
+
399
+ If you don't need ChatGPT/Gemini integration:
400
+
401
+ ```json
402
+ {
403
+ "mcpServers": {
404
+ "chrome-devtools-extension": {
405
+ "command": "npx",
406
+ "args": ["chrome-devtools-mcp-for-extension@latest"],
407
+ "env": {
408
+ "MCP_DISABLE_WEB_LLM": "true"
409
+ }
410
+ }
411
+ }
412
+ }
413
+ ```
414
+
415
+ ### External Plugins
416
+
417
+ Load custom plugins at startup:
418
+
419
+ ```json
420
+ {
421
+ "mcpServers": {
422
+ "chrome-devtools-extension": {
423
+ "command": "npx",
424
+ "args": ["chrome-devtools-mcp-for-extension@latest"],
425
+ "env": {
426
+ "MCP_PLUGINS": "./my-plugin.js,@org/another-plugin"
427
+ }
428
+ }
429
+ }
430
+ }
431
+ ```
432
+
433
+ **Plugin Interface:**
434
+ ```typescript
435
+ // my-plugin.js
436
+ export default {
437
+ id: 'my-plugin',
438
+ name: 'My Custom Plugin',
439
+ version: '1.0.0',
440
+
441
+ async register(ctx) {
442
+ ctx.registry.register({
443
+ name: 'my_custom_tool',
444
+ description: 'Does something useful',
445
+ schema: { /* zod schema */ },
446
+ annotations: { category: 'automation' },
447
+ async handler(input, response, context) {
448
+ // Tool implementation
449
+ }
450
+ });
451
+ ctx.log('Plugin registered!');
452
+ },
453
+
454
+ async unload() {
455
+ // Cleanup if needed
456
+ }
457
+ };
458
+ ```
459
+
460
+ ---
461
+
379
462
  ## 🙏 Credits
380
463
 
381
464
  This project is a fork of [Chrome DevTools MCP](https://github.com/ChromeDevTools/chrome-devtools-mcp) by Google LLC.
@@ -393,5 +476,5 @@ This project is a fork of [Chrome DevTools MCP](https://github.com/ChromeDevTool
393
476
 
394
477
  Apache-2.0
395
478
 
396
- **Version**: 0.18.0
479
+ **Version**: 0.26.0
397
480
  **Repository**: https://github.com/usedhonda/chrome-devtools-mcp
package/build/src/main.js CHANGED
@@ -46,17 +46,9 @@ import { McpResponse } from './McpResponse.js';
46
46
  import { Mutex } from './Mutex.js';
47
47
  import { setProjectRoot } from './project-root-state.js';
48
48
  import { resolveRoots } from './roots-manager.js';
49
- import * as chatgptWebTools from './tools/chatgpt-web.js';
50
- import * as consoleTools from './tools/console.js';
51
- import * as emulationTools from './tools/emulation.js';
52
- import * as geminiWebTools from './tools/gemini-web.js';
53
- import { click, fill, fillForm } from './tools/input.js';
54
- import * as networkTools from './tools/network.js';
55
- import { pages, navigate } from './tools/pages.js';
56
- import * as performanceTools from './tools/performance.js';
57
- import * as screenshotTools from './tools/screenshot.js';
58
- import * as scriptTools from './tools/script.js';
59
- import * as snapshotTools from './tools/snapshot.js';
49
+ import { ToolRegistry, PluginLoader } from './plugin-api.js';
50
+ import { registerCoreTools, getCoreToolCount } from './tools/core-tools.js';
51
+ import { registerOptionalTools, WEB_LLM_TOOLS_INFO } from './tools/optional-tools.js';
60
52
  function readPackageJson() {
61
53
  const currentDir = import.meta.dirname;
62
54
  const packageJsonPath = path.join(currentDir, '..', '..', 'package.json');
@@ -237,25 +229,33 @@ function registerTool(tool) {
237
229
  }
238
230
  });
239
231
  }
240
- const tools = [
241
- ...Object.values(chatgptWebTools),
242
- ...Object.values(geminiWebTools),
243
- ...Object.values(consoleTools),
244
- ...Object.values(emulationTools),
245
- click,
246
- fill,
247
- fillForm,
248
- ...Object.values(networkTools),
249
- pages,
250
- navigate,
251
- ...Object.values(performanceTools),
252
- ...Object.values(screenshotTools),
253
- ...Object.values(scriptTools),
254
- ...Object.values(snapshotTools),
255
- ];
256
- for (const tool of tools) {
232
+ // v0.26.0: Use ToolRegistry for plugin architecture
233
+ const toolRegistry = new ToolRegistry();
234
+ // Register core tools (stable, site-independent)
235
+ registerCoreTools(toolRegistry);
236
+ logger(`[tools] Registered ${getCoreToolCount()} core tools`);
237
+ // Register optional tools (web-llm, site-dependent)
238
+ const optionalCount = registerOptionalTools(toolRegistry);
239
+ if (optionalCount > 0) {
240
+ logger(`[tools] ${WEB_LLM_TOOLS_INFO.disclaimer}`);
241
+ }
242
+ // Load external plugins from MCP_PLUGINS environment variable
243
+ const pluginList = process.env.MCP_PLUGINS;
244
+ if (pluginList) {
245
+ const pluginLoader = new PluginLoader(toolRegistry, logger);
246
+ const { loaded, failed } = await pluginLoader.loadFromList(pluginList);
247
+ if (loaded.length > 0) {
248
+ logger(`[plugins] Successfully loaded: ${loaded.join(', ')}`);
249
+ }
250
+ if (failed.length > 0) {
251
+ logger(`[plugins] Failed to load: ${failed.join(', ')}`);
252
+ }
253
+ }
254
+ // Register all tools with MCP server
255
+ for (const tool of toolRegistry.getAll()) {
257
256
  registerTool(tool);
258
257
  }
258
+ logger(`[tools] Total registered: ${toolRegistry.size} tools`);
259
259
  // Set initialization callback
260
260
  server.server.oninitialized = () => {
261
261
  initializationComplete = true;
@@ -0,0 +1,189 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ /**
7
+ * Registry for managing MCP tools.
8
+ * Allows dynamic registration and querying of tools.
9
+ */
10
+ export class ToolRegistry {
11
+ tools = new Map();
12
+ categories = new Map();
13
+ /**
14
+ * Register a single tool.
15
+ * @throws Error if a tool with the same name already exists
16
+ */
17
+ register(tool) {
18
+ if (this.tools.has(tool.name)) {
19
+ throw new Error(`Tool "${tool.name}" is already registered`);
20
+ }
21
+ this.tools.set(tool.name, tool);
22
+ // Track by category
23
+ const category = tool.annotations.category;
24
+ if (!this.categories.has(category)) {
25
+ this.categories.set(category, new Set());
26
+ }
27
+ this.categories.get(category).add(tool.name);
28
+ }
29
+ /**
30
+ * Register multiple tools at once.
31
+ */
32
+ registerBatch(tools) {
33
+ for (const tool of tools) {
34
+ this.register(tool);
35
+ }
36
+ }
37
+ /**
38
+ * Get all registered tools.
39
+ */
40
+ getAll() {
41
+ return Array.from(this.tools.values());
42
+ }
43
+ /**
44
+ * Get tools by category.
45
+ */
46
+ getByCategory(category) {
47
+ const names = this.categories.get(category);
48
+ if (!names)
49
+ return [];
50
+ return Array.from(names)
51
+ .map((name) => this.tools.get(name))
52
+ .filter(Boolean);
53
+ }
54
+ /**
55
+ * Get a specific tool by name.
56
+ */
57
+ get(name) {
58
+ return this.tools.get(name);
59
+ }
60
+ /**
61
+ * Check if a tool is registered.
62
+ */
63
+ has(name) {
64
+ return this.tools.has(name);
65
+ }
66
+ /**
67
+ * Get count of registered tools.
68
+ */
69
+ get size() {
70
+ return this.tools.size;
71
+ }
72
+ /**
73
+ * Unregister a tool by name.
74
+ * Returns true if the tool was removed, false if it didn't exist.
75
+ */
76
+ unregister(name) {
77
+ const tool = this.tools.get(name);
78
+ if (!tool)
79
+ return false;
80
+ this.tools.delete(name);
81
+ const category = tool.annotations.category;
82
+ this.categories.get(category)?.delete(name);
83
+ return true;
84
+ }
85
+ /**
86
+ * Clear all registered tools.
87
+ */
88
+ clear() {
89
+ this.tools.clear();
90
+ this.categories.clear();
91
+ }
92
+ }
93
+ /**
94
+ * Plugin loader for dynamically loading plugins.
95
+ */
96
+ export class PluginLoader {
97
+ plugins = new Map();
98
+ registry;
99
+ log;
100
+ config;
101
+ constructor(registry, log = console.error, config = {}) {
102
+ this.registry = registry;
103
+ this.log = log;
104
+ this.config = config;
105
+ }
106
+ /**
107
+ * Load a plugin from a module path or package name.
108
+ * @param moduleId - Path to module or npm package name
109
+ */
110
+ async load(moduleId) {
111
+ try {
112
+ this.log(`[plugins] Loading plugin: ${moduleId}`);
113
+ // Dynamic import
114
+ const module = await import(moduleId);
115
+ const plugin = module.default || module.plugin || module;
116
+ if (!plugin.id || !plugin.register) {
117
+ this.log(`[plugins] Invalid plugin (missing id or register): ${moduleId}`);
118
+ return false;
119
+ }
120
+ if (this.plugins.has(plugin.id)) {
121
+ this.log(`[plugins] Plugin already loaded: ${plugin.id}`);
122
+ return false;
123
+ }
124
+ // Create plugin context
125
+ const ctx = {
126
+ registry: this.registry,
127
+ log: (msg) => this.log(`[${plugin.id}] ${msg}`),
128
+ config: this.config,
129
+ };
130
+ // Register the plugin
131
+ await plugin.register(ctx);
132
+ this.plugins.set(plugin.id, plugin);
133
+ this.log(`[plugins] Loaded: ${plugin.name} v${plugin.version} (${plugin.id})`);
134
+ return true;
135
+ }
136
+ catch (error) {
137
+ this.log(`[plugins] Failed to load ${moduleId}: ${error instanceof Error ? error.message : String(error)}`);
138
+ return false;
139
+ }
140
+ }
141
+ /**
142
+ * Load multiple plugins from environment variable or config.
143
+ * @param pluginIds - Comma-separated list of plugin module IDs
144
+ */
145
+ async loadFromList(pluginIds) {
146
+ const ids = pluginIds
147
+ .split(',')
148
+ .map((id) => id.trim())
149
+ .filter(Boolean);
150
+ const loaded = [];
151
+ const failed = [];
152
+ for (const id of ids) {
153
+ const success = await this.load(id);
154
+ if (success) {
155
+ loaded.push(id);
156
+ }
157
+ else {
158
+ failed.push(id);
159
+ }
160
+ }
161
+ return { loaded, failed };
162
+ }
163
+ /**
164
+ * Unload a plugin by ID.
165
+ */
166
+ async unload(pluginId) {
167
+ const plugin = this.plugins.get(pluginId);
168
+ if (!plugin)
169
+ return false;
170
+ try {
171
+ if (plugin.unload) {
172
+ await plugin.unload();
173
+ }
174
+ this.plugins.delete(pluginId);
175
+ this.log(`[plugins] Unloaded: ${pluginId}`);
176
+ return true;
177
+ }
178
+ catch (error) {
179
+ this.log(`[plugins] Failed to unload ${pluginId}: ${error instanceof Error ? error.message : String(error)}`);
180
+ return false;
181
+ }
182
+ }
183
+ /**
184
+ * Get list of loaded plugins.
185
+ */
186
+ getLoaded() {
187
+ return Array.from(this.plugins.values());
188
+ }
189
+ }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ // Input tools
7
+ import { click, hover, fill, drag, fillForm, uploadFile } from './input.js';
8
+ // Navigation tools
9
+ import { pages, navigate, resizePage, handleDialog } from './pages.js';
10
+ // Console tools
11
+ import * as consoleTools from './console.js';
12
+ // Emulation tools
13
+ import * as emulationTools from './emulation.js';
14
+ // Network tools
15
+ import * as networkTools from './network.js';
16
+ // Performance tools
17
+ import * as performanceTools from './performance.js';
18
+ // Screenshot tools
19
+ import * as screenshotTools from './screenshot.js';
20
+ // Script tools
21
+ import * as scriptTools from './script.js';
22
+ // Snapshot tools
23
+ import * as snapshotTools from './snapshot.js';
24
+ /**
25
+ * All core tools as an array.
26
+ */
27
+ export const coreTools = [
28
+ // Input automation
29
+ click,
30
+ hover,
31
+ fill,
32
+ drag,
33
+ fillForm,
34
+ uploadFile,
35
+ // Navigation
36
+ pages,
37
+ navigate,
38
+ resizePage,
39
+ handleDialog,
40
+ // Console
41
+ ...Object.values(consoleTools),
42
+ // Emulation
43
+ ...Object.values(emulationTools),
44
+ // Network
45
+ ...Object.values(networkTools),
46
+ // Performance
47
+ ...Object.values(performanceTools),
48
+ // Screenshot
49
+ ...Object.values(screenshotTools),
50
+ // Script
51
+ ...Object.values(scriptTools),
52
+ // Snapshot
53
+ ...Object.values(snapshotTools),
54
+ ];
55
+ /**
56
+ * Register all core tools with a ToolRegistry.
57
+ */
58
+ export function registerCoreTools(registry) {
59
+ for (const tool of coreTools) {
60
+ // Skip non-tool exports (like constants)
61
+ if (tool && typeof tool === 'object' && 'name' in tool && 'handler' in tool) {
62
+ registry.register(tool);
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Get count of core tools.
68
+ */
69
+ export function getCoreToolCount() {
70
+ return coreTools.filter((tool) => tool && typeof tool === 'object' && 'name' in tool && 'handler' in tool).length;
71
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Google LLC
4
+ * SPDX-License-Identifier: Apache-2.0
5
+ */
6
+ // ChatGPT web tools
7
+ import * as chatgptWebTools from './chatgpt-web.js';
8
+ // Gemini web tools
9
+ import * as geminiWebTools from './gemini-web.js';
10
+ /**
11
+ * All optional (web-llm) tools as an array.
12
+ */
13
+ export const optionalTools = [
14
+ ...Object.values(chatgptWebTools),
15
+ ...Object.values(geminiWebTools),
16
+ ];
17
+ /**
18
+ * Check if web-llm tools should be loaded.
19
+ * Returns false if MCP_DISABLE_WEB_LLM is set to 'true'.
20
+ */
21
+ export function shouldLoadWebLlmTools() {
22
+ const disable = process.env.MCP_DISABLE_WEB_LLM;
23
+ return disable !== 'true' && disable !== '1';
24
+ }
25
+ /**
26
+ * Register optional tools with a ToolRegistry.
27
+ * Respects MCP_DISABLE_WEB_LLM environment variable.
28
+ */
29
+ export function registerOptionalTools(registry) {
30
+ if (!shouldLoadWebLlmTools()) {
31
+ console.error('[tools] Web-LLM tools disabled via MCP_DISABLE_WEB_LLM');
32
+ return 0;
33
+ }
34
+ let count = 0;
35
+ for (const tool of optionalTools) {
36
+ // Skip non-tool exports (like constants)
37
+ if (tool && typeof tool === 'object' && 'name' in tool && 'handler' in tool) {
38
+ try {
39
+ registry.register(tool);
40
+ count++;
41
+ }
42
+ catch (error) {
43
+ // Log but don't fail - optional tools should not block startup
44
+ console.error(`[tools] Failed to register optional tool: ${error instanceof Error ? error.message : String(error)}`);
45
+ }
46
+ }
47
+ }
48
+ if (count > 0) {
49
+ console.error(`[tools] Loaded ${count} optional web-llm tools (experimental, may break)`);
50
+ }
51
+ return count;
52
+ }
53
+ /**
54
+ * Get count of optional tools.
55
+ */
56
+ export function getOptionalToolCount() {
57
+ return optionalTools.filter((tool) => tool && typeof tool === 'object' && 'name' in tool && 'handler' in tool).length;
58
+ }
59
+ /**
60
+ * Metadata about optional tools for documentation.
61
+ */
62
+ export const WEB_LLM_TOOLS_INFO = {
63
+ disclaimer: 'Web-LLM tools (ask_chatgpt_web, ask_gemini_web) are experimental and best-effort. ' +
64
+ 'They depend on specific website UIs and may break when those UIs change. ' +
65
+ 'For production use, consider using official APIs instead.',
66
+ disableEnvVar: 'MCP_DISABLE_WEB_LLM',
67
+ tools: ['ask_chatgpt_web', 'ask_gemini_web'],
68
+ };
package/package.json CHANGED
@@ -1,10 +1,14 @@
1
1
  {
2
2
  "name": "chrome-devtools-mcp-for-extension",
3
- "version": "0.25.8",
3
+ "version": "0.26.1",
4
4
  "description": "MCP server for Chrome extension development with Web Store automation. Fork of chrome-devtools-mcp with extension-specific tools.",
5
5
  "type": "module",
6
6
  "bin": "./scripts/cli.mjs",
7
7
  "main": "index.js",
8
+ "exports": {
9
+ ".": "./index.js",
10
+ "./plugin-api": "./build/src/plugin-api.js"
11
+ },
8
12
  "scripts": {
9
13
  "build": "tsc && node --experimental-strip-types --no-warnings=ExperimentalWarning scripts/post-build.ts",
10
14
  "dev": "MCP_ENV=development node scripts/mcp-wrapper.mjs",