chrome-devtools-mcp-for-extension 0.25.7 → 0.26.0
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/build/src/main.js +28 -28
- package/build/src/plugin-api.js +189 -0
- package/build/src/profile-resolver.js +64 -28
- package/build/src/tools/core-tools.js +71 -0
- package/build/src/tools/optional-tools.js +68 -0
- package/package.json +1 -1
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
|
|
50
|
-
import
|
|
51
|
-
import
|
|
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
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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
|
+
}
|
|
@@ -18,6 +18,7 @@ import crypto from 'node:crypto';
|
|
|
18
18
|
import fs from 'node:fs';
|
|
19
19
|
import os from 'node:os';
|
|
20
20
|
import path from 'node:path';
|
|
21
|
+
import { fileURLToPath } from 'node:url';
|
|
21
22
|
import { detectClientType } from './client-detector.js';
|
|
22
23
|
import { detectProjectName, detectProjectRoot } from './project-detector.js';
|
|
23
24
|
import { getProjectRoot } from './project-root-state.js';
|
|
@@ -30,49 +31,84 @@ export function resolveUserDataDir(opts) {
|
|
|
30
31
|
if (opts.rootsInfo) {
|
|
31
32
|
const stableProfilePath = path.join(CACHE_ROOT, 'profiles', opts.rootsInfo.profileKey, opts.rootsInfo.clientName, channel);
|
|
32
33
|
const normalized = pathNormalize(stableProfilePath);
|
|
33
|
-
// v0.25.
|
|
34
|
-
//
|
|
34
|
+
// v0.25.8: Comprehensive migration from legacy profile formats to stable identity profile
|
|
35
|
+
// Handles:
|
|
36
|
+
// - v0.18.x: URI+client+version hash, directory name as project name
|
|
37
|
+
// - v0.19.0-v0.25.4: URI+client hash (no version), directory name as project name
|
|
38
|
+
// - v0.25.5+: stable identity hash, package.json name as project name
|
|
35
39
|
if (!fs.existsSync(stableProfilePath) && opts.rootsInfo.rootsUris.length > 0) {
|
|
36
40
|
try {
|
|
37
41
|
const firstUri = opts.rootsInfo.rootsUris[0];
|
|
38
42
|
const url = new URL(firstUri);
|
|
39
43
|
if (url.protocol === 'file:') {
|
|
40
|
-
|
|
44
|
+
// Extract directory name for OLD project name (v0.25.4以前)
|
|
45
|
+
const rootPath = fileURLToPath(url);
|
|
41
46
|
const realRoot = realpathSafe(rootPath);
|
|
42
|
-
|
|
47
|
+
const dirName = path.basename(realRoot);
|
|
48
|
+
const oldProjectName = sanitize(dirName); // e.g., "adlogger" from "adLogger"
|
|
49
|
+
const newProjectName = opts.rootsInfo.projectName; // e.g., "adblocker" from package.json
|
|
50
|
+
// Try both old and new project names
|
|
51
|
+
const projectNamesToTry = [oldProjectName];
|
|
52
|
+
if (newProjectName !== oldProjectName) {
|
|
53
|
+
projectNamesToTry.push(newProjectName);
|
|
54
|
+
}
|
|
55
|
+
// Legacy hash formats to try
|
|
56
|
+
const sortedUris = [...opts.rootsInfo.rootsUris].sort();
|
|
57
|
+
const clientName = opts.rootsInfo.clientName;
|
|
58
|
+
const clientVersion = opts.rootsInfo.clientVersion;
|
|
43
59
|
const legacyHashFormats = [
|
|
44
|
-
// Format 1: v0.25.4
|
|
60
|
+
// Format 1: v0.19.0-v0.25.4 (URI+client, no version)
|
|
45
61
|
{
|
|
46
62
|
name: 'uri+client',
|
|
47
|
-
hash:
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
hash: crypto
|
|
64
|
+
.createHash('sha256')
|
|
65
|
+
.update(JSON.stringify({
|
|
66
|
+
roots: sortedUris,
|
|
67
|
+
client: clientName,
|
|
68
|
+
}))
|
|
69
|
+
.digest('hex')
|
|
70
|
+
.slice(0, 8),
|
|
55
71
|
},
|
|
56
|
-
// Format 2:
|
|
72
|
+
// Format 2: v0.18.x (URI+client+version) - try common version strings
|
|
73
|
+
...['1.0.0', '0.1.0', clientVersion].map((version) => ({
|
|
74
|
+
name: `uri+client+version(${version})`,
|
|
75
|
+
hash: crypto
|
|
76
|
+
.createHash('sha256')
|
|
77
|
+
.update(JSON.stringify({
|
|
78
|
+
roots: sortedUris,
|
|
79
|
+
client: clientName,
|
|
80
|
+
version,
|
|
81
|
+
}))
|
|
82
|
+
.digest('hex')
|
|
83
|
+
.slice(0, 8),
|
|
84
|
+
})),
|
|
85
|
+
// Format 3: Directory path hash
|
|
57
86
|
{
|
|
58
87
|
name: 'directory-path',
|
|
59
88
|
hash: shortHash(realRoot),
|
|
60
89
|
},
|
|
61
90
|
];
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
console.error(`[profiles] Migration:
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
// Try all combinations of project name × hash format
|
|
92
|
+
let migrated = false;
|
|
93
|
+
for (const projName of projectNamesToTry) {
|
|
94
|
+
if (migrated)
|
|
95
|
+
break;
|
|
96
|
+
for (const format of legacyHashFormats) {
|
|
97
|
+
const legacyKey = `${projName}_${format.hash}`;
|
|
98
|
+
const legacyPath = path.join(CACHE_ROOT, 'profiles', legacyKey, opts.rootsInfo.clientName, channel);
|
|
99
|
+
if (fs.existsSync(legacyPath)) {
|
|
100
|
+
console.error(`[profiles] Migration: Found legacy profile (${format.name}, project=${projName}): ${legacyPath}`);
|
|
101
|
+
console.error(`[profiles] Migration: Creating symlink to stable profile: ${stableProfilePath}`);
|
|
102
|
+
try {
|
|
103
|
+
fs.mkdirSync(path.dirname(stableProfilePath), { recursive: true });
|
|
104
|
+
fs.symlinkSync(legacyPath, stableProfilePath, 'dir');
|
|
105
|
+
console.error(`[profiles] Migration: ✅ Symlink created successfully`);
|
|
106
|
+
migrated = true;
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
catch (e) {
|
|
110
|
+
console.error(`[profiles] Migration: ⚠️ Failed to create symlink: ${e}`);
|
|
111
|
+
}
|
|
76
112
|
}
|
|
77
113
|
}
|
|
78
114
|
}
|
|
@@ -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,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-devtools-mcp-for-extension",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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",
|