@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.
Files changed (56) hide show
  1. package/cli.js +248 -0
  2. package/package.json +7 -5
  3. package/src/chatmodes//360/237/214/220 api-generator.chatmode.md" +409 -0
  4. package/src/chatmodes//360/237/214/220 api-healer.chatmode.md" +494 -0
  5. package/src/chatmodes//360/237/214/220 api-planner.chatmode.md" +954 -0
  6. package/src/config/environments/api-only.js +72 -0
  7. package/src/config/environments/development.js +73 -0
  8. package/src/config/environments/production.js +88 -0
  9. package/src/config/index.js +360 -0
  10. package/src/config/server.js +60 -0
  11. package/src/config/tools/api.js +86 -0
  12. package/src/config/tools/browser.js +109 -0
  13. package/src/config/tools/default.js +51 -0
  14. package/src/docs/Agent_README.md +310 -0
  15. package/src/docs/QUICK_REFERENCE.md +111 -0
  16. package/src/server.ts +234 -0
  17. package/src/services/browserService.js +344 -0
  18. package/src/skills/api-planning/SKILL.md +224 -0
  19. package/src/skills/test-execution/SKILL.md +777 -0
  20. package/src/skills/test-generation/SKILL.md +309 -0
  21. package/src/skills/test-healing/SKILL.md +405 -0
  22. package/src/tools/api/api-generator.js +1884 -0
  23. package/src/tools/api/api-healer.js +636 -0
  24. package/src/tools/api/api-planner.js +2617 -0
  25. package/src/tools/api/api-project-setup.js +332 -0
  26. package/src/tools/api/api-request.js +660 -0
  27. package/src/tools/api/api-session-report.js +1297 -0
  28. package/src/tools/api/api-session-status.js +414 -0
  29. package/src/tools/api/prompts/README.md +293 -0
  30. package/src/tools/api/prompts/generation-prompts.js +722 -0
  31. package/src/tools/api/prompts/healing-prompts.js +214 -0
  32. package/src/tools/api/prompts/index.js +44 -0
  33. package/src/tools/api/prompts/orchestrator.js +353 -0
  34. package/src/tools/api/prompts/validation-rules.js +358 -0
  35. package/src/tools/base/ToolBase.js +249 -0
  36. package/src/tools/base/ToolRegistry.js +288 -0
  37. package/src/tools/browser/advanced/browser-console.js +403 -0
  38. package/src/tools/browser/advanced/browser-dialog.js +338 -0
  39. package/src/tools/browser/advanced/browser-evaluate.js +356 -0
  40. package/src/tools/browser/advanced/browser-file.js +499 -0
  41. package/src/tools/browser/advanced/browser-keyboard.js +362 -0
  42. package/src/tools/browser/advanced/browser-mouse.js +351 -0
  43. package/src/tools/browser/advanced/browser-network.js +440 -0
  44. package/src/tools/browser/advanced/browser-pdf.js +426 -0
  45. package/src/tools/browser/advanced/browser-tabs.js +516 -0
  46. package/src/tools/browser/advanced/browser-wait.js +397 -0
  47. package/src/tools/browser/click.js +187 -0
  48. package/src/tools/browser/close.js +79 -0
  49. package/src/tools/browser/dom.js +89 -0
  50. package/src/tools/browser/launch.js +86 -0
  51. package/src/tools/browser/navigate.js +289 -0
  52. package/src/tools/browser/screenshot.js +370 -0
  53. package/src/tools/browser/type.js +193 -0
  54. package/src/tools/index.js +114 -0
  55. package/src/utils/agentInstaller.js +437 -0
  56. package/src/utils/browserHelpers.js +102 -0
@@ -0,0 +1,397 @@
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 Wait Tool - Provides sophisticated waiting strategies
25
+ * Inspired by Playwright MCP wait capabilities
26
+ */
27
+ class BrowserWaitTool extends ToolBase {
28
+ static definition = {
29
+ name: "browser_wait",
30
+ description: "Wait for various conditions including elements, text, network requests, or time delays. Supports complex waiting scenarios.",
31
+ input_schema: {
32
+ type: "object",
33
+ properties: {
34
+ browserId: {
35
+ type: "string",
36
+ description: "The ID of the browser instance"
37
+ },
38
+ condition: {
39
+ type: "string",
40
+ enum: ["time", "element", "text", "textGone", "url", "networkIdle", "domContentLoaded", "load"],
41
+ description: "The condition to wait for"
42
+ },
43
+ value: {
44
+ type: "string",
45
+ description: "Value for the condition (selector for element, text content, URL pattern, etc.)"
46
+ },
47
+ timeout: {
48
+ type: "number",
49
+ default: 30000,
50
+ description: "Maximum wait time in milliseconds"
51
+ },
52
+ time: {
53
+ type: "number",
54
+ description: "Time to wait in seconds (for 'time' condition)"
55
+ },
56
+ state: {
57
+ type: "string",
58
+ enum: ["visible", "hidden", "attached", "detached"],
59
+ default: "visible",
60
+ description: "Element state to wait for (for 'element' condition)"
61
+ },
62
+ networkIdleTime: {
63
+ type: "number",
64
+ default: 500,
65
+ description: "Time in ms to consider network idle (no requests for this duration)"
66
+ }
67
+ },
68
+ required: ["browserId", "condition"]
69
+ },
70
+ output_schema: {
71
+ type: "object",
72
+ properties: {
73
+ success: { type: "boolean", description: "Whether the wait condition was met" },
74
+ condition: { type: "string", description: "The condition that was waited for" },
75
+ value: { type: "string", description: "The value/selector that was waited for" },
76
+ actualValue: { type: "string", description: "The actual value found (for text conditions)" },
77
+ waitTime: { type: "number", description: "Actual time waited in milliseconds" },
78
+ timedOut: { type: "boolean", description: "Whether the wait timed out" },
79
+ browserId: { type: "string", description: "Browser instance ID" }
80
+ },
81
+ required: ["success", "condition", "browserId"]
82
+ }
83
+ };
84
+
85
+ async execute(parameters) {
86
+ const {
87
+ browserId,
88
+ condition,
89
+ value,
90
+ timeout = 30000,
91
+ time,
92
+ state = 'visible',
93
+ networkIdleTime = 500
94
+ } = parameters;
95
+
96
+ const browser = browserService.getBrowserInstance(browserId);
97
+ if (!browser) {
98
+ throw new Error(`Browser instance '${browserId}' not found`);
99
+ }
100
+
101
+ const client = browser.client;
102
+ const startTime = Date.now();
103
+
104
+ let result = {
105
+ success: false,
106
+ condition: condition,
107
+ browserId: browserId,
108
+ waitTime: 0,
109
+ timedOut: false
110
+ };
111
+
112
+ try {
113
+ switch (condition) {
114
+ case 'time':
115
+ if (!time) {
116
+ throw new Error('Time value is required for time condition');
117
+ }
118
+ await this.waitForTime(time * 1000);
119
+ result.success = true;
120
+ result.value = `${time} seconds`;
121
+ break;
122
+
123
+ case 'element':
124
+ if (!value) {
125
+ throw new Error('Selector is required for element condition');
126
+ }
127
+ const elementResult = await this.waitForElement(client, value, state, timeout);
128
+ result.success = elementResult.found;
129
+ result.value = value;
130
+ result.state = state;
131
+ break;
132
+
133
+ case 'text':
134
+ if (!value) {
135
+ throw new Error('Text is required for text condition');
136
+ }
137
+ const textResult = await this.waitForText(client, value, true, timeout);
138
+ result.success = textResult.found;
139
+ result.value = value;
140
+ result.actualValue = textResult.actualText;
141
+ break;
142
+
143
+ case 'textGone':
144
+ if (!value) {
145
+ throw new Error('Text is required for textGone condition');
146
+ }
147
+ const textGoneResult = await this.waitForText(client, value, false, timeout);
148
+ result.success = textGoneResult.found;
149
+ result.value = value;
150
+ break;
151
+
152
+ case 'url':
153
+ if (!value) {
154
+ throw new Error('URL pattern is required for url condition');
155
+ }
156
+ const urlResult = await this.waitForUrl(client, value, timeout);
157
+ result.success = urlResult.found;
158
+ result.value = value;
159
+ result.actualValue = urlResult.actualUrl;
160
+ break;
161
+
162
+ case 'networkIdle':
163
+ const networkResult = await this.waitForNetworkIdle(client, networkIdleTime, timeout);
164
+ result.success = networkResult.idle;
165
+ result.value = `${networkIdleTime}ms idle`;
166
+ break;
167
+
168
+ case 'domContentLoaded':
169
+ const domResult = await this.waitForDOMContentLoaded(client, timeout);
170
+ result.success = domResult.loaded;
171
+ break;
172
+
173
+ case 'load':
174
+ const loadResult = await this.waitForLoad(client, timeout);
175
+ result.success = loadResult.loaded;
176
+ break;
177
+
178
+ default:
179
+ throw new Error(`Unsupported wait condition: ${condition}`);
180
+ }
181
+ } catch (error) {
182
+ if (error.message.includes('timeout')) {
183
+ result.timedOut = true;
184
+ } else {
185
+ throw error;
186
+ }
187
+ }
188
+
189
+ result.waitTime = Date.now() - startTime;
190
+ return result;
191
+ }
192
+
193
+ /**
194
+ * Wait for a specific amount of time
195
+ */
196
+ async waitForTime(milliseconds) {
197
+ return new Promise(resolve => setTimeout(resolve, milliseconds));
198
+ }
199
+
200
+ /**
201
+ * Wait for element to be in specified state
202
+ */
203
+ async waitForElement(client, selector, state, timeout) {
204
+ const startTime = Date.now();
205
+
206
+ while (Date.now() - startTime < timeout) {
207
+ const result = await client.Runtime.evaluate({
208
+ expression: `
209
+ (() => {
210
+ const element = document.querySelector('${selector}');
211
+ if (!element) return { exists: false };
212
+
213
+ const rect = element.getBoundingClientRect();
214
+ const isVisible = rect.width > 0 && rect.height > 0 &&
215
+ window.getComputedStyle(element).visibility !== 'hidden' &&
216
+ window.getComputedStyle(element).display !== 'none';
217
+
218
+ return {
219
+ exists: true,
220
+ visible: isVisible,
221
+ attached: element.isConnected
222
+ };
223
+ })()
224
+ `
225
+ });
226
+
227
+ const elementData = result.result.value;
228
+
229
+ if (!elementData) {
230
+ if (state === 'detached') return { found: true };
231
+ await this.waitForTime(100);
232
+ continue;
233
+ }
234
+
235
+ switch (state) {
236
+ case 'visible':
237
+ if (elementData.exists && elementData.visible) return { found: true };
238
+ break;
239
+ case 'hidden':
240
+ if (elementData.exists && !elementData.visible) return { found: true };
241
+ break;
242
+ case 'attached':
243
+ if (elementData.exists && elementData.attached) return { found: true };
244
+ break;
245
+ case 'detached':
246
+ if (!elementData.exists || !elementData.attached) return { found: true };
247
+ break;
248
+ }
249
+
250
+ await this.waitForTime(100);
251
+ }
252
+
253
+ throw new Error(`Timeout waiting for element '${selector}' to be ${state}`);
254
+ }
255
+
256
+ /**
257
+ * Wait for text to appear or disappear
258
+ */
259
+ async waitForText(client, text, shouldAppear, timeout) {
260
+ const startTime = Date.now();
261
+
262
+ while (Date.now() - startTime < timeout) {
263
+ const result = await client.Runtime.evaluate({
264
+ expression: `
265
+ (() => {
266
+ const bodyText = document.body.innerText || document.body.textContent || '';
267
+ const found = bodyText.includes('${text}');
268
+ return { found: found, bodyText: bodyText.substring(0, 500) };
269
+ })()
270
+ `
271
+ });
272
+
273
+ const textData = result.result.value;
274
+
275
+ if (shouldAppear && textData.found) {
276
+ return { found: true, actualText: text };
277
+ } else if (!shouldAppear && !textData.found) {
278
+ return { found: true };
279
+ }
280
+
281
+ await this.waitForTime(100);
282
+ }
283
+
284
+ const condition = shouldAppear ? 'appear' : 'disappear';
285
+ throw new Error(`Timeout waiting for text '${text}' to ${condition}`);
286
+ }
287
+
288
+ /**
289
+ * Wait for URL to match pattern
290
+ */
291
+ async waitForUrl(client, urlPattern, timeout) {
292
+ const startTime = Date.now();
293
+
294
+ while (Date.now() - startTime < timeout) {
295
+ const result = await client.Runtime.evaluate({
296
+ expression: 'window.location.href'
297
+ });
298
+
299
+ const currentUrl = result.result.value;
300
+ const regex = new RegExp(urlPattern);
301
+
302
+ if (regex.test(currentUrl)) {
303
+ return { found: true, actualUrl: currentUrl };
304
+ }
305
+
306
+ await this.waitForTime(100);
307
+ }
308
+
309
+ throw new Error(`Timeout waiting for URL to match pattern '${urlPattern}'`);
310
+ }
311
+
312
+ /**
313
+ * Wait for network to be idle
314
+ */
315
+ async waitForNetworkIdle(client, idleTime, timeout) {
316
+ // Enable network monitoring
317
+ await client.Network.enable();
318
+
319
+ let requestCount = 0;
320
+ let lastRequestTime = Date.now();
321
+
322
+ const requestStarted = () => {
323
+ requestCount++;
324
+ lastRequestTime = Date.now();
325
+ };
326
+
327
+ const requestFinished = () => {
328
+ requestCount--;
329
+ lastRequestTime = Date.now();
330
+ };
331
+
332
+ client.Network.requestWillBeSent(requestStarted);
333
+ client.Network.responseReceived(requestFinished);
334
+
335
+ const startTime = Date.now();
336
+
337
+ while (Date.now() - startTime < timeout) {
338
+ const timeSinceLastRequest = Date.now() - lastRequestTime;
339
+
340
+ if (requestCount === 0 && timeSinceLastRequest >= idleTime) {
341
+ return { idle: true };
342
+ }
343
+
344
+ await this.waitForTime(100);
345
+ }
346
+
347
+ throw new Error(`Timeout waiting for network idle (${idleTime}ms)`);
348
+ }
349
+
350
+ /**
351
+ * Wait for DOM content loaded
352
+ */
353
+ async waitForDOMContentLoaded(client, timeout) {
354
+ const startTime = Date.now();
355
+
356
+ while (Date.now() - startTime < timeout) {
357
+ const result = await client.Runtime.evaluate({
358
+ expression: 'document.readyState'
359
+ });
360
+
361
+ const readyState = result.result.value;
362
+
363
+ if (readyState === 'interactive' || readyState === 'complete') {
364
+ return { loaded: true };
365
+ }
366
+
367
+ await this.waitForTime(100);
368
+ }
369
+
370
+ throw new Error('Timeout waiting for DOM content loaded');
371
+ }
372
+
373
+ /**
374
+ * Wait for page load complete
375
+ */
376
+ async waitForLoad(client, timeout) {
377
+ const startTime = Date.now();
378
+
379
+ while (Date.now() - startTime < timeout) {
380
+ const result = await client.Runtime.evaluate({
381
+ expression: 'document.readyState'
382
+ });
383
+
384
+ const readyState = result.result.value;
385
+
386
+ if (readyState === 'complete') {
387
+ return { loaded: true };
388
+ }
389
+
390
+ await this.waitForTime(100);
391
+ }
392
+
393
+ throw new Error('Timeout waiting for page load complete');
394
+ }
395
+ }
396
+
397
+ module.exports = BrowserWaitTool;
@@ -0,0 +1,187 @@
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 Click Tool
25
+ * Simulates a click on a specific element in the browser with Playwright-style locators
26
+ */
27
+ class BrowserClickTool extends ToolBase {
28
+ static definition = {
29
+ name: "browser_click",
30
+ description: "Simulates a click on a specific element 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
+ // Keep backward compatibility
49
+ selector: {
50
+ type: "string",
51
+ description: "The CSS selector of the element to click (deprecated, use locatorType and locatorValue instead)."
52
+ },
53
+ options: {
54
+ type: "object",
55
+ properties: {
56
+ timeout: {
57
+ type: "number",
58
+ default: 30000,
59
+ description: "Timeout in milliseconds"
60
+ },
61
+ force: {
62
+ type: "boolean",
63
+ default: false,
64
+ description: "Force click even if element is not actionable"
65
+ },
66
+ noWaitAfter: {
67
+ type: "boolean",
68
+ default: false,
69
+ description: "Do not wait for navigation after click"
70
+ },
71
+ button: {
72
+ type: "string",
73
+ enum: ["left", "right", "middle"],
74
+ default: "left",
75
+ description: "Mouse button to click"
76
+ }
77
+ },
78
+ description: "Additional click options"
79
+ }
80
+ },
81
+ anyOf: [
82
+ { required: ["browserId", "locatorType", "locatorValue"] },
83
+ { required: ["browserId", "selector"] }
84
+ ]
85
+ },
86
+ output_schema: {
87
+ type: "object",
88
+ properties: {
89
+ success: { type: "boolean", description: "Indicates if the click was successful." },
90
+ locatorType: { type: "string", description: "The locator type that was used." },
91
+ locatorValue: { type: "string", description: "The locator value that was used." },
92
+ browserId: { type: "string", description: "The browser instance ID that was used." },
93
+ elementFound: { type: "boolean", description: "Whether the element was found." },
94
+ message: { type: "string", description: "Success or error message." }
95
+ },
96
+ required: ["success", "browserId"]
97
+ }
98
+ };
99
+
100
+ async execute(parameters) {
101
+ const { browserId, locatorType, locatorValue, selector, options = {} } = parameters;
102
+
103
+ // Handle backward compatibility
104
+ let finalLocatorType, finalLocatorValue;
105
+ if (selector) {
106
+ finalLocatorType = "css";
107
+ finalLocatorValue = selector;
108
+ } else {
109
+ finalLocatorType = locatorType || "css";
110
+ finalLocatorValue = locatorValue;
111
+ }
112
+
113
+ console.error(`[BrowserClickTool] Simulating click in browser ${browserId} with locator: ${finalLocatorType}="${finalLocatorValue}"`);
114
+
115
+ try {
116
+ // Convert Playwright-style locator to appropriate format for browser service
117
+ const elementSelector = this.convertLocatorToSelector(finalLocatorType, finalLocatorValue);
118
+
119
+ await browserService.clickElement(browserId, elementSelector, options);
120
+
121
+ console.error(`[BrowserClickTool] Successfully clicked element in browser: ${browserId}`);
122
+
123
+ return {
124
+ success: true,
125
+ browserId: browserId,
126
+ locatorType: finalLocatorType,
127
+ locatorValue: finalLocatorValue,
128
+ elementFound: true,
129
+ message: `Successfully clicked element with ${finalLocatorType} locator: ${finalLocatorValue}`
130
+ };
131
+ } catch (error) {
132
+ console.error(`[BrowserClickTool] Failed to click element:`, error.message);
133
+
134
+ return {
135
+ success: false,
136
+ browserId: browserId,
137
+ locatorType: finalLocatorType,
138
+ locatorValue: finalLocatorValue,
139
+ elementFound: false,
140
+ message: `Failed to click element: ${error.message}`
141
+ };
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Convert Playwright-style locator to CSS selector or XPath
147
+ */
148
+ convertLocatorToSelector(locatorType, locatorValue) {
149
+ switch (locatorType) {
150
+ case "css":
151
+ return locatorValue;
152
+
153
+ case "xpath":
154
+ return locatorValue;
155
+
156
+ case "text":
157
+ // Convert text locator to XPath
158
+ return `//*[contains(text(), "${locatorValue}")]`;
159
+
160
+ case "role":
161
+ // Convert role locator to CSS attribute selector
162
+ return `[role="${locatorValue}"]`;
163
+
164
+ case "label":
165
+ // Convert label locator to XPath for label association
166
+ return `//input[@aria-label="${locatorValue}"] | //input[@id=//label[contains(text(), "${locatorValue}")]/@for]`;
167
+
168
+ case "placeholder":
169
+ // Convert placeholder locator to CSS attribute selector
170
+ return `[placeholder="${locatorValue}"]`;
171
+
172
+ case "testId":
173
+ // Convert test ID to CSS attribute selector (assuming data-testid)
174
+ return `[data-testid="${locatorValue}"]`;
175
+
176
+ case "altText":
177
+ // Convert alt text to CSS attribute selector
178
+ return `[alt="${locatorValue}"]`;
179
+
180
+ default:
181
+ // Default to CSS selector
182
+ return locatorValue;
183
+ }
184
+ }
185
+ }
186
+
187
+ module.exports = BrowserClickTool;
@@ -0,0 +1,79 @@
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 Close Tool
25
+ * Closes a specific browser instance and cleans up its resources
26
+ */
27
+ class BrowserCloseTool extends ToolBase {
28
+ static definition = {
29
+ name: "browser_close",
30
+ description: "Closes a specific browser instance and cleans up its resources. Always call this when done with a browser.",
31
+ input_schema: {
32
+ type: "object",
33
+ properties: {
34
+ browserId: {
35
+ type: "string",
36
+ description: "The ID of the browser instance to close."
37
+ }
38
+ },
39
+ required: ["browserId"]
40
+ },
41
+ output_schema: {
42
+ type: "object",
43
+ properties: {
44
+ message: { type: "string", description: "Confirmation message of successful closure." },
45
+ browserId: { type: "string", description: "The browser instance ID that was closed." }
46
+ },
47
+ required: ["message", "browserId"]
48
+ }
49
+ };
50
+
51
+ async execute(parameters) {
52
+ const { browserId } = parameters;
53
+
54
+ console.error(`[BrowserCloseTool] Closing browser: ${browserId}`);
55
+
56
+ try {
57
+ await browserService.closeBrowser(browserId);
58
+
59
+ console.error(`[BrowserCloseTool] Successfully closed browser: ${browserId}`);
60
+
61
+ return {
62
+ message: `Browser ${browserId} closed successfully`,
63
+ browserId: browserId
64
+ };
65
+
66
+ } catch (error) {
67
+ console.error(`[BrowserCloseTool] Failed to close browser:`, error.message);
68
+
69
+ // Provide more specific error messages
70
+ if (error.message.includes('not found')) {
71
+ throw new Error(`Browser instance '${browserId}' not found or already closed.`);
72
+ }
73
+
74
+ throw new Error(`Failed to close browser ${browserId}: ${error.message}`);
75
+ }
76
+ }
77
+ }
78
+
79
+ module.exports = BrowserCloseTool;