@debugelectron/debug-electron-mcp 1.6.9 → 1.6.10
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 +17 -3
- package/dist/index.js +471 -30
- package/dist/index.js.map +1 -1
- package/dist/schemas.d.ts +5 -2
- package/dist/screenshot.d.ts +10 -1
- package/dist/utils/electron-enhanced-commands.d.ts +6 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -48,7 +48,7 @@ Transform your Electron development experience with **AI-powered automation**:
|
|
|
48
48
|
{
|
|
49
49
|
"mcp": {
|
|
50
50
|
"servers": {
|
|
51
|
-
"electron": {
|
|
51
|
+
"debug-electron-mcp": {
|
|
52
52
|
"command": "npx",
|
|
53
53
|
"args": ["-y", "@debugelectron/debug-electron-mcp@latest"]
|
|
54
54
|
}
|
|
@@ -61,7 +61,7 @@ Transform your Electron development experience with **AI-powered automation**:
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
63
|
"mcpServers": {
|
|
64
|
-
"electron": {
|
|
64
|
+
"debug-electron-mcp": {
|
|
65
65
|
"command": "npx",
|
|
66
66
|
"args": ["-y", "@debugelectron/debug-electron-mcp@latest"]
|
|
67
67
|
}
|
|
@@ -73,7 +73,7 @@ Transform your Electron development experience with **AI-powered automation**:
|
|
|
73
73
|
```json
|
|
74
74
|
{
|
|
75
75
|
"mcpServers": {
|
|
76
|
-
"electron": {
|
|
76
|
+
"debug-electron-mcp": {
|
|
77
77
|
"command": "npx",
|
|
78
78
|
"args": ["-y", "@debugelectron/debug-electron-mcp@latest"]
|
|
79
79
|
}
|
|
@@ -316,7 +316,14 @@ When configuring an Electron app for MCP Server:
|
|
|
316
316
|
| --------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------ |
|
|
317
317
|
| `click_by_selector` | `{"selector": "css-selector"}` | `{"selector": "button.primary"}` |
|
|
318
318
|
| `click_by_text` | `{"text": "button text"}` | `{"text": "Submit"}` |
|
|
319
|
+
| `hover` | `{"selector": "css-selector"}` | `{"selector": "#menu-item"}` |
|
|
320
|
+
| `drag` | `{"startSelector": "source", "endSelector": "target"}` | `{"startSelector": "#item", "endSelector": "#zone"}` |
|
|
321
|
+
| `wait` | `{"duration": 1000}` or `{"selector": "..."}` or `{"text": "..."}` | `{"selector": "#loaded-content"}` |
|
|
322
|
+
| `type` | `{"selector": "...", "text": "..."}` | `{"selector": "#search", "text": "hello"}` |
|
|
319
323
|
| `fill_input` | `{"value": "text", "selector": "..."}` or `{"value": "text", "placeholder": "..."}` | `{"placeholder": "Enter name", "value": "John"}` |
|
|
324
|
+
| `get_attribute` | `{"selector": "...", "attribute": "..."}` | `{"selector": "img", "attribute": "src"}` |
|
|
325
|
+
| `is_visible` | `{"selector": "..."}` | `{"selector": "#error-message"}` |
|
|
326
|
+
| `count` | `{"selector": "..."}` | `{"selector": ".list-item"}` |
|
|
320
327
|
| `send_keyboard_shortcut` | `{"text": "key combination"}` | `{"text": "Ctrl+N"}` |
|
|
321
328
|
| `eval` | `{"code": "javascript"}` | `{"code": "document.title"}` |
|
|
322
329
|
| `get_title`, `get_url`, `get_body_text` | No args needed | `{}` or omit args |
|
|
@@ -441,6 +448,13 @@ Execute JavaScript commands in the running Electron application via WebSocket.
|
|
|
441
448
|
- `click_by_text`: Click elements by their visible text, aria-label, or title (more reliable than selectors)
|
|
442
449
|
- `fill_input`: Fill input fields by selector, placeholder text, or associated label text
|
|
443
450
|
- `select_option`: Select dropdown options by value or visible text
|
|
451
|
+
- `hover`: Hover over elements (simulates mouseenter/mouseover)
|
|
452
|
+
- `drag`: Drag elements from one location to another (supports dataTransfer)
|
|
453
|
+
- `wait`: Wait for duration, element selector, or text presence
|
|
454
|
+
- `type`: Type text character-by-character into inputs
|
|
455
|
+
- `get_attribute`: Get value of any element attribute
|
|
456
|
+
- `is_visible`: Check if element exists and is visible in viewport
|
|
457
|
+
- `count`: Count elements matching a selector (total and visible)
|
|
444
458
|
- `get_page_structure`: Get organized overview of all page elements (buttons, inputs, selects, links)
|
|
445
459
|
- `get_title`: Get document title
|
|
446
460
|
- `get_url`: Get current URL
|
package/dist/index.js
CHANGED
|
@@ -4289,7 +4289,14 @@ const TakeScreenshotSchema = external_zod_namespaceObject.z.object({
|
|
|
4289
4289
|
.string()
|
|
4290
4290
|
.optional()
|
|
4291
4291
|
.describe('Path to save the screenshot (optional, defaults to temp directory)'),
|
|
4292
|
-
|
|
4292
|
+
targetId: external_zod_namespaceObject.z
|
|
4293
|
+
.string()
|
|
4294
|
+
.optional()
|
|
4295
|
+
.describe('CDP target ID to screenshot a specific window (exact match, takes priority over windowTitle)'),
|
|
4296
|
+
windowTitle: external_zod_namespaceObject.z
|
|
4297
|
+
.string()
|
|
4298
|
+
.optional()
|
|
4299
|
+
.describe('Window title to screenshot (case-insensitive partial match). Use list_electron_windows to see available windows.'),
|
|
4293
4300
|
});
|
|
4294
4301
|
const ReadElectronLogsSchema = external_zod_namespaceObject.z.object({
|
|
4295
4302
|
logType: external_zod_namespaceObject.z
|
|
@@ -4321,17 +4328,26 @@ var ToolName;
|
|
|
4321
4328
|
ToolName["GET_ELECTRON_WINDOW_INFO"] = "get_electron_window_info";
|
|
4322
4329
|
ToolName["LIST_ELECTRON_WINDOWS"] = "list_electron_windows";
|
|
4323
4330
|
})(ToolName || (ToolName = {}));
|
|
4331
|
+
// Helper function to convert Zod schemas to JSON Schema with proper typing
|
|
4332
|
+
// Using 'as any' to work around deep type instantiation issues with zod-to-json-schema
|
|
4333
|
+
const toJsonSchema = (schema) => (0,external_zod_to_json_schema_namespaceObject.zodToJsonSchema)(schema);
|
|
4324
4334
|
// Define tools available to the MCP server
|
|
4325
4335
|
const tools = [
|
|
4326
4336
|
{
|
|
4327
4337
|
name: ToolName.GET_ELECTRON_WINDOW_INFO,
|
|
4328
4338
|
description: 'Get information about running Electron applications and their windows. Automatically detects any Electron app with remote debugging enabled (port 9222).',
|
|
4329
|
-
inputSchema: (
|
|
4339
|
+
inputSchema: toJsonSchema(GetElectronWindowInfoSchema),
|
|
4330
4340
|
},
|
|
4331
4341
|
{
|
|
4332
4342
|
name: ToolName.TAKE_SCREENSHOT,
|
|
4333
|
-
description:
|
|
4334
|
-
|
|
4343
|
+
description: `Take a screenshot of any running Electron application window. Returns base64 image data for AI analysis. No files created unless outputPath is specified.
|
|
4344
|
+
|
|
4345
|
+
Multi-window support:
|
|
4346
|
+
- targetId: Specify a CDP target ID to screenshot a specific window (exact match, takes priority)
|
|
4347
|
+
- windowTitle: Specify a window title to target (case-insensitive partial match)
|
|
4348
|
+
- If neither is specified, screenshots the first available main window (backward compatible)
|
|
4349
|
+
- Use 'list_electron_windows' to see available windows and their IDs`,
|
|
4350
|
+
inputSchema: toJsonSchema(TakeScreenshotSchema),
|
|
4335
4351
|
},
|
|
4336
4352
|
{
|
|
4337
4353
|
name: ToolName.SEND_COMMAND_TO_ELECTRON,
|
|
@@ -4351,6 +4367,15 @@ Enhanced UI interaction commands:
|
|
|
4351
4367
|
- 'get_title', 'get_url', 'get_body_text': Basic page information
|
|
4352
4368
|
- 'eval': Execute custom JavaScript code with enhanced error reporting
|
|
4353
4369
|
|
|
4370
|
+
Additional interaction commands:
|
|
4371
|
+
- 'hover': Hover over an element by selector (triggers mouseenter/mouseover events)
|
|
4372
|
+
- 'drag': Drag from one element to another {"startSelector": "...", "endSelector": "..."}
|
|
4373
|
+
- 'wait': Wait for element, text, or duration {"selector": "..."} or {"text": "..."} or {"duration": 1000}
|
|
4374
|
+
- 'type': Type text character by character {"text": "...", "selector": "..."} (triggers key events)
|
|
4375
|
+
- 'get_attribute': Get element attribute value {"selector": "...", "attribute": "href"}
|
|
4376
|
+
- 'is_visible': Check if element is visible {"selector": "..."} returns visibility info
|
|
4377
|
+
- 'count': Count elements matching selector {"selector": "..."} returns total and visible count
|
|
4378
|
+
|
|
4354
4379
|
IMPORTANT: Arguments must be passed as an object with the correct properties:
|
|
4355
4380
|
|
|
4356
4381
|
Examples:
|
|
@@ -4359,6 +4384,13 @@ Examples:
|
|
|
4359
4384
|
- fill_input: {"placeholder": "Enter name", "value": "John Doe"}
|
|
4360
4385
|
- fill_input: {"selector": "#email", "value": "user@example.com"}
|
|
4361
4386
|
- send_keyboard_shortcut: {"text": "Enter"}
|
|
4387
|
+
- hover: {"selector": ".dropdown-trigger"}
|
|
4388
|
+
- drag: {"startSelector": ".draggable", "endSelector": ".drop-zone"}
|
|
4389
|
+
- wait: {"selector": ".loading-spinner", "timeout": 5000}
|
|
4390
|
+
- type: {"text": "Hello World", "selector": "#input-field"}
|
|
4391
|
+
- get_attribute: {"selector": "a.link", "attribute": "href"}
|
|
4392
|
+
- is_visible: {"selector": ".modal"}
|
|
4393
|
+
- count: {"selector": "li.item"}
|
|
4362
4394
|
- eval: {"code": "document.title"}
|
|
4363
4395
|
|
|
4364
4396
|
Use 'get_page_structure' or 'debug_elements' first to understand available elements, then use specific interaction commands.
|
|
@@ -4368,17 +4400,17 @@ Multi-window support:
|
|
|
4368
4400
|
- windowTitle: Specify a window title to target (case-insensitive partial match)
|
|
4369
4401
|
- If neither is specified, commands are sent to the first available main window (backward compatible)
|
|
4370
4402
|
- Use 'list_electron_windows' to see available windows and their IDs`,
|
|
4371
|
-
inputSchema: (
|
|
4403
|
+
inputSchema: toJsonSchema(SendCommandToElectronSchema),
|
|
4372
4404
|
},
|
|
4373
4405
|
{
|
|
4374
4406
|
name: ToolName.LIST_ELECTRON_WINDOWS,
|
|
4375
4407
|
description: "List all available Electron window targets across all detected applications. Returns window IDs, titles, URLs, and ports. Use the returned IDs with send_command_to_electron's targetId parameter to target specific windows.",
|
|
4376
|
-
inputSchema: (
|
|
4408
|
+
inputSchema: toJsonSchema(ListElectronWindowsSchema),
|
|
4377
4409
|
},
|
|
4378
4410
|
{
|
|
4379
4411
|
name: ToolName.READ_ELECTRON_LOGS,
|
|
4380
4412
|
description: 'Read console logs and output from running Electron applications. Useful for debugging and monitoring app behavior.',
|
|
4381
|
-
inputSchema: (
|
|
4413
|
+
inputSchema: toJsonSchema(ReadElectronLogsSchema),
|
|
4382
4414
|
},
|
|
4383
4415
|
];
|
|
4384
4416
|
|
|
@@ -6277,10 +6309,379 @@ async function sendCommandToElectron(command, args, windowOptions) {
|
|
|
6277
6309
|
|
|
6278
6310
|
return JSON.stringify({ forms, formCount: forms.length }, null, 2);
|
|
6279
6311
|
})()
|
|
6312
|
+
`;
|
|
6313
|
+
break;
|
|
6314
|
+
case 'hover':
|
|
6315
|
+
// Hover over an element
|
|
6316
|
+
const hoverSelector = args?.selector || '';
|
|
6317
|
+
if (!hoverSelector) {
|
|
6318
|
+
return 'ERROR: Missing selector. Use: {"selector": "your-css-selector"}';
|
|
6319
|
+
}
|
|
6320
|
+
if (hoverSelector.includes('javascript:') || hoverSelector.includes('<script')) {
|
|
6321
|
+
return 'Invalid selector: contains dangerous content';
|
|
6322
|
+
}
|
|
6323
|
+
const escapedHoverSelector = JSON.stringify(hoverSelector);
|
|
6324
|
+
javascriptCode = `
|
|
6325
|
+
(function() {
|
|
6326
|
+
try {
|
|
6327
|
+
const element = document.querySelector(${escapedHoverSelector});
|
|
6328
|
+
if (!element) {
|
|
6329
|
+
return 'Element not found: ' + ${escapedHoverSelector};
|
|
6330
|
+
}
|
|
6331
|
+
|
|
6332
|
+
const rect = element.getBoundingClientRect();
|
|
6333
|
+
if (rect.width === 0 || rect.height === 0) {
|
|
6334
|
+
return 'Element not visible';
|
|
6335
|
+
}
|
|
6336
|
+
|
|
6337
|
+
const mouseenterEvent = new MouseEvent('mouseenter', {
|
|
6338
|
+
bubbles: false,
|
|
6339
|
+
cancelable: true,
|
|
6340
|
+
view: window,
|
|
6341
|
+
clientX: rect.left + rect.width / 2,
|
|
6342
|
+
clientY: rect.top + rect.height / 2
|
|
6343
|
+
});
|
|
6344
|
+
|
|
6345
|
+
const mouseoverEvent = new MouseEvent('mouseover', {
|
|
6346
|
+
bubbles: true,
|
|
6347
|
+
cancelable: true,
|
|
6348
|
+
view: window,
|
|
6349
|
+
clientX: rect.left + rect.width / 2,
|
|
6350
|
+
clientY: rect.top + rect.height / 2
|
|
6351
|
+
});
|
|
6352
|
+
|
|
6353
|
+
element.dispatchEvent(mouseenterEvent);
|
|
6354
|
+
element.dispatchEvent(mouseoverEvent);
|
|
6355
|
+
|
|
6356
|
+
return 'Hovered over element: ' + element.tagName +
|
|
6357
|
+
(element.textContent ? ' - "' + element.textContent.substring(0, 50).trim() + '"' : '');
|
|
6358
|
+
} catch (e) {
|
|
6359
|
+
return 'Error hovering: ' + e.message;
|
|
6360
|
+
}
|
|
6361
|
+
})();
|
|
6362
|
+
`;
|
|
6363
|
+
break;
|
|
6364
|
+
case 'drag':
|
|
6365
|
+
// Drag from one element to another
|
|
6366
|
+
const startSel = args?.startSelector || args?.selector || '';
|
|
6367
|
+
const endSel = args?.endSelector || '';
|
|
6368
|
+
if (!startSel || !endSel) {
|
|
6369
|
+
return 'ERROR: Missing selectors. Use: {"startSelector": "source-element", "endSelector": "target-element"}';
|
|
6370
|
+
}
|
|
6371
|
+
if (startSel.includes('javascript:') || endSel.includes('javascript:')) {
|
|
6372
|
+
return 'Invalid selector: contains dangerous content';
|
|
6373
|
+
}
|
|
6374
|
+
const escapedStartSel = JSON.stringify(startSel);
|
|
6375
|
+
const escapedEndSel = JSON.stringify(endSel);
|
|
6376
|
+
javascriptCode = `
|
|
6377
|
+
(function() {
|
|
6378
|
+
try {
|
|
6379
|
+
const startElement = document.querySelector(${escapedStartSel});
|
|
6380
|
+
const endElement = document.querySelector(${escapedEndSel});
|
|
6381
|
+
|
|
6382
|
+
if (!startElement) return 'Start element not found: ' + ${escapedStartSel};
|
|
6383
|
+
if (!endElement) return 'End element not found: ' + ${escapedEndSel};
|
|
6384
|
+
|
|
6385
|
+
const startRect = startElement.getBoundingClientRect();
|
|
6386
|
+
const endRect = endElement.getBoundingClientRect();
|
|
6387
|
+
|
|
6388
|
+
const startX = startRect.left + startRect.width / 2;
|
|
6389
|
+
const startY = startRect.top + startRect.height / 2;
|
|
6390
|
+
const endX = endRect.left + endRect.width / 2;
|
|
6391
|
+
const endY = endRect.top + endRect.height / 2;
|
|
6392
|
+
|
|
6393
|
+
const dataTransfer = new DataTransfer();
|
|
6394
|
+
dataTransfer.setData('text/plain', startElement.id || '');
|
|
6395
|
+
dataTransfer.effectAllowed = 'copyMove';
|
|
6396
|
+
dataTransfer.dropEffect = 'move';
|
|
6397
|
+
|
|
6398
|
+
const dragStartEvent = new DragEvent('dragstart', {
|
|
6399
|
+
bubbles: true, cancelable: true, clientX: startX, clientY: startY, dataTransfer: dataTransfer
|
|
6400
|
+
});
|
|
6401
|
+
const dragEvent = new DragEvent('drag', {
|
|
6402
|
+
bubbles: true, cancelable: true, clientX: endX, clientY: endY, dataTransfer: dataTransfer
|
|
6403
|
+
});
|
|
6404
|
+
const dragEnterEvent = new DragEvent('dragenter', {
|
|
6405
|
+
bubbles: true, cancelable: true, clientX: endX, clientY: endY, dataTransfer: dataTransfer
|
|
6406
|
+
});
|
|
6407
|
+
const dragOverEvent = new DragEvent('dragover', {
|
|
6408
|
+
bubbles: true, cancelable: true, clientX: endX, clientY: endY, dataTransfer: dataTransfer
|
|
6409
|
+
});
|
|
6410
|
+
const dropEvent = new DragEvent('drop', {
|
|
6411
|
+
bubbles: true, cancelable: true, clientX: endX, clientY: endY, dataTransfer: dataTransfer
|
|
6412
|
+
});
|
|
6413
|
+
const dragEndEvent = new DragEvent('dragend', {
|
|
6414
|
+
bubbles: true, cancelable: true, clientX: endX, clientY: endY, dataTransfer: dataTransfer
|
|
6415
|
+
});
|
|
6416
|
+
|
|
6417
|
+
startElement.dispatchEvent(dragStartEvent);
|
|
6418
|
+
startElement.dispatchEvent(dragEvent);
|
|
6419
|
+
endElement.dispatchEvent(dragEnterEvent);
|
|
6420
|
+
endElement.dispatchEvent(dragOverEvent);
|
|
6421
|
+
endElement.dispatchEvent(dropEvent);
|
|
6422
|
+
startElement.dispatchEvent(dragEndEvent);
|
|
6423
|
+
|
|
6424
|
+
return 'Dragged from ' + startElement.tagName + ' to ' + endElement.tagName;
|
|
6425
|
+
} catch (e) {
|
|
6426
|
+
return 'Error performing drag: ' + e.message;
|
|
6427
|
+
}
|
|
6428
|
+
})();
|
|
6429
|
+
`;
|
|
6430
|
+
break;
|
|
6431
|
+
case 'wait':
|
|
6432
|
+
// Wait for element, text, or specified time
|
|
6433
|
+
const waitSelector = args?.selector || '';
|
|
6434
|
+
const waitText = args?.text || '';
|
|
6435
|
+
const waitDuration = args?.duration || 0;
|
|
6436
|
+
const waitTimeout = args?.timeout || 5000;
|
|
6437
|
+
if (!waitSelector && !waitText && !waitDuration) {
|
|
6438
|
+
return 'ERROR: Specify selector, text, or duration. Use: {"selector": "..."} or {"text": "..."} or {"duration": 1000}';
|
|
6439
|
+
}
|
|
6440
|
+
if (waitDuration > 0) {
|
|
6441
|
+
javascriptCode = `
|
|
6442
|
+
(function() {
|
|
6443
|
+
return new Promise(resolve => {
|
|
6444
|
+
setTimeout(() => resolve('Waited ${waitDuration}ms'), ${waitDuration});
|
|
6445
|
+
});
|
|
6446
|
+
})();
|
|
6447
|
+
`;
|
|
6448
|
+
}
|
|
6449
|
+
else if (waitSelector) {
|
|
6450
|
+
const escapedWaitSelector = JSON.stringify(waitSelector);
|
|
6451
|
+
javascriptCode = `
|
|
6452
|
+
(function() {
|
|
6453
|
+
return new Promise((resolve) => {
|
|
6454
|
+
const startTime = Date.now();
|
|
6455
|
+
const timeout = ${waitTimeout};
|
|
6456
|
+
|
|
6457
|
+
function check() {
|
|
6458
|
+
const element = document.querySelector(${escapedWaitSelector});
|
|
6459
|
+
if (element && element.getBoundingClientRect().width > 0) {
|
|
6460
|
+
resolve('Element found: ' + ${escapedWaitSelector} + ' (after ' + (Date.now() - startTime) + 'ms)');
|
|
6461
|
+
return;
|
|
6462
|
+
}
|
|
6463
|
+
if (Date.now() - startTime > timeout) {
|
|
6464
|
+
resolve('Timeout waiting for element: ' + ${escapedWaitSelector});
|
|
6465
|
+
return;
|
|
6466
|
+
}
|
|
6467
|
+
setTimeout(check, 100);
|
|
6468
|
+
}
|
|
6469
|
+
check();
|
|
6470
|
+
});
|
|
6471
|
+
})();
|
|
6472
|
+
`;
|
|
6473
|
+
}
|
|
6474
|
+
else if (waitText) {
|
|
6475
|
+
const escapedWaitText = JSON.stringify(waitText);
|
|
6476
|
+
javascriptCode = `
|
|
6477
|
+
(function() {
|
|
6478
|
+
return new Promise((resolve) => {
|
|
6479
|
+
const startTime = Date.now();
|
|
6480
|
+
const timeout = ${waitTimeout};
|
|
6481
|
+
|
|
6482
|
+
function check() {
|
|
6483
|
+
if (document.body.innerText.includes(${escapedWaitText})) {
|
|
6484
|
+
resolve('Text found: ' + ${escapedWaitText} + ' (after ' + (Date.now() - startTime) + 'ms)');
|
|
6485
|
+
return;
|
|
6486
|
+
}
|
|
6487
|
+
if (Date.now() - startTime > timeout) {
|
|
6488
|
+
resolve('Timeout waiting for text: ' + ${escapedWaitText});
|
|
6489
|
+
return;
|
|
6490
|
+
}
|
|
6491
|
+
setTimeout(check, 100);
|
|
6492
|
+
}
|
|
6493
|
+
check();
|
|
6494
|
+
});
|
|
6495
|
+
})();
|
|
6496
|
+
`;
|
|
6497
|
+
}
|
|
6498
|
+
else {
|
|
6499
|
+
javascriptCode = `'Invalid wait parameters'`;
|
|
6500
|
+
}
|
|
6501
|
+
break;
|
|
6502
|
+
case 'type':
|
|
6503
|
+
// Type text character by character (different from fill which sets value directly)
|
|
6504
|
+
const typeText = args?.text || '';
|
|
6505
|
+
const typeSelector = args?.selector || '';
|
|
6506
|
+
const typeSlowly = args?.slowly !== false; // Default to true for realistic typing
|
|
6507
|
+
if (!typeText) {
|
|
6508
|
+
return 'ERROR: Missing text. Use: {"text": "text to type"} or {"text": "...", "selector": "input-selector"}';
|
|
6509
|
+
}
|
|
6510
|
+
const escapedTypeText = JSON.stringify(typeText);
|
|
6511
|
+
const escapedTypeSelector = typeSelector ? JSON.stringify(typeSelector) : 'null';
|
|
6512
|
+
javascriptCode = `
|
|
6513
|
+
(function() {
|
|
6514
|
+
try {
|
|
6515
|
+
let element;
|
|
6516
|
+
if (${escapedTypeSelector}) {
|
|
6517
|
+
element = document.querySelector(${escapedTypeSelector});
|
|
6518
|
+
if (!element) return 'Element not found: ' + ${escapedTypeSelector};
|
|
6519
|
+
} else {
|
|
6520
|
+
element = document.activeElement;
|
|
6521
|
+
if (!element || element === document.body) {
|
|
6522
|
+
return 'No element focused. Use {"selector": "..."} to specify an input';
|
|
6523
|
+
}
|
|
6524
|
+
}
|
|
6525
|
+
|
|
6526
|
+
element.focus();
|
|
6527
|
+
|
|
6528
|
+
const text = ${escapedTypeText};
|
|
6529
|
+
const slowly = ${typeSlowly};
|
|
6530
|
+
|
|
6531
|
+
if (slowly) {
|
|
6532
|
+
return new Promise((resolve) => {
|
|
6533
|
+
let index = 0;
|
|
6534
|
+
|
|
6535
|
+
function typeNext() {
|
|
6536
|
+
if (index >= text.length) {
|
|
6537
|
+
resolve('Typed: "' + text + '" into ' + element.tagName);
|
|
6538
|
+
return;
|
|
6539
|
+
}
|
|
6540
|
+
|
|
6541
|
+
const char = text[index];
|
|
6542
|
+
|
|
6543
|
+
element.dispatchEvent(new KeyboardEvent('keydown', {
|
|
6544
|
+
key: char,
|
|
6545
|
+
code: 'Key' + char.toUpperCase(),
|
|
6546
|
+
bubbles: true
|
|
6547
|
+
}));
|
|
6548
|
+
|
|
6549
|
+
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
6550
|
+
element.value += char;
|
|
6551
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
6552
|
+
}
|
|
6553
|
+
|
|
6554
|
+
element.dispatchEvent(new KeyboardEvent('keyup', {
|
|
6555
|
+
key: char,
|
|
6556
|
+
code: 'Key' + char.toUpperCase(),
|
|
6557
|
+
bubbles: true
|
|
6558
|
+
}));
|
|
6559
|
+
|
|
6560
|
+
index++;
|
|
6561
|
+
setTimeout(typeNext, 50);
|
|
6562
|
+
}
|
|
6563
|
+
|
|
6564
|
+
typeNext();
|
|
6565
|
+
});
|
|
6566
|
+
} else {
|
|
6567
|
+
if (element.tagName === 'INPUT' || element.tagName === 'TEXTAREA') {
|
|
6568
|
+
element.value += text;
|
|
6569
|
+
element.dispatchEvent(new Event('input', { bubbles: true }));
|
|
6570
|
+
element.dispatchEvent(new Event('change', { bubbles: true }));
|
|
6571
|
+
}
|
|
6572
|
+
return 'Typed: "' + text + '" into ' + element.tagName;
|
|
6573
|
+
}
|
|
6574
|
+
} catch (e) {
|
|
6575
|
+
return 'Error typing: ' + e.message;
|
|
6576
|
+
}
|
|
6577
|
+
})();
|
|
6578
|
+
`;
|
|
6579
|
+
break;
|
|
6580
|
+
case 'get_attribute':
|
|
6581
|
+
// Get an attribute value from an element
|
|
6582
|
+
const attrSelector = args?.selector || '';
|
|
6583
|
+
const attrName = args?.attribute || '';
|
|
6584
|
+
if (!attrSelector || !attrName) {
|
|
6585
|
+
return 'ERROR: Missing selector or attribute. Use: {"selector": "...", "attribute": "href"}';
|
|
6586
|
+
}
|
|
6587
|
+
const escapedAttrSelector = JSON.stringify(attrSelector);
|
|
6588
|
+
const escapedAttrName = JSON.stringify(attrName);
|
|
6589
|
+
javascriptCode = `
|
|
6590
|
+
(function() {
|
|
6591
|
+
try {
|
|
6592
|
+
const element = document.querySelector(${escapedAttrSelector});
|
|
6593
|
+
if (!element) {
|
|
6594
|
+
return 'Element not found: ' + ${escapedAttrSelector};
|
|
6595
|
+
}
|
|
6596
|
+
|
|
6597
|
+
const value = element.getAttribute(${escapedAttrName});
|
|
6598
|
+
if (value === null) {
|
|
6599
|
+
return 'Attribute not found: ' + ${escapedAttrName} + ' on ' + element.tagName;
|
|
6600
|
+
}
|
|
6601
|
+
|
|
6602
|
+
return JSON.stringify({
|
|
6603
|
+
selector: ${escapedAttrSelector},
|
|
6604
|
+
attribute: ${escapedAttrName},
|
|
6605
|
+
value: value
|
|
6606
|
+
});
|
|
6607
|
+
} catch (e) {
|
|
6608
|
+
return 'Error getting attribute: ' + e.message;
|
|
6609
|
+
}
|
|
6610
|
+
})();
|
|
6611
|
+
`;
|
|
6612
|
+
break;
|
|
6613
|
+
case 'is_visible':
|
|
6614
|
+
// Check if an element is visible
|
|
6615
|
+
const visSelector = args?.selector || '';
|
|
6616
|
+
if (!visSelector) {
|
|
6617
|
+
return 'ERROR: Missing selector. Use: {"selector": "your-css-selector"}';
|
|
6618
|
+
}
|
|
6619
|
+
const escapedVisSelector = JSON.stringify(visSelector);
|
|
6620
|
+
javascriptCode = `
|
|
6621
|
+
(function() {
|
|
6622
|
+
try {
|
|
6623
|
+
const element = document.querySelector(${escapedVisSelector});
|
|
6624
|
+
if (!element) {
|
|
6625
|
+
return JSON.stringify({ selector: ${escapedVisSelector}, exists: false, visible: false });
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
const rect = element.getBoundingClientRect();
|
|
6629
|
+
const style = window.getComputedStyle(element);
|
|
6630
|
+
|
|
6631
|
+
const isVisible = rect.width > 0 &&
|
|
6632
|
+
rect.height > 0 &&
|
|
6633
|
+
style.visibility !== 'hidden' &&
|
|
6634
|
+
style.display !== 'none' &&
|
|
6635
|
+
style.opacity !== '0';
|
|
6636
|
+
|
|
6637
|
+
const inViewport = rect.top < window.innerHeight &&
|
|
6638
|
+
rect.bottom > 0 &&
|
|
6639
|
+
rect.left < window.innerWidth &&
|
|
6640
|
+
rect.right > 0;
|
|
6641
|
+
|
|
6642
|
+
return JSON.stringify({
|
|
6643
|
+
selector: ${escapedVisSelector},
|
|
6644
|
+
exists: true,
|
|
6645
|
+
visible: isVisible,
|
|
6646
|
+
inViewport: inViewport,
|
|
6647
|
+
dimensions: { width: rect.width, height: rect.height },
|
|
6648
|
+
position: { top: rect.top, left: rect.left }
|
|
6649
|
+
});
|
|
6650
|
+
} catch (e) {
|
|
6651
|
+
return 'Error checking visibility: ' + e.message;
|
|
6652
|
+
}
|
|
6653
|
+
})();
|
|
6654
|
+
`;
|
|
6655
|
+
break;
|
|
6656
|
+
case 'count':
|
|
6657
|
+
// Count elements matching a selector
|
|
6658
|
+
const countSelector = args?.selector || '';
|
|
6659
|
+
if (!countSelector) {
|
|
6660
|
+
return 'ERROR: Missing selector. Use: {"selector": "your-css-selector"}';
|
|
6661
|
+
}
|
|
6662
|
+
const escapedCountSelector = JSON.stringify(countSelector);
|
|
6663
|
+
javascriptCode = `
|
|
6664
|
+
(function() {
|
|
6665
|
+
try {
|
|
6666
|
+
const elements = document.querySelectorAll(${escapedCountSelector});
|
|
6667
|
+
const visibleCount = Array.from(elements).filter(el => {
|
|
6668
|
+
const rect = el.getBoundingClientRect();
|
|
6669
|
+
return rect.width > 0 && rect.height > 0;
|
|
6670
|
+
}).length;
|
|
6671
|
+
|
|
6672
|
+
return JSON.stringify({
|
|
6673
|
+
selector: ${escapedCountSelector},
|
|
6674
|
+
total: elements.length,
|
|
6675
|
+
visible: visibleCount
|
|
6676
|
+
});
|
|
6677
|
+
} catch (e) {
|
|
6678
|
+
return 'Error counting elements: ' + e.message;
|
|
6679
|
+
}
|
|
6680
|
+
})();
|
|
6280
6681
|
`;
|
|
6281
6682
|
break;
|
|
6282
6683
|
case 'console_log':
|
|
6283
|
-
javascriptCode = `console.log('MCP Command:',
|
|
6684
|
+
javascriptCode = `console.log('MCP Command:', ${JSON.stringify(args?.message ?? 'Hello from MCP!')}); 'Console message sent'`;
|
|
6284
6685
|
break;
|
|
6285
6686
|
case 'eval':
|
|
6286
6687
|
const rawCode = typeof args === 'string' ? args : args?.code || command;
|
|
@@ -6567,9 +6968,11 @@ const promises_namespaceObject = require("fs/promises");
|
|
|
6567
6968
|
/**
|
|
6568
6969
|
* Take a screenshot of the running Electron application using Chrome DevTools Protocol
|
|
6569
6970
|
*/
|
|
6570
|
-
async function takeScreenshot(
|
|
6971
|
+
async function takeScreenshot(options = {}) {
|
|
6972
|
+
const { outputPath, targetId, windowTitle } = options;
|
|
6571
6973
|
logger.info('📸 Taking screenshot of Electron application', {
|
|
6572
6974
|
outputPath,
|
|
6975
|
+
targetId,
|
|
6573
6976
|
windowTitle,
|
|
6574
6977
|
timestamp: new Date().toISOString(),
|
|
6575
6978
|
});
|
|
@@ -6579,12 +6982,39 @@ async function takeScreenshot(outputPath, windowTitle) {
|
|
|
6579
6982
|
if (apps.length === 0) {
|
|
6580
6983
|
throw new Error('No running Electron applications found with remote debugging enabled');
|
|
6581
6984
|
}
|
|
6582
|
-
//
|
|
6985
|
+
// Find target app and target based on options
|
|
6986
|
+
// Priority: targetId > windowTitle > first available window
|
|
6583
6987
|
let targetApp = apps[0];
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6988
|
+
let targetInfo = null;
|
|
6989
|
+
if (targetId) {
|
|
6990
|
+
// Search for exact targetId match across all apps
|
|
6991
|
+
for (const app of apps) {
|
|
6992
|
+
const match = app.targets.find((t) => t.id === targetId);
|
|
6993
|
+
if (match) {
|
|
6994
|
+
targetApp = app;
|
|
6995
|
+
targetInfo = { port: app.port, targetId: match.id };
|
|
6996
|
+
logger.debug(`Found target by ID "${targetId}" on port ${app.port}`);
|
|
6997
|
+
break;
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
if (!targetInfo) {
|
|
7001
|
+
throw new Error(`No window found with targetId "${targetId}". Use list_electron_windows to see available targets.`);
|
|
7002
|
+
}
|
|
7003
|
+
}
|
|
7004
|
+
else if (windowTitle) {
|
|
7005
|
+
// Search for case-insensitive partial title match
|
|
7006
|
+
const searchTitle = windowTitle.toLowerCase();
|
|
7007
|
+
for (const app of apps) {
|
|
7008
|
+
const match = app.targets.find((t) => t.title && t.title.toLowerCase().includes(searchTitle));
|
|
7009
|
+
if (match) {
|
|
7010
|
+
targetApp = app;
|
|
7011
|
+
targetInfo = { port: app.port, targetId: match.id };
|
|
7012
|
+
logger.debug(`Found target by title "${windowTitle}" on port ${app.port}`);
|
|
7013
|
+
break;
|
|
7014
|
+
}
|
|
7015
|
+
}
|
|
7016
|
+
if (!targetInfo) {
|
|
7017
|
+
throw new Error(`No window found with title matching "${windowTitle}". Use list_electron_windows to see available targets.`);
|
|
6588
7018
|
}
|
|
6589
7019
|
}
|
|
6590
7020
|
// Connect to the Electron app's debugging port
|
|
@@ -6598,22 +7028,33 @@ async function takeScreenshot(outputPath, windowTitle) {
|
|
|
6598
7028
|
if (pages.length === 0) {
|
|
6599
7029
|
throw new Error('No pages found in the browser context');
|
|
6600
7030
|
}
|
|
6601
|
-
// Find the
|
|
7031
|
+
// Find the target page
|
|
6602
7032
|
let targetPage = pages[0];
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
if (
|
|
6608
|
-
|
|
6609
|
-
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
7033
|
+
if (targetInfo?.targetId) {
|
|
7034
|
+
// If we found a specific target, try to find the matching page
|
|
7035
|
+
// Note: We need to find the page by URL/title since CDP targetId doesn't directly map to Playwright pages
|
|
7036
|
+
const targetData = targetApp.targets.find((t) => t.id === targetInfo.targetId);
|
|
7037
|
+
if (targetData) {
|
|
7038
|
+
for (const page of pages) {
|
|
7039
|
+
const pageUrl = page.url();
|
|
7040
|
+
// Match by URL since that's more reliable than title
|
|
7041
|
+
if (pageUrl === targetData.url) {
|
|
7042
|
+
targetPage = page;
|
|
7043
|
+
break;
|
|
7044
|
+
}
|
|
6615
7045
|
}
|
|
6616
|
-
|
|
7046
|
+
}
|
|
7047
|
+
}
|
|
7048
|
+
else {
|
|
7049
|
+
// Find the main application page (skip DevTools pages)
|
|
7050
|
+
for (const page of pages) {
|
|
7051
|
+
const url = page.url();
|
|
7052
|
+
const title = await page.title().catch(() => '');
|
|
7053
|
+
// Skip DevTools and about:blank pages
|
|
7054
|
+
if (!url.includes('devtools://') &&
|
|
7055
|
+
!url.includes('about:blank') &&
|
|
7056
|
+
title &&
|
|
7057
|
+
!title.includes('DevTools')) {
|
|
6617
7058
|
targetPage = page;
|
|
6618
7059
|
break;
|
|
6619
7060
|
}
|
|
@@ -6677,8 +7118,8 @@ async function handleToolCall(request) {
|
|
|
6677
7118
|
};
|
|
6678
7119
|
}
|
|
6679
7120
|
case ToolName.TAKE_SCREENSHOT: {
|
|
6680
|
-
const { outputPath, windowTitle } = TakeScreenshotSchema.parse(args);
|
|
6681
|
-
const result = await takeScreenshot(outputPath, windowTitle);
|
|
7121
|
+
const { outputPath, targetId, windowTitle } = TakeScreenshotSchema.parse(args);
|
|
7122
|
+
const result = await takeScreenshot({ outputPath, targetId, windowTitle });
|
|
6682
7123
|
const content = [];
|
|
6683
7124
|
if (result.filePath) {
|
|
6684
7125
|
content.push({
|