@dynamicu/chromedebug-mcp 2.6.7 → 2.7.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/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/chrome-extension/background.js +611 -505
- package/chrome-extension/browser-recording-manager.js +1 -1
- package/chrome-extension/chrome-debug-logger.js +168 -0
- package/chrome-extension/console-interception-library.js +430 -0
- package/chrome-extension/content.css +16 -16
- package/chrome-extension/content.js +458 -126
- package/chrome-extension/extension-config.js +1 -1
- package/chrome-extension/license-helper.js +26 -0
- package/chrome-extension/manifest.free.json +0 -3
- package/chrome-extension/options.js +1 -1
- package/chrome-extension/popup.html +221 -191
- package/chrome-extension/popup.js +88 -379
- package/chrome-extension/pro/enhanced-capture.js +406 -0
- package/chrome-extension/pro/frame-editor.html +410 -0
- package/chrome-extension/pro/frame-editor.js +1496 -0
- package/chrome-extension/pro/function-tracker.js +843 -0
- package/chrome-extension/pro/jszip.min.js +13 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +3 -1
- package/scripts/webpack.config.free.cjs +8 -8
- package/scripts/webpack.config.pro.cjs +2 -0
- package/src/cli.js +2 -2
- package/src/database.js +55 -7
- package/src/index.js +9 -6
- package/src/mcp/server.js +2 -2
- package/src/services/process-manager.js +10 -6
- package/src/services/process-tracker.js +10 -5
- package/src/services/profile-manager.js +17 -2
- package/src/validation/schemas.js +2 -2
- package/src/index-direct.js +0 -157
- package/src/index-modular.js +0 -219
- package/src/index-monolithic-backup.js +0 -2230
- package/src/legacy/chrome-controller-old.js +0 -1406
- package/src/legacy/index-express.js +0 -625
- package/src/legacy/index-old.js +0 -977
- package/src/legacy/routes.js +0 -260
- package/src/legacy/shared-storage.js +0 -101
package/src/legacy/index-old.js
DELETED
|
@@ -1,977 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
4
|
-
ListToolsRequestSchema,
|
|
5
|
-
CallToolRequestSchema
|
|
6
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import express from 'express';
|
|
8
|
-
import { ChromeController } from './chrome-controller.js';
|
|
9
|
-
import { createRoutes } from './routes.js';
|
|
10
|
-
import { findAvailablePort } from './utils.js';
|
|
11
|
-
import { promises as fs } from 'fs';
|
|
12
|
-
import path from 'path';
|
|
13
|
-
|
|
14
|
-
const chromeController = new ChromeController();
|
|
15
|
-
const app = express();
|
|
16
|
-
let serverPort = null; // Store the actual server port
|
|
17
|
-
|
|
18
|
-
app.use((req, res, next) => {
|
|
19
|
-
res.header('Access-Control-Allow-Origin', '*');
|
|
20
|
-
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
21
|
-
res.header('Access-Control-Allow-Headers', 'Content-Type');
|
|
22
|
-
if (req.method === 'OPTIONS') {
|
|
23
|
-
res.sendStatus(200);
|
|
24
|
-
} else {
|
|
25
|
-
next();
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
app.use(express.json({ limit: '10mb' }));
|
|
30
|
-
|
|
31
|
-
const server = new Server(
|
|
32
|
-
{
|
|
33
|
-
name: 'chrome-pilot',
|
|
34
|
-
version: '1.0.0',
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
capabilities: {
|
|
38
|
-
tools: {},
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const tools = [
|
|
44
|
-
{
|
|
45
|
-
name: 'launch_chrome',
|
|
46
|
-
description: 'Launch a Chrome browser instance',
|
|
47
|
-
inputSchema: {
|
|
48
|
-
type: 'object',
|
|
49
|
-
properties: {},
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'connect_to_existing_chrome',
|
|
54
|
-
description: 'Connect to an existing Chrome instance with debugging enabled. Chrome must be running with --remote-debugging-port flag (e.g., --remote-debugging-port=9222)',
|
|
55
|
-
inputSchema: {
|
|
56
|
-
type: 'object',
|
|
57
|
-
properties: {
|
|
58
|
-
port: {
|
|
59
|
-
type: 'number',
|
|
60
|
-
description: 'Debugging port Chrome is running on (default: 9222)',
|
|
61
|
-
default: 9222,
|
|
62
|
-
},
|
|
63
|
-
},
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
{
|
|
67
|
-
name: 'pause_execution',
|
|
68
|
-
description: 'Pause Chrome execution',
|
|
69
|
-
inputSchema: {
|
|
70
|
-
type: 'object',
|
|
71
|
-
properties: {},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
name: 'resume_execution',
|
|
76
|
-
description: 'Resume Chrome execution',
|
|
77
|
-
inputSchema: {
|
|
78
|
-
type: 'object',
|
|
79
|
-
properties: {},
|
|
80
|
-
},
|
|
81
|
-
},
|
|
82
|
-
{
|
|
83
|
-
name: 'step_over',
|
|
84
|
-
description: 'Step over in Chrome debugger',
|
|
85
|
-
inputSchema: {
|
|
86
|
-
type: 'object',
|
|
87
|
-
properties: {},
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: 'evaluate_expression',
|
|
92
|
-
description: 'Evaluate a JavaScript expression in Chrome',
|
|
93
|
-
inputSchema: {
|
|
94
|
-
type: 'object',
|
|
95
|
-
properties: {
|
|
96
|
-
expression: {
|
|
97
|
-
type: 'string',
|
|
98
|
-
description: 'JavaScript expression to evaluate',
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
required: ['expression'],
|
|
102
|
-
},
|
|
103
|
-
},
|
|
104
|
-
{
|
|
105
|
-
name: 'get_scopes',
|
|
106
|
-
description: 'Get scope variables from the top call frame',
|
|
107
|
-
inputSchema: {
|
|
108
|
-
type: 'object',
|
|
109
|
-
properties: {},
|
|
110
|
-
},
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
name: 'set_breakpoint',
|
|
114
|
-
description: 'Set a breakpoint at a specific URL and line',
|
|
115
|
-
inputSchema: {
|
|
116
|
-
type: 'object',
|
|
117
|
-
properties: {
|
|
118
|
-
url: {
|
|
119
|
-
type: 'string',
|
|
120
|
-
description: 'URL of the script',
|
|
121
|
-
},
|
|
122
|
-
lineNumber: {
|
|
123
|
-
type: 'number',
|
|
124
|
-
description: 'Line number for the breakpoint',
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
required: ['url', 'lineNumber'],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: 'get_logs',
|
|
132
|
-
description: 'Get recent console logs',
|
|
133
|
-
inputSchema: {
|
|
134
|
-
type: 'object',
|
|
135
|
-
properties: {},
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
{
|
|
139
|
-
name: 'check_connection',
|
|
140
|
-
description: 'Check if Chrome is connected',
|
|
141
|
-
inputSchema: {
|
|
142
|
-
type: 'object',
|
|
143
|
-
properties: {},
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
name: 'force_reset',
|
|
148
|
-
description: 'Force reset Chrome by killing all processes and clearing state',
|
|
149
|
-
inputSchema: {
|
|
150
|
-
type: 'object',
|
|
151
|
-
properties: {},
|
|
152
|
-
},
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
name: 'take_screenshot',
|
|
156
|
-
description: 'Take a screenshot optimized for AI processing (320x568px viewport, JPEG quality 30, max 25KB)',
|
|
157
|
-
inputSchema: {
|
|
158
|
-
type: 'object',
|
|
159
|
-
properties: {
|
|
160
|
-
type: {
|
|
161
|
-
type: 'string',
|
|
162
|
-
description: 'Image type (default: jpeg)',
|
|
163
|
-
enum: ['png', 'jpeg'],
|
|
164
|
-
},
|
|
165
|
-
fullPage: {
|
|
166
|
-
type: 'boolean',
|
|
167
|
-
description: 'Capture full page (default: true, but limited to 600px height for AI)',
|
|
168
|
-
},
|
|
169
|
-
lowRes: {
|
|
170
|
-
type: 'boolean',
|
|
171
|
-
description: 'Low-resolution mode for AI (default: true, set false to disable)',
|
|
172
|
-
},
|
|
173
|
-
quality: {
|
|
174
|
-
type: 'number',
|
|
175
|
-
description: 'JPEG quality (1-100, default: 10)',
|
|
176
|
-
minimum: 1,
|
|
177
|
-
maximum: 100,
|
|
178
|
-
},
|
|
179
|
-
path: {
|
|
180
|
-
type: 'string',
|
|
181
|
-
description: 'Save screenshot to file path',
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
},
|
|
186
|
-
{
|
|
187
|
-
name: 'get_selected_element',
|
|
188
|
-
description: 'Get information about the currently selected element from the Chrome extension',
|
|
189
|
-
inputSchema: {
|
|
190
|
-
type: 'object',
|
|
191
|
-
properties: {},
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'apply_css_to_selected',
|
|
196
|
-
description: 'Apply CSS styles to the currently selected element',
|
|
197
|
-
inputSchema: {
|
|
198
|
-
type: 'object',
|
|
199
|
-
properties: {
|
|
200
|
-
css: {
|
|
201
|
-
type: 'string',
|
|
202
|
-
description: 'CSS rules to apply (e.g., "background-color: blue !important; padding: 20px !important")',
|
|
203
|
-
},
|
|
204
|
-
},
|
|
205
|
-
required: ['css'],
|
|
206
|
-
},
|
|
207
|
-
},
|
|
208
|
-
{
|
|
209
|
-
name: 'execute_js_on_selected',
|
|
210
|
-
description: 'Execute JavaScript code on the currently selected element',
|
|
211
|
-
inputSchema: {
|
|
212
|
-
type: 'object',
|
|
213
|
-
properties: {
|
|
214
|
-
code: {
|
|
215
|
-
type: 'string',
|
|
216
|
-
description: 'JavaScript code to execute. The element is available as the "element" variable.',
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
required: ['code'],
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
name: 'clear_selected_element',
|
|
224
|
-
description: 'Clear the currently selected element',
|
|
225
|
-
inputSchema: {
|
|
226
|
-
type: 'object',
|
|
227
|
-
properties: {},
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
name: 'get_recording',
|
|
232
|
-
description: 'Get information about a screen recording (frame capture only). This tool identifies the recording type and guides you to the appropriate tools',
|
|
233
|
-
inputSchema: {
|
|
234
|
-
type: 'object',
|
|
235
|
-
properties: {
|
|
236
|
-
recordingId: {
|
|
237
|
-
type: 'string',
|
|
238
|
-
description: 'The recording ID returned when recording was saved',
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
required: ['recordingId'],
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
{
|
|
245
|
-
name: 'get_server_info',
|
|
246
|
-
description: 'Get information about the current Chrome Debug server including port and storage location',
|
|
247
|
-
inputSchema: {
|
|
248
|
-
type: 'object',
|
|
249
|
-
properties: {},
|
|
250
|
-
},
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
name: 'discover_server_port',
|
|
254
|
-
description: 'Discover the port of a running Chrome Debug server by checking common ports',
|
|
255
|
-
inputSchema: {
|
|
256
|
-
type: 'object',
|
|
257
|
-
properties: {
|
|
258
|
-
startPort: {
|
|
259
|
-
type: 'number',
|
|
260
|
-
description: 'Starting port to check (default: 3000)',
|
|
261
|
-
default: 3000,
|
|
262
|
-
},
|
|
263
|
-
maxPort: {
|
|
264
|
-
type: 'number',
|
|
265
|
-
description: 'Maximum port to check (default: 3020)',
|
|
266
|
-
default: 3020,
|
|
267
|
-
},
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
{
|
|
272
|
-
name: 'get_recording_info',
|
|
273
|
-
description: 'Get information about a chunked recording session including total chunks and metadata',
|
|
274
|
-
inputSchema: {
|
|
275
|
-
type: 'object',
|
|
276
|
-
properties: {
|
|
277
|
-
sessionId: {
|
|
278
|
-
type: 'string',
|
|
279
|
-
description: 'The session ID returned when recording was saved',
|
|
280
|
-
},
|
|
281
|
-
},
|
|
282
|
-
required: ['sessionId'],
|
|
283
|
-
},
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
name: 'get_recording_chunk',
|
|
287
|
-
description: 'Get a specific chunk from a recording session with synchronized console logs',
|
|
288
|
-
inputSchema: {
|
|
289
|
-
type: 'object',
|
|
290
|
-
properties: {
|
|
291
|
-
sessionId: {
|
|
292
|
-
type: 'string',
|
|
293
|
-
description: 'The session ID returned when recording was saved',
|
|
294
|
-
},
|
|
295
|
-
chunkIndex: {
|
|
296
|
-
type: 'number',
|
|
297
|
-
description: 'The chunk index to retrieve (0-based)',
|
|
298
|
-
},
|
|
299
|
-
},
|
|
300
|
-
required: ['sessionId', 'chunkIndex'],
|
|
301
|
-
},
|
|
302
|
-
},
|
|
303
|
-
{
|
|
304
|
-
name: 'get_frame_session_info',
|
|
305
|
-
description: 'Get information about a frame capture session including total frames and timestamps',
|
|
306
|
-
inputSchema: {
|
|
307
|
-
type: 'object',
|
|
308
|
-
properties: {
|
|
309
|
-
sessionId: {
|
|
310
|
-
type: 'string',
|
|
311
|
-
description: 'The frame session ID returned when recording was saved',
|
|
312
|
-
},
|
|
313
|
-
},
|
|
314
|
-
required: ['sessionId'],
|
|
315
|
-
},
|
|
316
|
-
},
|
|
317
|
-
{
|
|
318
|
-
name: 'get_frame',
|
|
319
|
-
description: 'Get a specific frame from a frame capture session',
|
|
320
|
-
inputSchema: {
|
|
321
|
-
type: 'object',
|
|
322
|
-
properties: {
|
|
323
|
-
sessionId: {
|
|
324
|
-
type: 'string',
|
|
325
|
-
description: 'The frame session ID',
|
|
326
|
-
},
|
|
327
|
-
frameIndex: {
|
|
328
|
-
type: 'number',
|
|
329
|
-
description: 'The frame index (0-based)',
|
|
330
|
-
},
|
|
331
|
-
},
|
|
332
|
-
required: ['sessionId', 'frameIndex'],
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
{
|
|
336
|
-
name: 'chrome_pilot_show_frames',
|
|
337
|
-
description: 'Display frame recording information in a compact format optimized for MCP tools. Shows frame metadata, console logs, and timestamps without including large image data.',
|
|
338
|
-
inputSchema: {
|
|
339
|
-
type: 'object',
|
|
340
|
-
properties: {
|
|
341
|
-
sessionId: {
|
|
342
|
-
type: 'string',
|
|
343
|
-
description: 'The frame session ID to display',
|
|
344
|
-
},
|
|
345
|
-
maxFrames: {
|
|
346
|
-
type: 'number',
|
|
347
|
-
description: 'Maximum number of frames to show (default: 10)',
|
|
348
|
-
default: 10,
|
|
349
|
-
},
|
|
350
|
-
showLogs: {
|
|
351
|
-
type: 'boolean',
|
|
352
|
-
description: 'Whether to include console logs in the output (default: true)',
|
|
353
|
-
default: true,
|
|
354
|
-
},
|
|
355
|
-
},
|
|
356
|
-
required: ['sessionId'],
|
|
357
|
-
},
|
|
358
|
-
},
|
|
359
|
-
];
|
|
360
|
-
|
|
361
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
362
|
-
return { tools };
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
366
|
-
const { name, arguments: args } = request.params;
|
|
367
|
-
|
|
368
|
-
try {
|
|
369
|
-
switch (name) {
|
|
370
|
-
case 'launch_chrome': {
|
|
371
|
-
const result = await chromeController.launch();
|
|
372
|
-
return {
|
|
373
|
-
content: [
|
|
374
|
-
{
|
|
375
|
-
type: 'text',
|
|
376
|
-
text: JSON.stringify(result, null, 2),
|
|
377
|
-
},
|
|
378
|
-
],
|
|
379
|
-
};
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
case 'connect_to_existing_chrome': {
|
|
383
|
-
const port = args.port || 9222;
|
|
384
|
-
const result = await chromeController.connectToExisting(port);
|
|
385
|
-
return {
|
|
386
|
-
content: [
|
|
387
|
-
{
|
|
388
|
-
type: 'text',
|
|
389
|
-
text: JSON.stringify(result, null, 2),
|
|
390
|
-
},
|
|
391
|
-
],
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
case 'pause_execution': {
|
|
396
|
-
const result = await chromeController.pause();
|
|
397
|
-
return {
|
|
398
|
-
content: [
|
|
399
|
-
{
|
|
400
|
-
type: 'text',
|
|
401
|
-
text: JSON.stringify(result, null, 2),
|
|
402
|
-
},
|
|
403
|
-
],
|
|
404
|
-
};
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
case 'resume_execution': {
|
|
408
|
-
const result = await chromeController.resume();
|
|
409
|
-
return {
|
|
410
|
-
content: [
|
|
411
|
-
{
|
|
412
|
-
type: 'text',
|
|
413
|
-
text: JSON.stringify(result, null, 2),
|
|
414
|
-
},
|
|
415
|
-
],
|
|
416
|
-
};
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
case 'step_over': {
|
|
420
|
-
const result = await chromeController.stepOver();
|
|
421
|
-
return {
|
|
422
|
-
content: [
|
|
423
|
-
{
|
|
424
|
-
type: 'text',
|
|
425
|
-
text: JSON.stringify(result, null, 2),
|
|
426
|
-
},
|
|
427
|
-
],
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
case 'evaluate_expression': {
|
|
432
|
-
const result = await chromeController.evaluate(args.expression);
|
|
433
|
-
return {
|
|
434
|
-
content: [
|
|
435
|
-
{
|
|
436
|
-
type: 'text',
|
|
437
|
-
text: JSON.stringify(result, null, 2),
|
|
438
|
-
},
|
|
439
|
-
],
|
|
440
|
-
};
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
case 'get_scopes': {
|
|
444
|
-
const result = await chromeController.getScopes();
|
|
445
|
-
return {
|
|
446
|
-
content: [
|
|
447
|
-
{
|
|
448
|
-
type: 'text',
|
|
449
|
-
text: JSON.stringify(result, null, 2),
|
|
450
|
-
},
|
|
451
|
-
],
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
case 'set_breakpoint': {
|
|
456
|
-
const result = await chromeController.setBreakpoint(args.url, args.lineNumber);
|
|
457
|
-
return {
|
|
458
|
-
content: [
|
|
459
|
-
{
|
|
460
|
-
type: 'text',
|
|
461
|
-
text: JSON.stringify(result, null, 2),
|
|
462
|
-
},
|
|
463
|
-
],
|
|
464
|
-
};
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
case 'get_logs': {
|
|
468
|
-
const result = chromeController.getLogs();
|
|
469
|
-
return {
|
|
470
|
-
content: [
|
|
471
|
-
{
|
|
472
|
-
type: 'text',
|
|
473
|
-
text: JSON.stringify(result, null, 2),
|
|
474
|
-
},
|
|
475
|
-
],
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
case 'check_connection': {
|
|
480
|
-
const isConnected = await chromeController.isConnected();
|
|
481
|
-
return {
|
|
482
|
-
content: [
|
|
483
|
-
{
|
|
484
|
-
type: 'text',
|
|
485
|
-
text: JSON.stringify({ connected: isConnected }, null, 2),
|
|
486
|
-
},
|
|
487
|
-
],
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
case 'force_reset': {
|
|
492
|
-
const result = await chromeController.forceReset();
|
|
493
|
-
return {
|
|
494
|
-
content: [
|
|
495
|
-
{
|
|
496
|
-
type: 'text',
|
|
497
|
-
text: JSON.stringify(result, null, 2),
|
|
498
|
-
},
|
|
499
|
-
],
|
|
500
|
-
};
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
case 'take_screenshot': {
|
|
504
|
-
const result = await chromeController.takeScreenshot(args);
|
|
505
|
-
|
|
506
|
-
// Format response based on whether screenshot was saved or returned
|
|
507
|
-
if (result.saved) {
|
|
508
|
-
return {
|
|
509
|
-
content: [
|
|
510
|
-
{
|
|
511
|
-
type: 'text',
|
|
512
|
-
text: `Screenshot saved successfully!\n\nPath: ${result.path}\nType: ${result.type}\nFull page: ${result.fullPage}`,
|
|
513
|
-
},
|
|
514
|
-
],
|
|
515
|
-
};
|
|
516
|
-
} else if (result.truncated) {
|
|
517
|
-
return {
|
|
518
|
-
content: [
|
|
519
|
-
{
|
|
520
|
-
type: 'text',
|
|
521
|
-
text: `Screenshot preview (truncated):\n\nSize: ${result.size}\nType: ${result.type}\nFull page: ${result.fullPage}\n\n${result.message}\n\nTo get the full screenshot, please provide a 'path' parameter to save it to a file.`,
|
|
522
|
-
},
|
|
523
|
-
],
|
|
524
|
-
};
|
|
525
|
-
} else if (result.error) {
|
|
526
|
-
return {
|
|
527
|
-
content: [
|
|
528
|
-
{
|
|
529
|
-
type: 'text',
|
|
530
|
-
text: `Error taking screenshot: ${result.message}`,
|
|
531
|
-
},
|
|
532
|
-
],
|
|
533
|
-
isError: true,
|
|
534
|
-
};
|
|
535
|
-
} else if (result.lowRes) {
|
|
536
|
-
// For low-res screenshots, return the full base64 data for AI parsing
|
|
537
|
-
return {
|
|
538
|
-
content: [
|
|
539
|
-
{
|
|
540
|
-
type: 'text',
|
|
541
|
-
text: `Low-resolution screenshot captured for AI parsing\n\nSize: ${result.size}\nType: ${result.type}\nFull page: ${result.fullPage}\nQuality: ${result.quality || 'N/A'}\n\ndata:image/${result.type};base64,${result.screenshot}`,
|
|
542
|
-
},
|
|
543
|
-
],
|
|
544
|
-
};
|
|
545
|
-
} else {
|
|
546
|
-
// For normal screenshots, show preview only
|
|
547
|
-
return {
|
|
548
|
-
content: [
|
|
549
|
-
{
|
|
550
|
-
type: 'text',
|
|
551
|
-
text: `Screenshot captured!\n\nSize: ${result.size}\nType: ${result.type}\nFull page: ${result.fullPage}\n\nBase64 data: ${result.screenshot.substring(0, 100)}...\n\nNote: Full screenshot is too large to display. Use 'lowRes: true' for AI-parseable screenshots or 'path' to save to file.`,
|
|
552
|
-
},
|
|
553
|
-
],
|
|
554
|
-
};
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
|
|
558
|
-
case 'get_selected_element': {
|
|
559
|
-
const result = await chromeController.getSelectedElement();
|
|
560
|
-
return {
|
|
561
|
-
content: [
|
|
562
|
-
{
|
|
563
|
-
type: 'text',
|
|
564
|
-
text: JSON.stringify(result, null, 2),
|
|
565
|
-
},
|
|
566
|
-
],
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
|
|
570
|
-
case 'apply_css_to_selected': {
|
|
571
|
-
const result = await chromeController.applyToSelectedElement(args.css);
|
|
572
|
-
return {
|
|
573
|
-
content: [
|
|
574
|
-
{
|
|
575
|
-
type: 'text',
|
|
576
|
-
text: JSON.stringify(result, null, 2),
|
|
577
|
-
},
|
|
578
|
-
],
|
|
579
|
-
};
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
case 'execute_js_on_selected': {
|
|
583
|
-
const result = await chromeController.executeOnSelectedElement(args.code);
|
|
584
|
-
return {
|
|
585
|
-
content: [
|
|
586
|
-
{
|
|
587
|
-
type: 'text',
|
|
588
|
-
text: JSON.stringify(result, null, 2),
|
|
589
|
-
},
|
|
590
|
-
],
|
|
591
|
-
};
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
case 'clear_selected_element': {
|
|
595
|
-
const result = await chromeController.clearSelectedElement();
|
|
596
|
-
return {
|
|
597
|
-
content: [
|
|
598
|
-
{
|
|
599
|
-
type: 'text',
|
|
600
|
-
text: JSON.stringify(result, null, 2),
|
|
601
|
-
},
|
|
602
|
-
],
|
|
603
|
-
};
|
|
604
|
-
}
|
|
605
|
-
|
|
606
|
-
case 'get_recording': {
|
|
607
|
-
if (!args.recordingId) {
|
|
608
|
-
throw new Error('Recording ID is required');
|
|
609
|
-
}
|
|
610
|
-
const result = await chromeController.getRecording(args.recordingId);
|
|
611
|
-
if (result.error) {
|
|
612
|
-
throw new Error(result.message);
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
// Handle frame capture sessions
|
|
616
|
-
if (result.type === 'frame_capture') {
|
|
617
|
-
return {
|
|
618
|
-
content: [
|
|
619
|
-
{
|
|
620
|
-
type: 'text',
|
|
621
|
-
text: `Recording ${args.recordingId} is a FRAME CAPTURE session.\n\n` +
|
|
622
|
-
`${result.message}\n\n` +
|
|
623
|
-
`Session Info:\n` +
|
|
624
|
-
`- Total Frames: ${result.recording.totalFrames}\n` +
|
|
625
|
-
`- Session ID: ${args.recordingId}\n\n` +
|
|
626
|
-
`To access this recording, use:\n` +
|
|
627
|
-
`1. get_frame_session_info with sessionId: "${args.recordingId}"\n` +
|
|
628
|
-
`2. get_frame with sessionId: "${args.recordingId}" and frameIndex: 0, 1, 2, etc.`
|
|
629
|
-
}
|
|
630
|
-
],
|
|
631
|
-
};
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// Handle chunked recordings (legacy - no longer supported)
|
|
635
|
-
if (result.type === 'chunked_recording') {
|
|
636
|
-
return {
|
|
637
|
-
content: [
|
|
638
|
-
{
|
|
639
|
-
type: 'text',
|
|
640
|
-
text: `Recording ${args.recordingId} is a CHUNKED recording (legacy).\n\n` +
|
|
641
|
-
`${result.message}\n\n` +
|
|
642
|
-
`Session Info:\n` +
|
|
643
|
-
`- Total Chunks: ${result.recording.totalChunks}\n` +
|
|
644
|
-
`- Session ID: ${args.recordingId}\n\n` +
|
|
645
|
-
`To access this recording, use:\n` +
|
|
646
|
-
`1. get_recording_info with sessionId: "${args.recordingId}"\n` +
|
|
647
|
-
`2. get_recording_chunk with sessionId: "${args.recordingId}" and chunkIndex: 0, 1, 2, etc.`
|
|
648
|
-
}
|
|
649
|
-
],
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
// Handle old single-file recordings
|
|
654
|
-
return {
|
|
655
|
-
content: [
|
|
656
|
-
{
|
|
657
|
-
type: 'text',
|
|
658
|
-
text: `Recording ${args.recordingId}:\n` +
|
|
659
|
-
`Duration: ${result.recording.duration}ms\n` +
|
|
660
|
-
`Logs: ${result.recording.logs.length} entries\n` +
|
|
661
|
-
`Data: ${result.recording.filename || 'legacy recording'}`
|
|
662
|
-
},
|
|
663
|
-
{
|
|
664
|
-
type: 'text',
|
|
665
|
-
text: '--- Console Logs ---\n' + result.recording.logs.map(log =>
|
|
666
|
-
`[${log.relativeTime}ms] [${log.level}] ${log.message}`
|
|
667
|
-
).join('\n')
|
|
668
|
-
}
|
|
669
|
-
],
|
|
670
|
-
};
|
|
671
|
-
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
case 'get_server_info': {
|
|
675
|
-
const port = serverPort || process.env.PORT || 3000;
|
|
676
|
-
const storageLocation = path.join(process.cwd(), 'src', '.recordings-storage.json');
|
|
677
|
-
|
|
678
|
-
// Check if storage file exists
|
|
679
|
-
let storageInfo = 'Not created yet';
|
|
680
|
-
try {
|
|
681
|
-
const stats = await fs.stat(storageLocation);
|
|
682
|
-
const sizeKB = Math.round(stats.size / 1024);
|
|
683
|
-
storageInfo = `${sizeKB}KB`;
|
|
684
|
-
} catch (e) {
|
|
685
|
-
// File doesn't exist yet
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
return {
|
|
689
|
-
content: [
|
|
690
|
-
{
|
|
691
|
-
type: 'text',
|
|
692
|
-
text: `Chrome Debug Server Information:\n\n` +
|
|
693
|
-
`Server Port: ${port}\n` +
|
|
694
|
-
`Storage Location: ${storageLocation}\n` +
|
|
695
|
-
`Storage Size: ${storageInfo}\n` +
|
|
696
|
-
`Working Directory: ${process.cwd()}\n\n` +
|
|
697
|
-
`To retrieve recordings from a different server, use:\n` +
|
|
698
|
-
`get_recording_from_server with port parameter`
|
|
699
|
-
}
|
|
700
|
-
],
|
|
701
|
-
};
|
|
702
|
-
}
|
|
703
|
-
|
|
704
|
-
case 'discover_server_port': {
|
|
705
|
-
const startPort = args.startPort || 3000;
|
|
706
|
-
const maxPort = args.maxPort || 3020;
|
|
707
|
-
|
|
708
|
-
const foundServers = [];
|
|
709
|
-
|
|
710
|
-
for (let port = startPort; port <= maxPort; port++) {
|
|
711
|
-
try {
|
|
712
|
-
const response = await fetch(`http://localhost:${port}/chrome-pilot/port`);
|
|
713
|
-
if (response.ok) {
|
|
714
|
-
const data = await response.json();
|
|
715
|
-
foundServers.push({
|
|
716
|
-
port: port,
|
|
717
|
-
reported_port: data.port,
|
|
718
|
-
timestamp: data.timestamp,
|
|
719
|
-
message: data.message
|
|
720
|
-
});
|
|
721
|
-
}
|
|
722
|
-
} catch (e) {
|
|
723
|
-
// Port not available or no server running
|
|
724
|
-
}
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
if (foundServers.length === 0) {
|
|
728
|
-
return {
|
|
729
|
-
content: [
|
|
730
|
-
{
|
|
731
|
-
type: 'text',
|
|
732
|
-
text: `No Chrome Debug servers found on ports ${startPort}-${maxPort}.\n\n` +
|
|
733
|
-
`To start a server, run: npm start`
|
|
734
|
-
}
|
|
735
|
-
],
|
|
736
|
-
};
|
|
737
|
-
}
|
|
738
|
-
|
|
739
|
-
return {
|
|
740
|
-
content: [
|
|
741
|
-
{
|
|
742
|
-
type: 'text',
|
|
743
|
-
text: `Found ${foundServers.length} Chrome Debug server(s):\n\n` +
|
|
744
|
-
foundServers.map(server =>
|
|
745
|
-
`Port ${server.port}: ${server.message} (${server.timestamp})`
|
|
746
|
-
).join('\n') + '\n\n' +
|
|
747
|
-
`Use these ports to connect to the servers.`
|
|
748
|
-
}
|
|
749
|
-
],
|
|
750
|
-
};
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
case 'get_recording_info': {
|
|
754
|
-
if (!args.sessionId) {
|
|
755
|
-
throw new Error('Session ID is required');
|
|
756
|
-
}
|
|
757
|
-
const result = await chromeController.getRecordingInfo(args.sessionId);
|
|
758
|
-
if (result.error) {
|
|
759
|
-
throw new Error(result.message);
|
|
760
|
-
}
|
|
761
|
-
return {
|
|
762
|
-
content: [
|
|
763
|
-
{
|
|
764
|
-
type: 'text',
|
|
765
|
-
text: `Recording Session ${args.sessionId}:\n` +
|
|
766
|
-
`Total Chunks: ${result.info.totalChunks}\n` +
|
|
767
|
-
`Created: ${result.info.createdAt}\n` +
|
|
768
|
-
`Last Updated: ${result.info.lastUpdated}\n` +
|
|
769
|
-
`Available Chunks: ${result.info.chunkIndexes.join(', ')}`
|
|
770
|
-
}
|
|
771
|
-
],
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
case 'get_recording_chunk': {
|
|
776
|
-
if (!args.sessionId) {
|
|
777
|
-
throw new Error('Session ID is required');
|
|
778
|
-
}
|
|
779
|
-
if (args.chunkIndex === undefined) {
|
|
780
|
-
throw new Error('Chunk index is required');
|
|
781
|
-
}
|
|
782
|
-
const result = await chromeController.getRecordingChunk(args.sessionId, args.chunkIndex);
|
|
783
|
-
if (result.error) {
|
|
784
|
-
throw new Error(result.message);
|
|
785
|
-
}
|
|
786
|
-
return {
|
|
787
|
-
content: [
|
|
788
|
-
{
|
|
789
|
-
type: 'text',
|
|
790
|
-
text: `Recording Chunk ${args.chunkIndex} from Session ${args.sessionId}:\n` +
|
|
791
|
-
`Time Range: ${result.chunk.startTimeMs}ms - ${result.chunk.endTimeMs}ms\n` +
|
|
792
|
-
`Duration: ${result.chunk.endTimeMs - result.chunk.startTimeMs}ms\n` +
|
|
793
|
-
`Console Logs: ${result.chunk.logs.length} entries\n` +
|
|
794
|
-
`Data: ${result.chunk.filename || 'chunk_' + args.chunkIndex}`
|
|
795
|
-
},
|
|
796
|
-
{
|
|
797
|
-
type: 'text',
|
|
798
|
-
text: '--- Console Logs for this chunk ---\n' + result.chunk.logs.map(log =>
|
|
799
|
-
`[${log.chunkRelativeTime || log.relativeTime}ms] [${log.level}] ${log.message}`
|
|
800
|
-
).join('\n')
|
|
801
|
-
}
|
|
802
|
-
],
|
|
803
|
-
};
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
case 'get_frame_session_info': {
|
|
807
|
-
if (!args.sessionId) {
|
|
808
|
-
throw new Error('Session ID is required');
|
|
809
|
-
}
|
|
810
|
-
const info = await chromeController.getFrameSessionInfo(args.sessionId);
|
|
811
|
-
if (!info) {
|
|
812
|
-
throw new Error('Frame session not found');
|
|
813
|
-
}
|
|
814
|
-
return {
|
|
815
|
-
content: [
|
|
816
|
-
{
|
|
817
|
-
type: 'text',
|
|
818
|
-
text: `Frame Session ${args.sessionId}:\n` +
|
|
819
|
-
`Total Frames: ${info.totalFrames}\n` +
|
|
820
|
-
`Created: ${new Date(info.timestamp).toISOString()}\n` +
|
|
821
|
-
`Frame Timestamps: ${info.frameTimestamps.map(t => t + 'ms').join(', ')}`
|
|
822
|
-
}
|
|
823
|
-
],
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
case 'get_frame': {
|
|
828
|
-
if (!args.sessionId) {
|
|
829
|
-
throw new Error('Session ID is required');
|
|
830
|
-
}
|
|
831
|
-
if (args.frameIndex === undefined) {
|
|
832
|
-
throw new Error('Frame index is required');
|
|
833
|
-
}
|
|
834
|
-
const frame = await chromeController.getFrame(args.sessionId, args.frameIndex);
|
|
835
|
-
if (!frame) {
|
|
836
|
-
throw new Error('Frame not found');
|
|
837
|
-
}
|
|
838
|
-
return {
|
|
839
|
-
content: [
|
|
840
|
-
{
|
|
841
|
-
type: 'text',
|
|
842
|
-
text: `Frame ${args.frameIndex} from Session ${args.sessionId}:\n` +
|
|
843
|
-
`Timestamp: ${frame.timestamp}ms\n` +
|
|
844
|
-
`Console Logs: ${frame.logs.length} entries`
|
|
845
|
-
},
|
|
846
|
-
{
|
|
847
|
-
type: 'resource',
|
|
848
|
-
resource: {
|
|
849
|
-
uri: frame.imageData,
|
|
850
|
-
mimeType: 'image/jpeg',
|
|
851
|
-
text: `Frame ${args.frameIndex} at ${frame.timestamp}ms`
|
|
852
|
-
}
|
|
853
|
-
},
|
|
854
|
-
{
|
|
855
|
-
type: 'text',
|
|
856
|
-
text: 'Console Logs:\n' + frame.logs.map(log =>
|
|
857
|
-
`[${log.level.toUpperCase()}] ${log.message}`
|
|
858
|
-
).join('\n')
|
|
859
|
-
}
|
|
860
|
-
],
|
|
861
|
-
};
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
case 'chrome_pilot_show_frames': {
|
|
865
|
-
if (!args.sessionId) {
|
|
866
|
-
throw new Error('Session ID is required');
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
const maxFrames = args.maxFrames || 10;
|
|
870
|
-
const showLogs = args.showLogs !== false;
|
|
871
|
-
|
|
872
|
-
const sessionInfo = await chromeController.getFrameSessionInfo(args.sessionId);
|
|
873
|
-
if (!sessionInfo) {
|
|
874
|
-
throw new Error('Frame session not found');
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
const session = await chromeController.getFrameSession(args.sessionId);
|
|
878
|
-
if (!session) {
|
|
879
|
-
throw new Error('Frame session data not found');
|
|
880
|
-
}
|
|
881
|
-
|
|
882
|
-
// Limit the number of frames to display
|
|
883
|
-
const framesToShow = session.frames.slice(0, maxFrames);
|
|
884
|
-
|
|
885
|
-
let output = `Frame Recording: ${args.sessionId}\n`;
|
|
886
|
-
output += `Created: ${new Date(sessionInfo.timestamp).toLocaleString()}\n`;
|
|
887
|
-
output += `Total Frames: ${sessionInfo.totalFrames}\n`;
|
|
888
|
-
output += `Showing: ${framesToShow.length} of ${sessionInfo.totalFrames} frames\n\n`;
|
|
889
|
-
|
|
890
|
-
framesToShow.forEach((frame, index) => {
|
|
891
|
-
output += `Frame ${index}:\n`;
|
|
892
|
-
output += ` Timestamp: ${frame.timestamp}ms\n`;
|
|
893
|
-
output += ` Image Size: ${frame.imageData ? Math.round(frame.imageData.length / 1024) + 'KB' : 'N/A'}\n`;
|
|
894
|
-
|
|
895
|
-
if (showLogs && frame.logs && frame.logs.length > 0) {
|
|
896
|
-
output += ` Console Logs (${frame.logs.length}):\n`;
|
|
897
|
-
frame.logs.forEach(log => {
|
|
898
|
-
output += ` [${log.level.toUpperCase()}] ${log.message}\n`;
|
|
899
|
-
});
|
|
900
|
-
} else {
|
|
901
|
-
output += ` Console Logs: ${frame.logs ? frame.logs.length : 0}\n`;
|
|
902
|
-
}
|
|
903
|
-
output += '\n';
|
|
904
|
-
});
|
|
905
|
-
|
|
906
|
-
if (sessionInfo.totalFrames > maxFrames) {
|
|
907
|
-
output += `... and ${sessionInfo.totalFrames - maxFrames} more frames\n`;
|
|
908
|
-
output += `Use get_frame with frameIndex to view specific frames\n`;
|
|
909
|
-
}
|
|
910
|
-
|
|
911
|
-
return {
|
|
912
|
-
content: [
|
|
913
|
-
{
|
|
914
|
-
type: 'text',
|
|
915
|
-
text: output
|
|
916
|
-
}
|
|
917
|
-
],
|
|
918
|
-
};
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
default:
|
|
922
|
-
throw new Error(`Unknown tool: ${name}`);
|
|
923
|
-
}
|
|
924
|
-
} catch (error) {
|
|
925
|
-
return {
|
|
926
|
-
content: [
|
|
927
|
-
{
|
|
928
|
-
type: 'text',
|
|
929
|
-
text: `Error: ${error.message}`,
|
|
930
|
-
},
|
|
931
|
-
],
|
|
932
|
-
isError: true,
|
|
933
|
-
};
|
|
934
|
-
}
|
|
935
|
-
});
|
|
936
|
-
|
|
937
|
-
async function main() {
|
|
938
|
-
app.use(createRoutes(chromeController));
|
|
939
|
-
|
|
940
|
-
app.use((err, req, res, next) => {
|
|
941
|
-
console.error('Express error:', err);
|
|
942
|
-
res.status(500).json({ error: err.message });
|
|
943
|
-
});
|
|
944
|
-
|
|
945
|
-
const startPort = process.env.PORT ? parseInt(process.env.PORT) : 3000;
|
|
946
|
-
const port = await findAvailablePort(startPort);
|
|
947
|
-
serverPort = port; // Store for get_server_info
|
|
948
|
-
|
|
949
|
-
// Set SERVER_PORT environment variable for the port discovery endpoint
|
|
950
|
-
process.env.SERVER_PORT = port.toString();
|
|
951
|
-
|
|
952
|
-
app.listen(port, () => {
|
|
953
|
-
console.error(`Chrome Debug REST API listening on port ${port}`);
|
|
954
|
-
if (port !== startPort) {
|
|
955
|
-
console.error(`Note: Port ${startPort} was busy, using port ${port} instead`);
|
|
956
|
-
}
|
|
957
|
-
});
|
|
958
|
-
|
|
959
|
-
const transport = new StdioServerTransport();
|
|
960
|
-
await server.connect(transport);
|
|
961
|
-
console.error('Chrome Debug MCP server running');
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
process.on('SIGINT', async () => {
|
|
965
|
-
await chromeController.close();
|
|
966
|
-
process.exit(0);
|
|
967
|
-
});
|
|
968
|
-
|
|
969
|
-
process.on('SIGTERM', async () => {
|
|
970
|
-
await chromeController.close();
|
|
971
|
-
process.exit(0);
|
|
972
|
-
});
|
|
973
|
-
|
|
974
|
-
main().catch((error) => {
|
|
975
|
-
console.error('Fatal error:', error);
|
|
976
|
-
process.exit(1);
|
|
977
|
-
});
|