@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,174 @@
1
+ const ToolBase = require('../base/ToolBase');
2
+ const browserService = require('../../services/browserService');
3
+
4
+ /**
5
+ * Browser Type Tool
6
+ * Types text into a specific input field in the browser with Playwright-style locators
7
+ */
8
+ class BrowserTypeTool extends ToolBase {
9
+ static definition = {
10
+ name: "browser_type",
11
+ description: "Types text into a specific input field in the browser.",
12
+ input_schema: {
13
+ type: "object",
14
+ properties: {
15
+ browserId: {
16
+ type: "string",
17
+ description: "The ID of the browser instance."
18
+ },
19
+ locatorType: {
20
+ type: "string",
21
+ enum: ["css", "xpath", "text", "role", "label", "placeholder", "testId", "altText"],
22
+ default: "css",
23
+ description: "The type of locator to use"
24
+ },
25
+ locatorValue: {
26
+ type: "string",
27
+ description: "The locator value (CSS selector, XPath, text content, etc.)"
28
+ },
29
+ text: {
30
+ type: "string",
31
+ description: "The text to type into the input field."
32
+ },
33
+ // Keep backward compatibility
34
+ selector: {
35
+ type: "string",
36
+ description: "The CSS selector of the input field (deprecated, use locatorType and locatorValue instead)."
37
+ },
38
+ options: {
39
+ type: "object",
40
+ properties: {
41
+ timeout: {
42
+ type: "number",
43
+ default: 30000,
44
+ description: "Timeout in milliseconds"
45
+ },
46
+ delay: {
47
+ type: "number",
48
+ default: 0,
49
+ description: "Delay between keystrokes in milliseconds"
50
+ },
51
+ clear: {
52
+ type: "boolean",
53
+ default: true,
54
+ description: "Clear the field before typing"
55
+ },
56
+ noWaitAfter: {
57
+ type: "boolean",
58
+ default: false,
59
+ description: "Do not wait for navigation after typing"
60
+ }
61
+ },
62
+ description: "Additional typing options"
63
+ }
64
+ },
65
+ anyOf: [
66
+ { required: ["browserId", "locatorType", "locatorValue", "text"] },
67
+ { required: ["browserId", "selector", "text"] }
68
+ ]
69
+ },
70
+ output_schema: {
71
+ type: "object",
72
+ properties: {
73
+ success: { type: "boolean", description: "Indicates if the typing was successful." },
74
+ locatorType: { type: "string", description: "The locator type that was used." },
75
+ locatorValue: { type: "string", description: "The locator value that was used." },
76
+ text: { type: "string", description: "The text that was typed." },
77
+ browserId: { type: "string", description: "The browser instance ID that was used." },
78
+ elementFound: { type: "boolean", description: "Whether the element was found." },
79
+ message: { type: "string", description: "Success or error message." }
80
+ },
81
+ required: ["success", "browserId"]
82
+ }
83
+ };
84
+
85
+ async execute(parameters) {
86
+ const { browserId, locatorType, locatorValue, selector, text, options = {} } = parameters;
87
+
88
+ // Handle backward compatibility
89
+ let finalLocatorType, finalLocatorValue;
90
+ if (selector) {
91
+ finalLocatorType = "css";
92
+ finalLocatorValue = selector;
93
+ } else {
94
+ finalLocatorType = locatorType || "css";
95
+ finalLocatorValue = locatorValue;
96
+ }
97
+
98
+ console.error(`[BrowserTypeTool] Typing in browser ${browserId} with locator: ${finalLocatorType}="${finalLocatorValue}"`);
99
+
100
+ try {
101
+ // Convert Playwright-style locator to appropriate format for browser service
102
+ const elementSelector = this.convertLocatorToSelector(finalLocatorType, finalLocatorValue);
103
+
104
+ await browserService.typeIntoElement(browserId, elementSelector, text, options);
105
+
106
+ console.error(`[BrowserTypeTool] Successfully typed in browser: ${browserId}`);
107
+
108
+ return {
109
+ success: true,
110
+ browserId: browserId,
111
+ locatorType: finalLocatorType,
112
+ locatorValue: finalLocatorValue,
113
+ text: text,
114
+ elementFound: true,
115
+ message: `Successfully typed "${text}" into element with ${finalLocatorType} locator: ${finalLocatorValue}`
116
+ };
117
+ } catch (error) {
118
+ console.error(`[BrowserTypeTool] Failed to type in element:`, error.message);
119
+
120
+ return {
121
+ success: false,
122
+ browserId: browserId,
123
+ locatorType: finalLocatorType,
124
+ locatorValue: finalLocatorValue,
125
+ text: text,
126
+ elementFound: false,
127
+ message: `Failed to type into element: ${error.message}`
128
+ };
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Convert Playwright-style locator to CSS selector or XPath
134
+ */
135
+ convertLocatorToSelector(locatorType, locatorValue) {
136
+ switch (locatorType) {
137
+ case "css":
138
+ return locatorValue;
139
+
140
+ case "xpath":
141
+ return locatorValue;
142
+
143
+ case "text":
144
+ // Convert text locator to XPath for input elements
145
+ 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]`;
146
+
147
+ case "role":
148
+ // Convert role locator to CSS attribute selector
149
+ return `[role="${locatorValue}"]`;
150
+
151
+ case "label":
152
+ // Convert label locator to XPath for label association
153
+ 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]`;
154
+
155
+ case "placeholder":
156
+ // Convert placeholder locator to CSS attribute selector
157
+ return `[placeholder="${locatorValue}"]`;
158
+
159
+ case "testId":
160
+ // Convert test ID to CSS attribute selector (assuming data-testid)
161
+ return `[data-testid="${locatorValue}"]`;
162
+
163
+ case "altText":
164
+ // Convert alt text to CSS attribute selector (less common for input fields)
165
+ return `[alt="${locatorValue}"]`;
166
+
167
+ default:
168
+ // Default to CSS selector
169
+ return locatorValue;
170
+ }
171
+ }
172
+ }
173
+
174
+ module.exports = BrowserTypeTool;
@@ -0,0 +1,95 @@
1
+ const ToolRegistry = require('./base/ToolRegistry');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Initialize and configure the tool registry
6
+ * This file serves as the main entry point for the tools system
7
+ */
8
+
9
+ // Create the global tool registry instance
10
+ const toolRegistry = new ToolRegistry();
11
+
12
+ /**
13
+ * Initialize the tool registry by discovering and loading all tools
14
+ * @param {boolean} debugMode - Whether to enable debug logging
15
+ * @returns {Promise<ToolRegistry>} - The initialized tool registry
16
+ */
17
+ async function initializeTools(debugMode = false) {
18
+ if (debugMode) {
19
+ console.error('[Tools] Initializing tool system...');
20
+ }
21
+
22
+ try {
23
+ // Get the tools directory path
24
+ const toolsDir = path.join(__dirname);
25
+
26
+ // Discover and load all tools
27
+ await toolRegistry.discoverTools(toolsDir, debugMode);
28
+
29
+ // Log registry statistics
30
+ const stats = toolRegistry.getStats();
31
+ console.error(`[Tools] Tool system initialized successfully:`);
32
+ console.error(`[Tools] - Total tools: ${stats.total_tools}`);
33
+ if (debugMode) {
34
+ console.error(`[Tools] - Available tools: ${stats.tool_names.join(', ')}`);
35
+ }
36
+
37
+ return toolRegistry;
38
+
39
+ } catch (error) {
40
+ console.error('[Tools] Failed to initialize tool system:', error.message);
41
+ throw error;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Get the tool registry instance
47
+ * @returns {ToolRegistry} - The tool registry instance
48
+ */
49
+ function getToolRegistry() {
50
+ return toolRegistry;
51
+ }
52
+
53
+ /**
54
+ * Get all tool definitions for MCP tools/list response
55
+ * @returns {Array} - Array of tool definitions
56
+ */
57
+ function getToolDefinitions() {
58
+ return toolRegistry.getDefinitions();
59
+ }
60
+
61
+ /**
62
+ * Execute a tool by name
63
+ * @param {string} toolName - The name of the tool to execute
64
+ * @param {object} parameters - The parameters to pass to the tool
65
+ * @returns {Promise<object>} - The tool execution result
66
+ */
67
+ async function executeTool(toolName, parameters) {
68
+ return await toolRegistry.executeTool(toolName, parameters);
69
+ }
70
+
71
+ /**
72
+ * Check if a tool is available
73
+ * @param {string} toolName - The name of the tool to check
74
+ * @returns {boolean} - True if the tool is available
75
+ */
76
+ function isToolAvailable(toolName) {
77
+ return toolRegistry.hasTool(toolName);
78
+ }
79
+
80
+ /**
81
+ * Get list of available tool names
82
+ * @returns {Array<string>} - Array of available tool names
83
+ */
84
+ function getAvailableTools() {
85
+ return toolRegistry.getToolNames();
86
+ }
87
+
88
+ module.exports = {
89
+ initializeTools,
90
+ getToolRegistry,
91
+ getToolDefinitions,
92
+ executeTool,
93
+ isToolAvailable,
94
+ getAvailableTools
95
+ };
@@ -0,0 +1,83 @@
1
+ const path = require('path'); // Ensure path is imported if needed for other helpers
2
+
3
+ /**
4
+ * Finds a DOM node by selector (CSS or XPath) and returns its NodeId.
5
+ * @param {object} cdpClient - The Chrome DevTools Protocol client.
6
+ * @param {string} selectorType - 'css' or 'xpath'.
7
+ * @param {string} selectorValue - The CSS selector or XPath string.
8
+ * @returns {Promise<number|null>} - The NodeId of the found element, or null if not found.
9
+ */
10
+ async function findNodeBySelector(cdpClient, selectorType, selectorValue) {
11
+ const { DOM, Runtime } = cdpClient;
12
+ let nodeId = null;
13
+
14
+ try {
15
+ const rootNode = await DOM.getDocument({ depth: 1 }); // Get root document node
16
+
17
+ if (selectorType === 'css') {
18
+ const result = await DOM.querySelector({
19
+ nodeId: rootNode.root.nodeId,
20
+ selector: selectorValue
21
+ });
22
+ nodeId = result.nodeId;
23
+ } else if (selectorType === 'xpath') {
24
+ // Use Runtime.evaluate to execute XPath in the browser context
25
+ const expression = `document.evaluate("${selectorValue}", document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue`;
26
+ const { result: jsResult } = await Runtime.evaluate({ expression: expression, returnByValue: false });
27
+
28
+ if (jsResult.objectId) {
29
+ // Convert the JS objectId (remote object) back to a DOM NodeId
30
+ const { nodeId: resolvedNodeId } = await DOM.requestNode({ objectId: jsResult.objectId });
31
+ nodeId = resolvedNodeId;
32
+ }
33
+ } else {
34
+ console.warn(`[findNodeBySelector] Unsupported selector type: ${selectorType}`);
35
+ }
36
+
37
+ return nodeId;
38
+
39
+ } catch (error) {
40
+ console.error(`[findNodeBySelector] Error finding node by selector (${selectorType}: ${selectorValue}):`, error.message);
41
+ return null;
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Scrolls an element into view and calculates its center coordinates for clicking.
47
+ * @param {object} cdpClient - The Chrome DevTools Protocol client.
48
+ * @param {number} nodeId - The NodeId of the element to interact with.
49
+ * @returns {Promise<{x: number, y: number}|null>} - Object with x, y coordinates, or null if error.
50
+ */
51
+ async function getElementClickCoordinates(cdpClient, nodeId) {
52
+ const { DOM } = cdpClient; // Page is not directly used here
53
+ try {
54
+ // Scroll the element into view first
55
+ await DOM.scrollIntoViewIfNeeded({ nodeId: nodeId });
56
+ // Give browser a moment to render after scroll
57
+ await new Promise(resolve => setTimeout(resolve, 100)); // Small delay for rendering
58
+
59
+ // Get the box model to find coordinates
60
+ const { model } = await DOM.getBoxModel({ nodeId: nodeId });
61
+
62
+ if (!model) {
63
+ console.error(`[getElementClickCoordinates] No box model found for nodeId: ${nodeId}`);
64
+ return null;
65
+ }
66
+
67
+ // Calculate center coordinates based on the content box
68
+ // model.content is an array of 8 numbers: [x1, y1, x2, y2, x3, y3, x4, y4]
69
+ // representing the four corners of the content box (top-left, top-right, bottom-right, bottom-left)
70
+ const centerX = (model.content[0] + model.content[2]) / 2;
71
+ const centerY = (model.content[1] + model.content[5]) / 2;
72
+
73
+ return { x: centerX, y: centerY };
74
+ } catch (error) {
75
+ console.error(`[getElementClickCoordinates] Error getting element coordinates for nodeId ${nodeId}:`, error.message);
76
+ return null;
77
+ }
78
+ }
79
+
80
+ module.exports = {
81
+ findNodeBySelector,
82
+ getElementClickCoordinates
83
+ };