@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.
- package/LICENSE +15 -0
- package/README.md +423 -0
- package/browserControl.js +113 -0
- package/cli.js +187 -0
- package/docs/api/tool-reference.md +317 -0
- package/docs/api_tools_usage.md +477 -0
- package/docs/development/adding-tools.md +274 -0
- package/docs/development/configuration.md +332 -0
- package/docs/examples/authentication.md +124 -0
- package/docs/examples/basic-automation.md +105 -0
- package/docs/getting-started.md +214 -0
- package/docs/index.md +61 -0
- package/mcpServer.js +280 -0
- package/package.json +83 -0
- package/run-server.js +140 -0
- package/src/config/environments/api-only.js +53 -0
- package/src/config/environments/development.js +54 -0
- package/src/config/environments/production.js +69 -0
- package/src/config/index.js +341 -0
- package/src/config/server.js +41 -0
- package/src/config/tools/api.js +67 -0
- package/src/config/tools/browser.js +90 -0
- package/src/config/tools/default.js +32 -0
- package/src/services/browserService.js +325 -0
- package/src/tools/api/api-request.js +641 -0
- package/src/tools/api/api-session-report.js +1262 -0
- package/src/tools/api/api-session-status.js +395 -0
- package/src/tools/base/ToolBase.js +230 -0
- package/src/tools/base/ToolRegistry.js +269 -0
- package/src/tools/browser/advanced/browser-console.js +384 -0
- package/src/tools/browser/advanced/browser-dialog.js +319 -0
- package/src/tools/browser/advanced/browser-evaluate.js +337 -0
- package/src/tools/browser/advanced/browser-file.js +480 -0
- package/src/tools/browser/advanced/browser-keyboard.js +343 -0
- package/src/tools/browser/advanced/browser-mouse.js +332 -0
- package/src/tools/browser/advanced/browser-network.js +421 -0
- package/src/tools/browser/advanced/browser-pdf.js +407 -0
- package/src/tools/browser/advanced/browser-tabs.js +497 -0
- package/src/tools/browser/advanced/browser-wait.js +378 -0
- package/src/tools/browser/click.js +168 -0
- package/src/tools/browser/close.js +60 -0
- package/src/tools/browser/dom.js +70 -0
- package/src/tools/browser/launch.js +67 -0
- package/src/tools/browser/navigate.js +270 -0
- package/src/tools/browser/screenshot.js +351 -0
- package/src/tools/browser/type.js +174 -0
- package/src/tools/index.js +95 -0
- 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
|
+
};
|