@democratize-quality/mcp-server 1.2.0 → 1.2.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/cli.js +248 -0
- package/package.json +7 -5
- package/src/chatmodes//360/237/214/220 api-generator.chatmode.md" +409 -0
- package/src/chatmodes//360/237/214/220 api-healer.chatmode.md" +494 -0
- package/src/chatmodes//360/237/214/220 api-planner.chatmode.md" +954 -0
- package/src/config/environments/api-only.js +72 -0
- package/src/config/environments/development.js +73 -0
- package/src/config/environments/production.js +88 -0
- package/src/config/index.js +360 -0
- package/src/config/server.js +60 -0
- package/src/config/tools/api.js +86 -0
- package/src/config/tools/browser.js +109 -0
- package/src/config/tools/default.js +51 -0
- package/src/docs/Agent_README.md +310 -0
- package/src/docs/QUICK_REFERENCE.md +111 -0
- package/src/server.ts +234 -0
- package/src/services/browserService.js +344 -0
- package/src/skills/api-planning/SKILL.md +224 -0
- package/src/skills/test-execution/SKILL.md +777 -0
- package/src/skills/test-generation/SKILL.md +309 -0
- package/src/skills/test-healing/SKILL.md +405 -0
- package/src/tools/api/api-generator.js +1884 -0
- package/src/tools/api/api-healer.js +636 -0
- package/src/tools/api/api-planner.js +2617 -0
- package/src/tools/api/api-project-setup.js +332 -0
- package/src/tools/api/api-request.js +660 -0
- package/src/tools/api/api-session-report.js +1297 -0
- package/src/tools/api/api-session-status.js +414 -0
- package/src/tools/api/prompts/README.md +293 -0
- package/src/tools/api/prompts/generation-prompts.js +722 -0
- package/src/tools/api/prompts/healing-prompts.js +214 -0
- package/src/tools/api/prompts/index.js +44 -0
- package/src/tools/api/prompts/orchestrator.js +353 -0
- package/src/tools/api/prompts/validation-rules.js +358 -0
- package/src/tools/base/ToolBase.js +249 -0
- package/src/tools/base/ToolRegistry.js +288 -0
- package/src/tools/browser/advanced/browser-console.js +403 -0
- package/src/tools/browser/advanced/browser-dialog.js +338 -0
- package/src/tools/browser/advanced/browser-evaluate.js +356 -0
- package/src/tools/browser/advanced/browser-file.js +499 -0
- package/src/tools/browser/advanced/browser-keyboard.js +362 -0
- package/src/tools/browser/advanced/browser-mouse.js +351 -0
- package/src/tools/browser/advanced/browser-network.js +440 -0
- package/src/tools/browser/advanced/browser-pdf.js +426 -0
- package/src/tools/browser/advanced/browser-tabs.js +516 -0
- package/src/tools/browser/advanced/browser-wait.js +397 -0
- package/src/tools/browser/click.js +187 -0
- package/src/tools/browser/close.js +79 -0
- package/src/tools/browser/dom.js +89 -0
- package/src/tools/browser/launch.js +86 -0
- package/src/tools/browser/navigate.js +289 -0
- package/src/tools/browser/screenshot.js +370 -0
- package/src/tools/browser/type.js +193 -0
- package/src/tools/index.js +114 -0
- package/src/utils/agentInstaller.js +437 -0
- package/src/utils/browserHelpers.js +102 -0
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 Democratize Quality
|
|
3
|
+
*
|
|
4
|
+
* This file is part of Democratize Quality MCP Server.
|
|
5
|
+
*
|
|
6
|
+
* Democratize Quality MCP Server is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* Democratize Quality MCP Server is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU Affero General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
17
|
+
* along with Democratize Quality MCP Server. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const ToolBase = require('../base/ToolBase');
|
|
21
|
+
const browserService = require('../../services/browserService');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Enhanced Browser Screenshot Tool
|
|
25
|
+
* Captures screenshots with advanced options including element-specific and full-page captures
|
|
26
|
+
* Inspired by Playwright MCP screenshot capabilities
|
|
27
|
+
*/
|
|
28
|
+
class BrowserScreenshotTool extends ToolBase {
|
|
29
|
+
static definition = {
|
|
30
|
+
name: "browser_screenshot",
|
|
31
|
+
description: "Capture screenshots with advanced options including full page, element-specific, and custom formats with quality control.",
|
|
32
|
+
input_schema: {
|
|
33
|
+
type: "object",
|
|
34
|
+
properties: {
|
|
35
|
+
browserId: {
|
|
36
|
+
type: "string",
|
|
37
|
+
description: "The ID of the browser instance."
|
|
38
|
+
},
|
|
39
|
+
fileName: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "Optional. The name of the file to save the screenshot as (e.g., 'my_page.png'). Saved to the server's configured output directory. If not provided, a timestamped name is used."
|
|
42
|
+
},
|
|
43
|
+
saveToDisk: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
description: "Optional. Whether to save the screenshot to disk on the server. Defaults to true. Set to false to only receive base64 data.",
|
|
46
|
+
default: true
|
|
47
|
+
},
|
|
48
|
+
options: {
|
|
49
|
+
type: "object",
|
|
50
|
+
properties: {
|
|
51
|
+
fullPage: {
|
|
52
|
+
type: "boolean",
|
|
53
|
+
default: false,
|
|
54
|
+
description: "Capture full scrollable page instead of just viewport"
|
|
55
|
+
},
|
|
56
|
+
element: {
|
|
57
|
+
type: "string",
|
|
58
|
+
description: "CSS selector of element to screenshot (captures only that element)"
|
|
59
|
+
},
|
|
60
|
+
format: {
|
|
61
|
+
type: "string",
|
|
62
|
+
enum: ["png", "jpeg", "webp"],
|
|
63
|
+
default: "png",
|
|
64
|
+
description: "Image format"
|
|
65
|
+
},
|
|
66
|
+
quality: {
|
|
67
|
+
type: "number",
|
|
68
|
+
minimum: 0,
|
|
69
|
+
maximum: 100,
|
|
70
|
+
description: "JPEG/WebP quality (0-100, ignored for PNG)"
|
|
71
|
+
},
|
|
72
|
+
clip: {
|
|
73
|
+
type: "object",
|
|
74
|
+
properties: {
|
|
75
|
+
x: { type: "number", description: "X coordinate of clip area" },
|
|
76
|
+
y: { type: "number", description: "Y coordinate of clip area" },
|
|
77
|
+
width: { type: "number", description: "Width of clip area" },
|
|
78
|
+
height: { type: "number", description: "Height of clip area" },
|
|
79
|
+
scale: { type: "number", description: "Scale factor for clip area" }
|
|
80
|
+
},
|
|
81
|
+
description: "Specific area to capture"
|
|
82
|
+
},
|
|
83
|
+
omitBackground: {
|
|
84
|
+
type: "boolean",
|
|
85
|
+
default: false,
|
|
86
|
+
description: "Hide default white background for transparent screenshots"
|
|
87
|
+
},
|
|
88
|
+
captureBeyondViewport: {
|
|
89
|
+
type: "boolean",
|
|
90
|
+
default: false,
|
|
91
|
+
description: "Capture content outside viewport"
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
description: "Screenshot capture options"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
required: ["browserId"]
|
|
98
|
+
},
|
|
99
|
+
output_schema: {
|
|
100
|
+
type: "object",
|
|
101
|
+
properties: {
|
|
102
|
+
success: { type: "boolean", description: "Whether screenshot was captured successfully" },
|
|
103
|
+
imageData: { type: "string", description: "Base64 encoded image data" },
|
|
104
|
+
format: { type: "string", description: "Image format used" },
|
|
105
|
+
fileName: { type: "string", description: "The file name if saved to disk" },
|
|
106
|
+
dimensions: {
|
|
107
|
+
type: "object",
|
|
108
|
+
properties: {
|
|
109
|
+
width: { type: "number" },
|
|
110
|
+
height: { type: "number" }
|
|
111
|
+
},
|
|
112
|
+
description: "Image dimensions"
|
|
113
|
+
},
|
|
114
|
+
fileSize: { type: "number", description: "File size in bytes" },
|
|
115
|
+
element: { type: "string", description: "CSS selector if element screenshot was taken" },
|
|
116
|
+
browserId: { type: "string", description: "The browser instance ID that was used" }
|
|
117
|
+
},
|
|
118
|
+
required: ["success", "imageData", "format", "browserId"]
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
async execute(parameters) {
|
|
123
|
+
const {
|
|
124
|
+
browserId,
|
|
125
|
+
fileName,
|
|
126
|
+
saveToDisk = true,
|
|
127
|
+
options = {}
|
|
128
|
+
} = parameters;
|
|
129
|
+
|
|
130
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
131
|
+
if (!browser) {
|
|
132
|
+
throw new Error(`Browser instance '${browserId}' not found. Please launch a browser first using browser_launch.`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Generate filename if not provided
|
|
136
|
+
const fileExtension = this.getFileExtension(options.format || 'png');
|
|
137
|
+
const finalFileName = fileName || `screenshot_${browserId}_${Date.now()}.${fileExtension}`;
|
|
138
|
+
|
|
139
|
+
console.error(`[BrowserScreenshotTool] Taking screenshot of browser ${browserId}, fileName: ${finalFileName}, saveToDisk: ${saveToDisk}`);
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
let screenshotResult;
|
|
143
|
+
|
|
144
|
+
if (options.element) {
|
|
145
|
+
screenshotResult = await this.captureElementScreenshot(browser.client, options);
|
|
146
|
+
} else {
|
|
147
|
+
screenshotResult = await this.capturePageScreenshot(browser.client, options);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Save to disk if requested
|
|
151
|
+
let savedPath = null;
|
|
152
|
+
if (saveToDisk) {
|
|
153
|
+
savedPath = await this.saveScreenshot(screenshotResult.data, finalFileName);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
console.error(`[BrowserScreenshotTool] Successfully captured screenshot for browser: ${browserId}`);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
imageData: screenshotResult.data,
|
|
161
|
+
format: options.format || 'png',
|
|
162
|
+
fileName: savedPath ? finalFileName : null,
|
|
163
|
+
dimensions: screenshotResult.dimensions,
|
|
164
|
+
fileSize: screenshotResult.fileSize,
|
|
165
|
+
element: options.element || null,
|
|
166
|
+
browserId: browserId
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(`[BrowserScreenshotTool] Failed to take screenshot:`, error.message);
|
|
171
|
+
throw new Error(`Failed to take screenshot of browser ${browserId}: ${error.message}`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Capture page screenshot with options
|
|
177
|
+
*/
|
|
178
|
+
async capturePageScreenshot(client, options) {
|
|
179
|
+
await client.Page.enable();
|
|
180
|
+
|
|
181
|
+
const screenshotOptions = {
|
|
182
|
+
format: options.format || 'png',
|
|
183
|
+
quality: options.format !== 'png' ? options.quality : undefined,
|
|
184
|
+
optimizeForSpeed: false,
|
|
185
|
+
captureBeyondViewport: options.captureBeyondViewport || options.fullPage || false
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Handle clip area
|
|
189
|
+
if (options.clip) {
|
|
190
|
+
screenshotOptions.clip = options.clip;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Handle full page screenshot
|
|
194
|
+
if (options.fullPage && !options.clip) {
|
|
195
|
+
const layoutMetrics = await client.Page.getLayoutMetrics();
|
|
196
|
+
screenshotOptions.clip = {
|
|
197
|
+
x: 0,
|
|
198
|
+
y: 0,
|
|
199
|
+
width: layoutMetrics.contentSize.width,
|
|
200
|
+
height: layoutMetrics.contentSize.height,
|
|
201
|
+
scale: 1
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Handle transparent background
|
|
206
|
+
if (options.omitBackground) {
|
|
207
|
+
// Set background to transparent (requires format support)
|
|
208
|
+
await client.Emulation.setDefaultBackgroundColorOverride({
|
|
209
|
+
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const result = await client.Page.captureScreenshot(screenshotOptions);
|
|
214
|
+
|
|
215
|
+
// Reset background if it was changed
|
|
216
|
+
if (options.omitBackground) {
|
|
217
|
+
await client.Emulation.setDefaultBackgroundColorOverride();
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
data: result.data,
|
|
222
|
+
dimensions: this.getImageDimensions(screenshotOptions.clip),
|
|
223
|
+
fileSize: Buffer.from(result.data, 'base64').length
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Capture element screenshot
|
|
229
|
+
*/
|
|
230
|
+
async captureElementScreenshot(client, options) {
|
|
231
|
+
await client.DOM.enable();
|
|
232
|
+
await client.Page.enable();
|
|
233
|
+
|
|
234
|
+
// Find the element
|
|
235
|
+
const document = await client.DOM.getDocument();
|
|
236
|
+
const element = await client.DOM.querySelector({
|
|
237
|
+
nodeId: document.root.nodeId,
|
|
238
|
+
selector: options.element
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
if (!element.nodeId) {
|
|
242
|
+
throw new Error(`Element not found with selector: ${options.element}`);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Get element bounds
|
|
246
|
+
const box = await client.DOM.getBoxModel({ nodeId: element.nodeId });
|
|
247
|
+
if (!box.model) {
|
|
248
|
+
throw new Error(`Could not get bounds for element: ${options.element}`);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Use content bounds for the clip
|
|
252
|
+
const bounds = box.model.content;
|
|
253
|
+
const clip = {
|
|
254
|
+
x: Math.round(bounds[0]),
|
|
255
|
+
y: Math.round(bounds[1]),
|
|
256
|
+
width: Math.round(bounds[4] - bounds[0]),
|
|
257
|
+
height: Math.round(bounds[5] - bounds[1]),
|
|
258
|
+
scale: 1
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const screenshotOptions = {
|
|
262
|
+
format: options.format || 'png',
|
|
263
|
+
quality: options.format !== 'png' ? options.quality : undefined,
|
|
264
|
+
clip: clip,
|
|
265
|
+
optimizeForSpeed: false
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
// Handle transparent background for element
|
|
269
|
+
if (options.omitBackground) {
|
|
270
|
+
await client.Emulation.setDefaultBackgroundColorOverride({
|
|
271
|
+
color: { r: 0, g: 0, b: 0, a: 0 }
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const result = await client.Page.captureScreenshot(screenshotOptions);
|
|
276
|
+
|
|
277
|
+
if (options.omitBackground) {
|
|
278
|
+
await client.Emulation.setDefaultBackgroundColorOverride();
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
data: result.data,
|
|
283
|
+
dimensions: { width: clip.width, height: clip.height },
|
|
284
|
+
fileSize: Buffer.from(result.data, 'base64').length
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Save screenshot to disk
|
|
290
|
+
*/
|
|
291
|
+
async saveScreenshot(base64Data, fileName) {
|
|
292
|
+
const fs = require('fs').promises;
|
|
293
|
+
const path = require('path');
|
|
294
|
+
const os = require('os');
|
|
295
|
+
|
|
296
|
+
// Use output directory from environment or default to user home directory
|
|
297
|
+
const defaultOutputDir = process.env.HOME
|
|
298
|
+
? path.join(process.env.HOME, '.mcp-browser-control')
|
|
299
|
+
: path.join(os.tmpdir(), 'mcp-browser-control');
|
|
300
|
+
const outputDir = process.env.OUTPUT_DIR || defaultOutputDir;
|
|
301
|
+
const filePath = path.join(outputDir, fileName);
|
|
302
|
+
|
|
303
|
+
// Ensure directory exists
|
|
304
|
+
await fs.mkdir(outputDir, { recursive: true });
|
|
305
|
+
|
|
306
|
+
// Save file
|
|
307
|
+
const buffer = Buffer.from(base64Data, 'base64');
|
|
308
|
+
await fs.writeFile(filePath, buffer);
|
|
309
|
+
|
|
310
|
+
return filePath;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Get file extension for format
|
|
315
|
+
*/
|
|
316
|
+
getFileExtension(format) {
|
|
317
|
+
const extensions = {
|
|
318
|
+
'png': 'png',
|
|
319
|
+
'jpeg': 'jpg',
|
|
320
|
+
'webp': 'webp'
|
|
321
|
+
};
|
|
322
|
+
return extensions[format] || 'png';
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Get image dimensions from clip or default
|
|
327
|
+
*/
|
|
328
|
+
getImageDimensions(clip) {
|
|
329
|
+
if (clip) {
|
|
330
|
+
return {
|
|
331
|
+
width: clip.width,
|
|
332
|
+
height: clip.height
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
return null; // Will be determined by actual screenshot
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/**
|
|
339
|
+
* Capture screenshot with scroll handling
|
|
340
|
+
*/
|
|
341
|
+
async captureWithScroll(client, options) {
|
|
342
|
+
// Save current scroll position
|
|
343
|
+
const scrollPosition = await client.Runtime.evaluate({
|
|
344
|
+
expression: '({ x: window.scrollX, y: window.scrollY })'
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
try {
|
|
348
|
+
// Scroll to top for full page capture
|
|
349
|
+
if (options.fullPage) {
|
|
350
|
+
await client.Runtime.evaluate({
|
|
351
|
+
expression: 'window.scrollTo(0, 0)'
|
|
352
|
+
});
|
|
353
|
+
// Wait for scroll to complete
|
|
354
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const result = await this.capturePageScreenshot(client, options);
|
|
358
|
+
|
|
359
|
+
return result;
|
|
360
|
+
} finally {
|
|
361
|
+
// Restore original scroll position
|
|
362
|
+
const original = scrollPosition.result.value;
|
|
363
|
+
await client.Runtime.evaluate({
|
|
364
|
+
expression: `window.scrollTo(${original.x}, ${original.y})`
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
module.exports = BrowserScreenshotTool;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 Democratize Quality
|
|
3
|
+
*
|
|
4
|
+
* This file is part of Democratize Quality MCP Server.
|
|
5
|
+
*
|
|
6
|
+
* Democratize Quality MCP Server is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* Democratize Quality MCP Server is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU Affero General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
17
|
+
* along with Democratize Quality MCP Server. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const ToolBase = require('../base/ToolBase');
|
|
21
|
+
const browserService = require('../../services/browserService');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Browser Type Tool
|
|
25
|
+
* Types text into a specific input field in the browser with Playwright-style locators
|
|
26
|
+
*/
|
|
27
|
+
class BrowserTypeTool extends ToolBase {
|
|
28
|
+
static definition = {
|
|
29
|
+
name: "browser_type",
|
|
30
|
+
description: "Types text into a specific input field in the browser.",
|
|
31
|
+
input_schema: {
|
|
32
|
+
type: "object",
|
|
33
|
+
properties: {
|
|
34
|
+
browserId: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "The ID of the browser instance."
|
|
37
|
+
},
|
|
38
|
+
locatorType: {
|
|
39
|
+
type: "string",
|
|
40
|
+
enum: ["css", "xpath", "text", "role", "label", "placeholder", "testId", "altText"],
|
|
41
|
+
default: "css",
|
|
42
|
+
description: "The type of locator to use"
|
|
43
|
+
},
|
|
44
|
+
locatorValue: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "The locator value (CSS selector, XPath, text content, etc.)"
|
|
47
|
+
},
|
|
48
|
+
text: {
|
|
49
|
+
type: "string",
|
|
50
|
+
description: "The text to type into the input field."
|
|
51
|
+
},
|
|
52
|
+
// Keep backward compatibility
|
|
53
|
+
selector: {
|
|
54
|
+
type: "string",
|
|
55
|
+
description: "The CSS selector of the input field (deprecated, use locatorType and locatorValue instead)."
|
|
56
|
+
},
|
|
57
|
+
options: {
|
|
58
|
+
type: "object",
|
|
59
|
+
properties: {
|
|
60
|
+
timeout: {
|
|
61
|
+
type: "number",
|
|
62
|
+
default: 30000,
|
|
63
|
+
description: "Timeout in milliseconds"
|
|
64
|
+
},
|
|
65
|
+
delay: {
|
|
66
|
+
type: "number",
|
|
67
|
+
default: 0,
|
|
68
|
+
description: "Delay between keystrokes in milliseconds"
|
|
69
|
+
},
|
|
70
|
+
clear: {
|
|
71
|
+
type: "boolean",
|
|
72
|
+
default: true,
|
|
73
|
+
description: "Clear the field before typing"
|
|
74
|
+
},
|
|
75
|
+
noWaitAfter: {
|
|
76
|
+
type: "boolean",
|
|
77
|
+
default: false,
|
|
78
|
+
description: "Do not wait for navigation after typing"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
description: "Additional typing options"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
anyOf: [
|
|
85
|
+
{ required: ["browserId", "locatorType", "locatorValue", "text"] },
|
|
86
|
+
{ required: ["browserId", "selector", "text"] }
|
|
87
|
+
]
|
|
88
|
+
},
|
|
89
|
+
output_schema: {
|
|
90
|
+
type: "object",
|
|
91
|
+
properties: {
|
|
92
|
+
success: { type: "boolean", description: "Indicates if the typing was successful." },
|
|
93
|
+
locatorType: { type: "string", description: "The locator type that was used." },
|
|
94
|
+
locatorValue: { type: "string", description: "The locator value that was used." },
|
|
95
|
+
text: { type: "string", description: "The text that was typed." },
|
|
96
|
+
browserId: { type: "string", description: "The browser instance ID that was used." },
|
|
97
|
+
elementFound: { type: "boolean", description: "Whether the element was found." },
|
|
98
|
+
message: { type: "string", description: "Success or error message." }
|
|
99
|
+
},
|
|
100
|
+
required: ["success", "browserId"]
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
async execute(parameters) {
|
|
105
|
+
const { browserId, locatorType, locatorValue, selector, text, options = {} } = parameters;
|
|
106
|
+
|
|
107
|
+
// Handle backward compatibility
|
|
108
|
+
let finalLocatorType, finalLocatorValue;
|
|
109
|
+
if (selector) {
|
|
110
|
+
finalLocatorType = "css";
|
|
111
|
+
finalLocatorValue = selector;
|
|
112
|
+
} else {
|
|
113
|
+
finalLocatorType = locatorType || "css";
|
|
114
|
+
finalLocatorValue = locatorValue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
console.error(`[BrowserTypeTool] Typing in browser ${browserId} with locator: ${finalLocatorType}="${finalLocatorValue}"`);
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// Convert Playwright-style locator to appropriate format for browser service
|
|
121
|
+
const elementSelector = this.convertLocatorToSelector(finalLocatorType, finalLocatorValue);
|
|
122
|
+
|
|
123
|
+
await browserService.typeIntoElement(browserId, elementSelector, text, options);
|
|
124
|
+
|
|
125
|
+
console.error(`[BrowserTypeTool] Successfully typed in browser: ${browserId}`);
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
success: true,
|
|
129
|
+
browserId: browserId,
|
|
130
|
+
locatorType: finalLocatorType,
|
|
131
|
+
locatorValue: finalLocatorValue,
|
|
132
|
+
text: text,
|
|
133
|
+
elementFound: true,
|
|
134
|
+
message: `Successfully typed "${text}" into element with ${finalLocatorType} locator: ${finalLocatorValue}`
|
|
135
|
+
};
|
|
136
|
+
} catch (error) {
|
|
137
|
+
console.error(`[BrowserTypeTool] Failed to type in element:`, error.message);
|
|
138
|
+
|
|
139
|
+
return {
|
|
140
|
+
success: false,
|
|
141
|
+
browserId: browserId,
|
|
142
|
+
locatorType: finalLocatorType,
|
|
143
|
+
locatorValue: finalLocatorValue,
|
|
144
|
+
text: text,
|
|
145
|
+
elementFound: false,
|
|
146
|
+
message: `Failed to type into element: ${error.message}`
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Convert Playwright-style locator to CSS selector or XPath
|
|
153
|
+
*/
|
|
154
|
+
convertLocatorToSelector(locatorType, locatorValue) {
|
|
155
|
+
switch (locatorType) {
|
|
156
|
+
case "css":
|
|
157
|
+
return locatorValue;
|
|
158
|
+
|
|
159
|
+
case "xpath":
|
|
160
|
+
return locatorValue;
|
|
161
|
+
|
|
162
|
+
case "text":
|
|
163
|
+
// Convert text locator to XPath for input elements
|
|
164
|
+
return `//input[contains(@placeholder, "${locatorValue}")] | //input[contains(@value, "${locatorValue}")] | //textarea[contains(text(), "${locatorValue}")] | //label[contains(text(), "${locatorValue}")]/following-sibling::input | //label[contains(text(), "${locatorValue}")]/following::input[1]`;
|
|
165
|
+
|
|
166
|
+
case "role":
|
|
167
|
+
// Convert role locator to CSS attribute selector
|
|
168
|
+
return `[role="${locatorValue}"]`;
|
|
169
|
+
|
|
170
|
+
case "label":
|
|
171
|
+
// Convert label locator to XPath for label association
|
|
172
|
+
return `//input[@aria-label="${locatorValue}"] | //input[@id=//label[contains(text(), "${locatorValue}")]/@for] | //label[contains(text(), "${locatorValue}")]/following-sibling::input | //label[contains(text(), "${locatorValue}")]/following::input[1]`;
|
|
173
|
+
|
|
174
|
+
case "placeholder":
|
|
175
|
+
// Convert placeholder locator to CSS attribute selector
|
|
176
|
+
return `[placeholder="${locatorValue}"]`;
|
|
177
|
+
|
|
178
|
+
case "testId":
|
|
179
|
+
// Convert test ID to CSS attribute selector (assuming data-testid)
|
|
180
|
+
return `[data-testid="${locatorValue}"]`;
|
|
181
|
+
|
|
182
|
+
case "altText":
|
|
183
|
+
// Convert alt text to CSS attribute selector (less common for input fields)
|
|
184
|
+
return `[alt="${locatorValue}"]`;
|
|
185
|
+
|
|
186
|
+
default:
|
|
187
|
+
// Default to CSS selector
|
|
188
|
+
return locatorValue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = BrowserTypeTool;
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (C) 2025 Democratize Quality
|
|
3
|
+
*
|
|
4
|
+
* This file is part of Democratize Quality MCP Server.
|
|
5
|
+
*
|
|
6
|
+
* Democratize Quality MCP Server is free software: you can redistribute it and/or modify
|
|
7
|
+
* it under the terms of the GNU Affero General Public License as published by
|
|
8
|
+
* the Free Software Foundation, either version 3 of the License, or
|
|
9
|
+
* (at your option) any later version.
|
|
10
|
+
*
|
|
11
|
+
* Democratize Quality MCP Server is distributed in the hope that it will be useful,
|
|
12
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
13
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
14
|
+
* GNU Affero General Public License for more details.
|
|
15
|
+
*
|
|
16
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
17
|
+
* along with Democratize Quality MCP Server. If not, see <https://www.gnu.org/licenses/>.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const ToolRegistry = require('./base/ToolRegistry');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Initialize and configure the tool registry
|
|
25
|
+
* This file serves as the main entry point for the tools system
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
// Create the global tool registry instance
|
|
29
|
+
const toolRegistry = new ToolRegistry();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Initialize the tool registry by discovering and loading all tools
|
|
33
|
+
* @param {boolean} debugMode - Whether to enable debug logging
|
|
34
|
+
* @returns {Promise<ToolRegistry>} - The initialized tool registry
|
|
35
|
+
*/
|
|
36
|
+
async function initializeTools(debugMode = false) {
|
|
37
|
+
if (debugMode) {
|
|
38
|
+
console.error('[Tools] Initializing tool system...');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// Get the tools directory path
|
|
43
|
+
const toolsDir = path.join(__dirname);
|
|
44
|
+
|
|
45
|
+
// Discover and load all tools
|
|
46
|
+
await toolRegistry.discoverTools(toolsDir, debugMode);
|
|
47
|
+
|
|
48
|
+
// Log registry statistics
|
|
49
|
+
const stats = toolRegistry.getStats();
|
|
50
|
+
console.error(`[Tools] Tool system initialized successfully:`);
|
|
51
|
+
console.error(`[Tools] - Total tools: ${stats.total_tools}`);
|
|
52
|
+
if (debugMode) {
|
|
53
|
+
console.error(`[Tools] - Available tools: ${stats.tool_names.join(', ')}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return toolRegistry;
|
|
57
|
+
|
|
58
|
+
} catch (error) {
|
|
59
|
+
console.error('[Tools] Failed to initialize tool system:', error.message);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the tool registry instance
|
|
66
|
+
* @returns {ToolRegistry} - The tool registry instance
|
|
67
|
+
*/
|
|
68
|
+
function getToolRegistry() {
|
|
69
|
+
return toolRegistry;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Get all tool definitions for MCP tools/list response
|
|
74
|
+
* @returns {Array} - Array of tool definitions
|
|
75
|
+
*/
|
|
76
|
+
function getToolDefinitions() {
|
|
77
|
+
return toolRegistry.getDefinitions();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Execute a tool by name
|
|
82
|
+
* @param {string} toolName - The name of the tool to execute
|
|
83
|
+
* @param {object} parameters - The parameters to pass to the tool
|
|
84
|
+
* @returns {Promise<object>} - The tool execution result
|
|
85
|
+
*/
|
|
86
|
+
async function executeTool(toolName, parameters) {
|
|
87
|
+
return await toolRegistry.executeTool(toolName, parameters);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Check if a tool is available
|
|
92
|
+
* @param {string} toolName - The name of the tool to check
|
|
93
|
+
* @returns {boolean} - True if the tool is available
|
|
94
|
+
*/
|
|
95
|
+
function isToolAvailable(toolName) {
|
|
96
|
+
return toolRegistry.hasTool(toolName);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get list of available tool names
|
|
101
|
+
* @returns {Array<string>} - Array of available tool names
|
|
102
|
+
*/
|
|
103
|
+
function getAvailableTools() {
|
|
104
|
+
return toolRegistry.getToolNames();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
initializeTools,
|
|
109
|
+
getToolRegistry,
|
|
110
|
+
getToolDefinitions,
|
|
111
|
+
executeTool,
|
|
112
|
+
isToolAvailable,
|
|
113
|
+
getAvailableTools
|
|
114
|
+
};
|