@democratize-quality/mcp-server 1.0.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.
Files changed (48) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +423 -0
  3. package/browserControl.js +113 -0
  4. package/cli.js +187 -0
  5. package/docs/api/tool-reference.md +317 -0
  6. package/docs/api_tools_usage.md +477 -0
  7. package/docs/development/adding-tools.md +274 -0
  8. package/docs/development/configuration.md +332 -0
  9. package/docs/examples/authentication.md +124 -0
  10. package/docs/examples/basic-automation.md +105 -0
  11. package/docs/getting-started.md +214 -0
  12. package/docs/index.md +61 -0
  13. package/mcpServer.js +280 -0
  14. package/package.json +83 -0
  15. package/run-server.js +140 -0
  16. package/src/config/environments/api-only.js +53 -0
  17. package/src/config/environments/development.js +54 -0
  18. package/src/config/environments/production.js +69 -0
  19. package/src/config/index.js +341 -0
  20. package/src/config/server.js +41 -0
  21. package/src/config/tools/api.js +67 -0
  22. package/src/config/tools/browser.js +90 -0
  23. package/src/config/tools/default.js +32 -0
  24. package/src/services/browserService.js +325 -0
  25. package/src/tools/api/api-request.js +641 -0
  26. package/src/tools/api/api-session-report.js +1262 -0
  27. package/src/tools/api/api-session-status.js +395 -0
  28. package/src/tools/base/ToolBase.js +230 -0
  29. package/src/tools/base/ToolRegistry.js +269 -0
  30. package/src/tools/browser/advanced/browser-console.js +384 -0
  31. package/src/tools/browser/advanced/browser-dialog.js +319 -0
  32. package/src/tools/browser/advanced/browser-evaluate.js +337 -0
  33. package/src/tools/browser/advanced/browser-file.js +480 -0
  34. package/src/tools/browser/advanced/browser-keyboard.js +343 -0
  35. package/src/tools/browser/advanced/browser-mouse.js +332 -0
  36. package/src/tools/browser/advanced/browser-network.js +421 -0
  37. package/src/tools/browser/advanced/browser-pdf.js +407 -0
  38. package/src/tools/browser/advanced/browser-tabs.js +497 -0
  39. package/src/tools/browser/advanced/browser-wait.js +378 -0
  40. package/src/tools/browser/click.js +168 -0
  41. package/src/tools/browser/close.js +60 -0
  42. package/src/tools/browser/dom.js +70 -0
  43. package/src/tools/browser/launch.js +67 -0
  44. package/src/tools/browser/navigate.js +270 -0
  45. package/src/tools/browser/screenshot.js +351 -0
  46. package/src/tools/browser/type.js +174 -0
  47. package/src/tools/index.js +95 -0
  48. package/src/utils/browserHelpers.js +83 -0
@@ -0,0 +1,319 @@
1
+ const ToolBase = require('../../base/ToolBase');
2
+ const browserService = require('../../../services/browserService');
3
+
4
+ /**
5
+ * Dialog Tool - Handle browser dialogs (alert, confirm, prompt)
6
+ * Inspired by Playwright MCP dialog capabilities
7
+ */
8
+ class BrowserDialogTool extends ToolBase {
9
+ static definition = {
10
+ name: "browser_dialog",
11
+ description: "Handle browser dialogs including alert, confirm, and prompt dialogs with automatic detection and response.",
12
+ input_schema: {
13
+ type: "object",
14
+ properties: {
15
+ browserId: {
16
+ type: "string",
17
+ description: "The ID of the browser instance"
18
+ },
19
+ action: {
20
+ type: "string",
21
+ enum: ["handle", "dismiss", "accept", "getInfo", "setupHandler"],
22
+ description: "Dialog action to perform"
23
+ },
24
+ accept: {
25
+ type: "boolean",
26
+ default: true,
27
+ description: "Whether to accept or dismiss the dialog (for handle action)"
28
+ },
29
+ promptText: {
30
+ type: "string",
31
+ description: "Text to enter in prompt dialogs"
32
+ },
33
+ autoHandle: {
34
+ type: "boolean",
35
+ default: false,
36
+ description: "Whether to automatically handle future dialogs (for setupHandler action)"
37
+ },
38
+ defaultResponse: {
39
+ type: "object",
40
+ properties: {
41
+ accept: { type: "boolean", description: "Default accept/dismiss behavior" },
42
+ promptText: { type: "string", description: "Default text for prompts" }
43
+ },
44
+ description: "Default responses for auto-handling dialogs"
45
+ }
46
+ },
47
+ required: ["browserId", "action"]
48
+ },
49
+ output_schema: {
50
+ type: "object",
51
+ properties: {
52
+ success: { type: "boolean", description: "Whether the operation was successful" },
53
+ action: { type: "string", description: "The action that was performed" },
54
+ dialog: {
55
+ type: "object",
56
+ properties: {
57
+ type: { type: "string" },
58
+ message: { type: "string" },
59
+ defaultValue: { type: "string" },
60
+ handled: { type: "boolean" },
61
+ response: { type: "string" }
62
+ },
63
+ description: "Dialog information"
64
+ },
65
+ autoHandling: { type: "boolean", description: "Whether auto-handling is enabled" },
66
+ browserId: { type: "string", description: "Browser instance ID" }
67
+ },
68
+ required: ["success", "action", "browserId"]
69
+ }
70
+ };
71
+
72
+ constructor() {
73
+ super();
74
+ this.dialogStates = new Map(); // browserId -> dialog state
75
+ this.autoHandlers = new Map(); // browserId -> auto-handler config
76
+ }
77
+
78
+ async execute(parameters) {
79
+ const {
80
+ browserId,
81
+ action,
82
+ accept = true,
83
+ promptText,
84
+ autoHandle = false,
85
+ defaultResponse = {}
86
+ } = parameters;
87
+
88
+ const browser = browserService.getBrowserInstance(browserId);
89
+ if (!browser) {
90
+ throw new Error(`Browser instance '${browserId}' not found`);
91
+ }
92
+
93
+ const client = browser.client;
94
+
95
+ let result = {
96
+ success: false,
97
+ action: action,
98
+ browserId: browserId
99
+ };
100
+
101
+ switch (action) {
102
+ case 'setupHandler':
103
+ await this.setupDialogHandler(client, browserId, autoHandle, defaultResponse);
104
+ result.success = true;
105
+ result.autoHandling = autoHandle;
106
+ result.message = autoHandle ? 'Auto dialog handling enabled' : 'Dialog monitoring enabled';
107
+ break;
108
+
109
+ case 'handle':
110
+ const handleResult = await this.handleDialog(browserId, accept, promptText);
111
+ result.success = true;
112
+ result.dialog = handleResult;
113
+ result.message = 'Dialog handled';
114
+ break;
115
+
116
+ case 'accept':
117
+ const acceptResult = await this.handleDialog(browserId, true, promptText);
118
+ result.success = true;
119
+ result.dialog = acceptResult;
120
+ result.message = 'Dialog accepted';
121
+ break;
122
+
123
+ case 'dismiss':
124
+ const dismissResult = await this.handleDialog(browserId, false);
125
+ result.success = true;
126
+ result.dialog = dismissResult;
127
+ result.message = 'Dialog dismissed';
128
+ break;
129
+
130
+ case 'getInfo':
131
+ const dialogInfo = this.getDialogInfo(browserId);
132
+ result.success = true;
133
+ result.dialog = dialogInfo.dialog;
134
+ result.autoHandling = dialogInfo.autoHandling;
135
+ break;
136
+
137
+ default:
138
+ throw new Error(`Unsupported dialog action: ${action}`);
139
+ }
140
+
141
+ return result;
142
+ }
143
+
144
+ /**
145
+ * Setup dialog handler with auto-handling capability
146
+ */
147
+ async setupDialogHandler(client, browserId, autoHandle, defaultResponse) {
148
+ // Enable Page domain for dialog events
149
+ await client.Page.enable();
150
+
151
+ // Store auto-handler configuration
152
+ if (autoHandle) {
153
+ this.autoHandlers.set(browserId, {
154
+ enabled: true,
155
+ accept: defaultResponse.accept !== undefined ? defaultResponse.accept : true,
156
+ promptText: defaultResponse.promptText || ''
157
+ });
158
+ } else {
159
+ this.autoHandlers.delete(browserId);
160
+ }
161
+
162
+ // Set up dialog event listener
163
+ client.Page.javascriptDialogOpening((params) => {
164
+ const dialogInfo = {
165
+ type: params.type,
166
+ message: params.message,
167
+ defaultValue: params.defaultPrompt || '',
168
+ timestamp: new Date().toISOString(),
169
+ handled: false
170
+ };
171
+
172
+ this.dialogStates.set(browserId, dialogInfo);
173
+
174
+ // Auto-handle if configured
175
+ const autoHandler = this.autoHandlers.get(browserId);
176
+ if (autoHandler && autoHandler.enabled) {
177
+ setTimeout(async () => {
178
+ try {
179
+ await this.handleDialog(
180
+ browserId,
181
+ autoHandler.accept,
182
+ params.type === 'prompt' ? autoHandler.promptText : undefined
183
+ );
184
+ } catch (error) {
185
+ console.error(`[Dialog] Auto-handle failed for ${browserId}:`, error.message);
186
+ }
187
+ }, 100); // Small delay to ensure dialog is fully loaded
188
+ }
189
+ });
190
+
191
+ client.Page.javascriptDialogClosed(() => {
192
+ const dialogState = this.dialogStates.get(browserId);
193
+ if (dialogState) {
194
+ dialogState.handled = true;
195
+ dialogState.closedAt = new Date().toISOString();
196
+ }
197
+ });
198
+ }
199
+
200
+ /**
201
+ * Handle an active dialog
202
+ */
203
+ async handleDialog(browserId, accept, promptText) {
204
+ const browser = browserService.getBrowserInstance(browserId);
205
+ if (!browser) {
206
+ throw new Error(`Browser instance '${browserId}' not found`);
207
+ }
208
+
209
+ const dialogState = this.dialogStates.get(browserId);
210
+ if (!dialogState) {
211
+ throw new Error('No active dialog found');
212
+ }
213
+
214
+ if (dialogState.handled) {
215
+ throw new Error('Dialog has already been handled');
216
+ }
217
+
218
+ const client = browser.client;
219
+
220
+ try {
221
+ // Handle the dialog
222
+ await client.Page.handleJavaScriptDialog({
223
+ accept: accept,
224
+ promptText: dialogState.type === 'prompt' ? (promptText || '') : undefined
225
+ });
226
+
227
+ // Update dialog state
228
+ dialogState.handled = true;
229
+ dialogState.response = accept ? 'accepted' : 'dismissed';
230
+ dialogState.promptText = promptText;
231
+ dialogState.handledAt = new Date().toISOString();
232
+
233
+ return dialogState;
234
+ } catch (error) {
235
+ throw new Error(`Failed to handle dialog: ${error.message}`);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Get information about current dialog state
241
+ */
242
+ getDialogInfo(browserId) {
243
+ const dialogState = this.dialogStates.get(browserId);
244
+ const autoHandler = this.autoHandlers.get(browserId);
245
+
246
+ return {
247
+ dialog: dialogState || null,
248
+ autoHandling: autoHandler?.enabled || false,
249
+ autoConfig: autoHandler || null
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Wait for a dialog to appear
255
+ */
256
+ async waitForDialog(browserId, timeout = 10000) {
257
+ return new Promise((resolve, reject) => {
258
+ const timeoutId = setTimeout(() => {
259
+ reject(new Error(`Dialog wait timeout after ${timeout}ms`));
260
+ }, timeout);
261
+
262
+ const checkDialog = () => {
263
+ const dialogState = this.dialogStates.get(browserId);
264
+ if (dialogState && !dialogState.handled) {
265
+ clearTimeout(timeoutId);
266
+ resolve(dialogState);
267
+ } else {
268
+ setTimeout(checkDialog, 100);
269
+ }
270
+ };
271
+
272
+ checkDialog();
273
+ });
274
+ }
275
+
276
+ /**
277
+ * Clear dialog state
278
+ */
279
+ clearDialogState(browserId) {
280
+ this.dialogStates.delete(browserId);
281
+ }
282
+
283
+ /**
284
+ * Get dialog history
285
+ */
286
+ getDialogHistory(browserId) {
287
+ // In a real implementation, you might store a history of dialogs
288
+ const currentDialog = this.dialogStates.get(browserId);
289
+ return currentDialog ? [currentDialog] : [];
290
+ }
291
+
292
+ /**
293
+ * Setup custom dialog responses for testing
294
+ */
295
+ setupTestDialogResponses(browserId, responses) {
296
+ // Store custom responses for different dialog types
297
+ this.autoHandlers.set(browserId, {
298
+ enabled: true,
299
+ customResponses: responses
300
+ });
301
+ }
302
+
303
+ /**
304
+ * Inject dialog trigger for testing
305
+ */
306
+ async triggerTestDialog(client, type = 'alert', message = 'Test dialog') {
307
+ const expressions = {
308
+ alert: `alert('${message}')`,
309
+ confirm: `confirm('${message}')`,
310
+ prompt: `prompt('${message}', 'default value')`
311
+ };
312
+
313
+ return await client.Runtime.evaluate({
314
+ expression: expressions[type] || expressions.alert
315
+ });
316
+ }
317
+ }
318
+
319
+ module.exports = BrowserDialogTool;
@@ -0,0 +1,337 @@
1
+ const ToolBase = require('../../base/ToolBase');
2
+ const browserService = require('../../../services/browserService');
3
+
4
+ /**
5
+ * Enhanced Evaluate Tool - Execute JavaScript in browser context
6
+ * Inspired by Playwright MCP evaluate capabilities
7
+ */
8
+ class BrowserEvaluateTool extends ToolBase {
9
+ static definition = {
10
+ name: "browser_evaluate",
11
+ description: "Execute JavaScript code in the browser context. Can execute on page or specific elements. Returns execution results and handles errors gracefully.",
12
+ input_schema: {
13
+ type: "object",
14
+ properties: {
15
+ browserId: {
16
+ type: "string",
17
+ description: "The ID of the browser instance"
18
+ },
19
+ expression: {
20
+ type: "string",
21
+ description: "JavaScript expression or function to execute"
22
+ },
23
+ target: {
24
+ type: "object",
25
+ properties: {
26
+ selector: {
27
+ type: "string",
28
+ description: "CSS selector of element to execute on (optional)"
29
+ }
30
+ },
31
+ description: "Target element for execution (if not provided, executes in page context)"
32
+ },
33
+ args: {
34
+ type: "array",
35
+ items: {},
36
+ description: "Arguments to pass to the function (for function expressions)"
37
+ },
38
+ returnByValue: {
39
+ type: "boolean",
40
+ default: true,
41
+ description: "Whether to return result by value or as object reference"
42
+ },
43
+ awaitPromise: {
44
+ type: "boolean",
45
+ default: true,
46
+ description: "Whether to await promises in the result"
47
+ },
48
+ timeout: {
49
+ type: "number",
50
+ default: 30000,
51
+ description: "Execution timeout in milliseconds"
52
+ }
53
+ },
54
+ required: ["browserId", "expression"]
55
+ },
56
+ output_schema: {
57
+ type: "object",
58
+ properties: {
59
+ success: { type: "boolean", description: "Whether the execution was successful" },
60
+ result: { description: "The result of the JavaScript execution" },
61
+ type: { type: "string", description: "Type of the returned result" },
62
+ error: { type: "string", description: "Error message if execution failed" },
63
+ executionTime: { type: "number", description: "Execution time in milliseconds" },
64
+ target: { type: "string", description: "CSS selector if targeting an element" },
65
+ browserId: { type: "string", description: "Browser instance ID" }
66
+ },
67
+ required: ["success", "browserId"]
68
+ }
69
+ };
70
+
71
+ async execute(parameters) {
72
+ const {
73
+ browserId,
74
+ expression,
75
+ target,
76
+ args = [],
77
+ returnByValue = true,
78
+ awaitPromise = true,
79
+ timeout = 30000
80
+ } = parameters;
81
+
82
+ const browser = browserService.getBrowserInstance(browserId);
83
+ if (!browser) {
84
+ throw new Error(`Browser instance '${browserId}' not found`);
85
+ }
86
+
87
+ const client = browser.client;
88
+ const startTime = Date.now();
89
+
90
+ let result = {
91
+ success: false,
92
+ browserId: browserId,
93
+ executionTime: 0
94
+ };
95
+
96
+ try {
97
+ if (target?.selector) {
98
+ // Execute on specific element
99
+ result = await this.executeOnElement(client, expression, target.selector, args, returnByValue, awaitPromise, timeout);
100
+ result.target = target.selector;
101
+ } else {
102
+ // Execute in page context
103
+ result = await this.executeInPage(client, expression, args, returnByValue, awaitPromise, timeout);
104
+ }
105
+
106
+ result.success = true;
107
+
108
+ } catch (error) {
109
+ result.success = false;
110
+ result.error = error.message;
111
+
112
+ // Try to extract more meaningful error information
113
+ if (error.exceptionDetails) {
114
+ result.error = error.exceptionDetails.text || error.exceptionDetails.exception?.description || result.error;
115
+ result.lineNumber = error.exceptionDetails.lineNumber;
116
+ result.columnNumber = error.exceptionDetails.columnNumber;
117
+ }
118
+ }
119
+
120
+ result.executionTime = Date.now() - startTime;
121
+ result.browserId = browserId;
122
+
123
+ return result;
124
+ }
125
+
126
+ /**
127
+ * Execute JavaScript in page context
128
+ */
129
+ async executeInPage(client, expression, args, returnByValue, awaitPromise, timeout) {
130
+ // Wrap expression in timeout handling
131
+ const wrappedExpression = this.wrapWithTimeout(expression, timeout);
132
+
133
+ const evalResult = await client.Runtime.evaluate({
134
+ expression: wrappedExpression,
135
+ returnByValue: returnByValue,
136
+ awaitPromise: awaitPromise,
137
+ timeout: timeout
138
+ });
139
+
140
+ if (evalResult.exceptionDetails) {
141
+ throw evalResult.exceptionDetails;
142
+ }
143
+
144
+ return this.formatResult(evalResult.result);
145
+ }
146
+
147
+ /**
148
+ * Execute JavaScript on a specific element
149
+ */
150
+ async executeOnElement(client, expression, selector, args, returnByValue, awaitPromise, timeout) {
151
+ // First, get the element
152
+ const elementExpression = `
153
+ (() => {
154
+ const element = document.querySelector('${selector}');
155
+ if (!element) {
156
+ throw new Error('Element not found with selector: ${selector}');
157
+ }
158
+ return element;
159
+ })()
160
+ `;
161
+
162
+ const elementResult = await client.Runtime.evaluate({
163
+ expression: elementExpression,
164
+ returnByValue: false
165
+ });
166
+
167
+ if (elementResult.exceptionDetails) {
168
+ throw new Error(`Failed to find element: ${elementResult.exceptionDetails.text}`);
169
+ }
170
+
171
+ // Now execute the expression on the element
172
+ const executeExpression = this.createElementExpression(expression, args);
173
+ const wrappedExpression = this.wrapWithTimeout(executeExpression, timeout);
174
+
175
+ const evalResult = await client.Runtime.callFunctionOn({
176
+ functionDeclaration: wrappedExpression,
177
+ objectId: elementResult.result.objectId,
178
+ arguments: args.map(arg => ({ value: arg })),
179
+ returnByValue: returnByValue,
180
+ awaitPromise: awaitPromise
181
+ });
182
+
183
+ if (evalResult.exceptionDetails) {
184
+ throw evalResult.exceptionDetails;
185
+ }
186
+
187
+ return this.formatResult(evalResult.result);
188
+ }
189
+
190
+ /**
191
+ * Create expression for element execution
192
+ */
193
+ createElementExpression(expression, args) {
194
+ // If expression is a function, use it directly
195
+ if (expression.trim().startsWith('function') || expression.includes('=>')) {
196
+ return `(${expression}).apply(this, arguments)`;
197
+ }
198
+
199
+ // If it's a simple expression, wrap it in a function
200
+ return `function() { return ${expression}; }`;
201
+ }
202
+
203
+ /**
204
+ * Wrap expression with timeout handling
205
+ */
206
+ wrapWithTimeout(expression, timeout) {
207
+ return `
208
+ (async () => {
209
+ const timeoutPromise = new Promise((_, reject) =>
210
+ setTimeout(() => reject(new Error('Execution timeout')), ${timeout})
211
+ );
212
+
213
+ const executionPromise = (async () => {
214
+ ${expression.includes('return') ? expression : `return (${expression})`}
215
+ })();
216
+
217
+ return Promise.race([executionPromise, timeoutPromise]);
218
+ })()
219
+ `;
220
+ }
221
+
222
+ /**
223
+ * Format execution result
224
+ */
225
+ formatResult(result) {
226
+ if (!result) {
227
+ return { result: undefined, type: 'undefined' };
228
+ }
229
+
230
+ let formattedResult = {
231
+ type: result.type
232
+ };
233
+
234
+ switch (result.type) {
235
+ case 'object':
236
+ if (result.subtype === 'null') {
237
+ formattedResult.result = null;
238
+ } else if (result.subtype === 'array') {
239
+ formattedResult.result = result.value || result.description;
240
+ formattedResult.subtype = 'array';
241
+ } else if (result.subtype === 'error') {
242
+ formattedResult.result = result.description;
243
+ formattedResult.subtype = 'error';
244
+ } else {
245
+ formattedResult.result = result.value || result.description;
246
+ }
247
+ break;
248
+
249
+ case 'function':
250
+ formattedResult.result = result.description;
251
+ break;
252
+
253
+ case 'undefined':
254
+ formattedResult.result = undefined;
255
+ break;
256
+
257
+ case 'string':
258
+ case 'number':
259
+ case 'boolean':
260
+ formattedResult.result = result.value;
261
+ break;
262
+
263
+ default:
264
+ formattedResult.result = result.value !== undefined ? result.value : result.description;
265
+ }
266
+
267
+ return formattedResult;
268
+ }
269
+
270
+ /**
271
+ * Common JavaScript snippets for evaluation
272
+ */
273
+ static getCommonSnippets() {
274
+ return {
275
+ getPageInfo: `
276
+ ({
277
+ title: document.title,
278
+ url: window.location.href,
279
+ readyState: document.readyState,
280
+ elementCount: document.querySelectorAll('*').length,
281
+ viewport: {
282
+ width: window.innerWidth,
283
+ height: window.innerHeight
284
+ }
285
+ })
286
+ `,
287
+
288
+ getElementInfo: `
289
+ function(element) {
290
+ const rect = element.getBoundingClientRect();
291
+ return {
292
+ tagName: element.tagName,
293
+ id: element.id,
294
+ className: element.className,
295
+ textContent: element.textContent?.substring(0, 100),
296
+ bounds: {
297
+ x: rect.x,
298
+ y: rect.y,
299
+ width: rect.width,
300
+ height: rect.height
301
+ },
302
+ visible: rect.width > 0 && rect.height > 0,
303
+ attributes: Array.from(element.attributes).reduce((acc, attr) => {
304
+ acc[attr.name] = attr.value;
305
+ return acc;
306
+ }, {})
307
+ };
308
+ }
309
+ `,
310
+
311
+ scrollToElement: `
312
+ function(element) {
313
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
314
+ return { scrolled: true, element: element.tagName };
315
+ }
316
+ `,
317
+
318
+ getFormData: `
319
+ () => {
320
+ const forms = Array.from(document.forms);
321
+ return forms.map(form => ({
322
+ action: form.action,
323
+ method: form.method,
324
+ fields: Array.from(form.elements).map(el => ({
325
+ name: el.name,
326
+ type: el.type,
327
+ value: el.value,
328
+ required: el.required
329
+ }))
330
+ }));
331
+ }
332
+ `
333
+ };
334
+ }
335
+ }
336
+
337
+ module.exports = BrowserEvaluateTool;