@democratize-quality/mcp-server 1.1.9 → 1.2.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/dist/server.d.ts +41 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +225 -0
- package/dist/server.js.map +1 -0
- package/package.json +23 -24
- package/browserControl.js +0 -113
- package/cli.js +0 -228
- package/mcpServer.js +0 -335
- package/run-server.js +0 -140
- package/src/chatmodes//360/237/214/220 api-generator.chatmode.md" +0 -409
- package/src/chatmodes//360/237/214/220 api-healer.chatmode.md" +0 -494
- package/src/chatmodes//360/237/214/220 api-planner.chatmode.md" +0 -954
- package/src/config/environments/api-only.js +0 -53
- package/src/config/environments/development.js +0 -54
- package/src/config/environments/production.js +0 -69
- package/src/config/index.js +0 -341
- package/src/config/server.js +0 -41
- package/src/config/tools/api.js +0 -67
- package/src/config/tools/browser.js +0 -90
- package/src/config/tools/default.js +0 -32
- package/src/docs/Agent_README.md +0 -310
- package/src/docs/QUICK_REFERENCE.md +0 -111
- package/src/services/browserService.js +0 -325
- package/src/skills/api-planning/SKILL.md +0 -224
- package/src/skills/test-execution/SKILL.md +0 -777
- package/src/skills/test-generation/SKILL.md +0 -309
- package/src/skills/test-healing/SKILL.md +0 -405
- package/src/tools/api/api-generator.js +0 -1865
- package/src/tools/api/api-healer.js +0 -617
- package/src/tools/api/api-planner.js +0 -2598
- package/src/tools/api/api-project-setup.js +0 -313
- package/src/tools/api/api-request.js +0 -641
- package/src/tools/api/api-session-report.js +0 -1278
- package/src/tools/api/api-session-status.js +0 -395
- package/src/tools/api/prompts/README.md +0 -293
- package/src/tools/api/prompts/generation-prompts.js +0 -703
- package/src/tools/api/prompts/healing-prompts.js +0 -195
- package/src/tools/api/prompts/index.js +0 -25
- package/src/tools/api/prompts/orchestrator.js +0 -334
- package/src/tools/api/prompts/validation-rules.js +0 -339
- package/src/tools/base/ToolBase.js +0 -230
- package/src/tools/base/ToolRegistry.js +0 -269
- package/src/tools/browser/advanced/browser-console.js +0 -384
- package/src/tools/browser/advanced/browser-dialog.js +0 -319
- package/src/tools/browser/advanced/browser-evaluate.js +0 -337
- package/src/tools/browser/advanced/browser-file.js +0 -480
- package/src/tools/browser/advanced/browser-keyboard.js +0 -343
- package/src/tools/browser/advanced/browser-mouse.js +0 -332
- package/src/tools/browser/advanced/browser-network.js +0 -421
- package/src/tools/browser/advanced/browser-pdf.js +0 -407
- package/src/tools/browser/advanced/browser-tabs.js +0 -497
- package/src/tools/browser/advanced/browser-wait.js +0 -378
- package/src/tools/browser/click.js +0 -168
- package/src/tools/browser/close.js +0 -60
- package/src/tools/browser/dom.js +0 -70
- package/src/tools/browser/launch.js +0 -67
- package/src/tools/browser/navigate.js +0 -270
- package/src/tools/browser/screenshot.js +0 -351
- package/src/tools/browser/type.js +0 -174
- package/src/tools/index.js +0 -95
- package/src/utils/agentInstaller.js +0 -418
- package/src/utils/browserHelpers.js +0 -83
|
@@ -1,339 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Validation Rules for Generated Test Code
|
|
3
|
-
*
|
|
4
|
-
* These rules validate generated test code before writing to files.
|
|
5
|
-
* Catches common issues and triggers healing if needed.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
module.exports = {
|
|
9
|
-
/**
|
|
10
|
-
* Validate Playwright test code
|
|
11
|
-
*/
|
|
12
|
-
playwright: {
|
|
13
|
-
/**
|
|
14
|
-
* Check if code has required imports
|
|
15
|
-
*/
|
|
16
|
-
hasRequiredImports: (code, language) => {
|
|
17
|
-
if (language === 'typescript') {
|
|
18
|
-
return code.includes('import { test, expect }') || code.includes("import { test, expect }");
|
|
19
|
-
}
|
|
20
|
-
return code.includes('require(') || !code.includes('import');
|
|
21
|
-
},
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Check if code uses proper async/await
|
|
25
|
-
*/
|
|
26
|
-
hasProperAsync: (code) => {
|
|
27
|
-
if (code.includes('request.')) {
|
|
28
|
-
return code.includes('await request.');
|
|
29
|
-
}
|
|
30
|
-
return true;
|
|
31
|
-
},
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* Check if code has proper URL construction
|
|
35
|
-
*/
|
|
36
|
-
hasProperUrlConstruction: (code) => {
|
|
37
|
-
// Should use template literals with ${baseUrl}, not string concatenation
|
|
38
|
-
const hasTemplateUrl = code.includes('`${baseUrl}') || code.includes("'http");
|
|
39
|
-
const hasWrongUrl = code.includes("'${baseUrl}"); // This is the escaping bug!
|
|
40
|
-
|
|
41
|
-
return hasTemplateUrl && !hasWrongUrl;
|
|
42
|
-
},
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Check if code has assertions
|
|
46
|
-
*/
|
|
47
|
-
hasAssertions: (code) => {
|
|
48
|
-
return code.includes('expect(') && code.includes('.toBe(');
|
|
49
|
-
},
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Check for common syntax errors
|
|
53
|
-
*/
|
|
54
|
-
noSyntaxErrors: (code) => {
|
|
55
|
-
// Basic checks - full validation would require parsing
|
|
56
|
-
const balancedBraces = (code.match(/\{/g) || []).length === (code.match(/\}/g) || []).length;
|
|
57
|
-
const balancedParens = (code.match(/\(/g) || []).length === (code.match(/\)/g) || []).length;
|
|
58
|
-
const balancedBrackets = (code.match(/\[/g) || []).length === (code.match(/\]/g) || []).length;
|
|
59
|
-
|
|
60
|
-
return balancedBraces && balancedParens && balancedBrackets;
|
|
61
|
-
},
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Check if GraphQL request is properly structured
|
|
65
|
-
*/
|
|
66
|
-
hasGraphQLRequestStructure: (code) => {
|
|
67
|
-
// If code contains GraphQL query, check structure
|
|
68
|
-
if (code.includes('query:') || code.includes('mutation:') || code.includes('subscription:')) {
|
|
69
|
-
const hasQueryField = code.includes('query:') || code.includes('mutation:');
|
|
70
|
-
const hasDataObject = code.includes('data: {') && hasQueryField;
|
|
71
|
-
return hasQueryField && hasDataObject;
|
|
72
|
-
}
|
|
73
|
-
return true; // Not GraphQL, skip
|
|
74
|
-
},
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Check if GraphQL response validation is present
|
|
78
|
-
*/
|
|
79
|
-
hasGraphQLResponseValidation: (code) => {
|
|
80
|
-
// If it's a GraphQL test, must validate data or errors
|
|
81
|
-
if (code.includes('query:') || code.includes('mutation:')) {
|
|
82
|
-
const hasDataCheck = code.includes('.toHaveProperty(\'data\')');
|
|
83
|
-
const hasErrorsCheck = code.includes('.toHaveProperty(\'errors\')');
|
|
84
|
-
return hasDataCheck || hasErrorsCheck;
|
|
85
|
-
}
|
|
86
|
-
return true; // Not GraphQL
|
|
87
|
-
},
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Run all validations
|
|
91
|
-
*/
|
|
92
|
-
validateAll: (code, language, isGraphQL) => {
|
|
93
|
-
const issues = [];
|
|
94
|
-
|
|
95
|
-
// Note: We're validating generated TEST BODY code, not full files
|
|
96
|
-
// So we don't check for imports in the body
|
|
97
|
-
|
|
98
|
-
if (!module.exports.playwright.hasProperAsync(code)) {
|
|
99
|
-
issues.push({
|
|
100
|
-
severity: 'error',
|
|
101
|
-
message: 'Missing await keyword for async API calls',
|
|
102
|
-
fix: 'Add await before request.* calls'
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (!module.exports.playwright.hasProperUrlConstruction(code)) {
|
|
107
|
-
issues.push({
|
|
108
|
-
severity: 'critical',
|
|
109
|
-
message: 'Incorrect URL construction - template literal escaping issue',
|
|
110
|
-
fix: 'Use backticks for template literals: `${baseUrl}/endpoint` not \'${baseUrl}/endpoint\''
|
|
111
|
-
});
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (!module.exports.playwright.hasAssertions(code)) {
|
|
115
|
-
issues.push({
|
|
116
|
-
severity: 'warning',
|
|
117
|
-
message: 'No assertions found in test',
|
|
118
|
-
fix: 'Add expect() assertions to validate response'
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
if (!module.exports.playwright.noSyntaxErrors(code)) {
|
|
123
|
-
issues.push({
|
|
124
|
-
severity: 'error',
|
|
125
|
-
message: 'Possible syntax error - unbalanced brackets/braces/parens',
|
|
126
|
-
fix: 'Check code for balanced brackets, braces, and parentheses'
|
|
127
|
-
});
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// NEW: GraphQL-specific validations
|
|
131
|
-
if (isGraphQL) {
|
|
132
|
-
if (!module.exports.playwright.hasGraphQLRequestStructure(code)) {
|
|
133
|
-
issues.push({
|
|
134
|
-
severity: 'error',
|
|
135
|
-
message: 'GraphQL request structure incorrect',
|
|
136
|
-
fix: 'Use: data: { query: "...", variables: {...} }'
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!module.exports.playwright.hasGraphQLResponseValidation(code)) {
|
|
141
|
-
issues.push({
|
|
142
|
-
severity: 'warning',
|
|
143
|
-
message: 'Missing GraphQL response validation',
|
|
144
|
-
fix: 'Add: expect(responseData).toHaveProperty(\'data\') or .toHaveProperty(\'errors\')'
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
valid: issues.filter(i => i.severity === 'error' || i.severity === 'critical').length === 0,
|
|
151
|
-
issues: issues
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
},
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Validate Jest test code
|
|
158
|
-
*/
|
|
159
|
-
jest: {
|
|
160
|
-
/**
|
|
161
|
-
* Check if code uses API utils correctly
|
|
162
|
-
*/
|
|
163
|
-
hasProperApiCall: (code) => {
|
|
164
|
-
return code.includes('apiUtils.makeRequest') && code.includes('await');
|
|
165
|
-
},
|
|
166
|
-
|
|
167
|
-
/**
|
|
168
|
-
* Check if code has assertions
|
|
169
|
-
*/
|
|
170
|
-
hasAssertions: (code) => {
|
|
171
|
-
return code.includes('expect(');
|
|
172
|
-
},
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Check for common syntax errors
|
|
176
|
-
*/
|
|
177
|
-
noSyntaxErrors: (code) => {
|
|
178
|
-
const balancedBraces = (code.match(/\{/g) || []).length === (code.match(/\}/g) || []).length;
|
|
179
|
-
const balancedParens = (code.match(/\(/g) || []).length === (code.match(/\)/g) || []).length;
|
|
180
|
-
const balancedBrackets = (code.match(/\[/g) || []).length === (code.match(/\]/g) || []).length;
|
|
181
|
-
|
|
182
|
-
return balancedBraces && balancedParens && balancedBrackets;
|
|
183
|
-
},
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Check if GraphQL request is properly structured (Jest/axios style)
|
|
187
|
-
*/
|
|
188
|
-
hasGraphQLRequestStructure: (code) => {
|
|
189
|
-
if (code.includes('query:') || code.includes('mutation:') || code.includes('subscription:')) {
|
|
190
|
-
const hasQueryField = code.includes('query:') || code.includes('mutation:');
|
|
191
|
-
const hasDataObject = code.includes('data: {') && hasQueryField;
|
|
192
|
-
return hasQueryField && hasDataObject;
|
|
193
|
-
}
|
|
194
|
-
return true;
|
|
195
|
-
},
|
|
196
|
-
|
|
197
|
-
/**
|
|
198
|
-
* Check if GraphQL response validation is present (Jest style)
|
|
199
|
-
*/
|
|
200
|
-
hasGraphQLResponseValidation: (code) => {
|
|
201
|
-
if (code.includes('query:') || code.includes('mutation:')) {
|
|
202
|
-
const hasDataCheck = code.includes('.toHaveProperty(\'data\')');
|
|
203
|
-
const hasErrorsCheck = code.includes('.toHaveProperty(\'errors\')');
|
|
204
|
-
return hasDataCheck || hasErrorsCheck;
|
|
205
|
-
}
|
|
206
|
-
return true;
|
|
207
|
-
},
|
|
208
|
-
|
|
209
|
-
/**
|
|
210
|
-
* Run all validations
|
|
211
|
-
*/
|
|
212
|
-
validateAll: (code, language, isGraphQL) => {
|
|
213
|
-
const issues = [];
|
|
214
|
-
|
|
215
|
-
if (!module.exports.jest.hasProperApiCall(code)) {
|
|
216
|
-
issues.push({
|
|
217
|
-
severity: 'error',
|
|
218
|
-
message: 'Missing or incorrect API call using apiUtils.makeRequest',
|
|
219
|
-
fix: 'Use await apiUtils.makeRequest({ method, url, ... })'
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (!module.exports.jest.hasAssertions(code)) {
|
|
224
|
-
issues.push({
|
|
225
|
-
severity: 'warning',
|
|
226
|
-
message: 'No assertions found in test',
|
|
227
|
-
fix: 'Add expect() assertions to validate response'
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
if (!module.exports.jest.noSyntaxErrors(code)) {
|
|
232
|
-
issues.push({
|
|
233
|
-
severity: 'error',
|
|
234
|
-
message: 'Possible syntax error - unbalanced brackets/braces/parens',
|
|
235
|
-
fix: 'Check code for balanced brackets, braces, and parentheses'
|
|
236
|
-
});
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// NEW: GraphQL-specific validations for Jest
|
|
240
|
-
if (isGraphQL) {
|
|
241
|
-
if (!module.exports.jest.hasGraphQLRequestStructure(code)) {
|
|
242
|
-
issues.push({
|
|
243
|
-
severity: 'error',
|
|
244
|
-
message: 'GraphQL request structure incorrect',
|
|
245
|
-
fix: 'Use: data: { query: "...", variables: {...} } in makeRequest()'
|
|
246
|
-
});
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (!module.exports.jest.hasGraphQLResponseValidation(code)) {
|
|
250
|
-
issues.push({
|
|
251
|
-
severity: 'warning',
|
|
252
|
-
message: 'Missing GraphQL response validation',
|
|
253
|
-
fix: 'Add: expect(response.data).toHaveProperty(\'data\') or .toHaveProperty(\'errors\')'
|
|
254
|
-
});
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return {
|
|
259
|
-
valid: issues.filter(i => i.severity === 'error' || i.severity === 'critical').length === 0,
|
|
260
|
-
issues: issues
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
},
|
|
264
|
-
|
|
265
|
-
/**
|
|
266
|
-
* Validate Postman collection structure
|
|
267
|
-
*/
|
|
268
|
-
postman: {
|
|
269
|
-
/**
|
|
270
|
-
* Validate collection has required fields
|
|
271
|
-
*/
|
|
272
|
-
hasRequiredFields: (collection) => {
|
|
273
|
-
return collection.info &&
|
|
274
|
-
collection.info.name &&
|
|
275
|
-
collection.info.schema &&
|
|
276
|
-
Array.isArray(collection.item);
|
|
277
|
-
},
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Validate request structure
|
|
281
|
-
*/
|
|
282
|
-
hasValidRequests: (collection) => {
|
|
283
|
-
if (!collection.item || collection.item.length === 0) return false;
|
|
284
|
-
|
|
285
|
-
// Check first item as sample
|
|
286
|
-
const firstItem = collection.item[0];
|
|
287
|
-
if (firstItem.item && Array.isArray(firstItem.item)) {
|
|
288
|
-
// It's a folder, check its items
|
|
289
|
-
return firstItem.item.every(req => req.name && req.request);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
return firstItem.name && firstItem.request;
|
|
293
|
-
},
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Run all validations
|
|
297
|
-
*/
|
|
298
|
-
validateAll: (collection) => {
|
|
299
|
-
const issues = [];
|
|
300
|
-
|
|
301
|
-
if (!module.exports.postman.hasRequiredFields(collection)) {
|
|
302
|
-
issues.push({
|
|
303
|
-
severity: 'error',
|
|
304
|
-
message: 'Missing required collection fields (info, name, schema, item)',
|
|
305
|
-
fix: 'Ensure collection has proper Postman v2.1 structure'
|
|
306
|
-
});
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
if (!module.exports.postman.hasValidRequests(collection)) {
|
|
310
|
-
issues.push({
|
|
311
|
-
severity: 'error',
|
|
312
|
-
message: 'Invalid request structure in collection',
|
|
313
|
-
fix: 'Ensure each request has name and request object'
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
return {
|
|
318
|
-
valid: issues.filter(i => i.severity === 'error' || i.severity === 'critical').length === 0,
|
|
319
|
-
issues: issues
|
|
320
|
-
};
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* Get validator for output format
|
|
326
|
-
*/
|
|
327
|
-
getValidator: (format) => {
|
|
328
|
-
switch(format) {
|
|
329
|
-
case 'playwright':
|
|
330
|
-
return module.exports.playwright;
|
|
331
|
-
case 'jest':
|
|
332
|
-
return module.exports.jest;
|
|
333
|
-
case 'postman':
|
|
334
|
-
return module.exports.postman;
|
|
335
|
-
default:
|
|
336
|
-
throw new Error(`Unknown format: ${format}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
};
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Base class for all MCP tools
|
|
3
|
-
* Provides common functionality and enforces a consistent interface
|
|
4
|
-
*/
|
|
5
|
-
class ToolBase {
|
|
6
|
-
constructor() {
|
|
7
|
-
if (this.constructor === ToolBase) {
|
|
8
|
-
throw new Error("ToolBase is an abstract class and cannot be instantiated directly");
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
// Validate that required static properties are defined
|
|
12
|
-
if (!this.constructor.definition) {
|
|
13
|
-
throw new Error(`Tool ${this.constructor.name} must define a static 'definition' property`);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
this.validateDefinition(this.constructor.definition);
|
|
17
|
-
|
|
18
|
-
// Initialize configuration
|
|
19
|
-
this.config = require('../../config');
|
|
20
|
-
this.toolConfig = this._getToolConfig();
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Get tool-specific configuration
|
|
25
|
-
* @returns {object} - Tool configuration
|
|
26
|
-
*/
|
|
27
|
-
_getToolConfig() {
|
|
28
|
-
const toolName = this.constructor.definition.name;
|
|
29
|
-
const category = this._getToolCategory(toolName);
|
|
30
|
-
|
|
31
|
-
// Get configuration in order of precedence:
|
|
32
|
-
// 1. Tool-specific config
|
|
33
|
-
// 2. Category config
|
|
34
|
-
// 3. Default config
|
|
35
|
-
return {
|
|
36
|
-
...this.config.getToolConfig('default', {}),
|
|
37
|
-
...this.config.getToolConfig(category, {}),
|
|
38
|
-
...this.config.getToolConfig(category, toolName)
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Get tool category from tool name
|
|
44
|
-
* @param {string} toolName - The tool name
|
|
45
|
-
* @returns {string} - The tool category
|
|
46
|
-
*/
|
|
47
|
-
_getToolCategory(toolName) {
|
|
48
|
-
if (toolName.startsWith('browser_')) return 'browser';
|
|
49
|
-
if (toolName.startsWith('file_')) return 'file';
|
|
50
|
-
if (toolName.startsWith('network_')) return 'network';
|
|
51
|
-
return 'other';
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get a configuration value for this tool
|
|
56
|
-
* @param {string} key - Configuration key
|
|
57
|
-
* @param {any} defaultValue - Default value
|
|
58
|
-
* @returns {any} - Configuration value
|
|
59
|
-
*/
|
|
60
|
-
getConfig(key, defaultValue = undefined) {
|
|
61
|
-
return this.toolConfig[key] !== undefined ? this.toolConfig[key] : defaultValue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Validates the tool definition schema
|
|
66
|
-
* @param {object} definition - The tool definition object
|
|
67
|
-
*/
|
|
68
|
-
validateDefinition(definition) {
|
|
69
|
-
const required = ['name', 'description', 'input_schema'];
|
|
70
|
-
for (const field of required) {
|
|
71
|
-
if (!definition[field]) {
|
|
72
|
-
throw new Error(`Tool definition missing required field: ${field}`);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (!definition.input_schema.type || definition.input_schema.type !== 'object') {
|
|
77
|
-
throw new Error("Tool input_schema must be of type 'object'");
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Validates input parameters against the tool's schema
|
|
83
|
-
* @param {object} parameters - The input parameters to validate
|
|
84
|
-
*/
|
|
85
|
-
validateParameters(parameters) {
|
|
86
|
-
const schema = this.constructor.definition.input_schema;
|
|
87
|
-
|
|
88
|
-
// Check required parameters
|
|
89
|
-
if (schema.required) {
|
|
90
|
-
for (const required of schema.required) {
|
|
91
|
-
if (parameters[required] === undefined || parameters[required] === null) {
|
|
92
|
-
throw new Error(`Missing required parameter: ${required}`);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// Basic type checking for defined properties
|
|
98
|
-
if (schema.properties) {
|
|
99
|
-
for (const [key, value] of Object.entries(parameters)) {
|
|
100
|
-
if (schema.properties[key]) {
|
|
101
|
-
const expectedType = schema.properties[key].type;
|
|
102
|
-
const actualType = typeof value;
|
|
103
|
-
|
|
104
|
-
if (expectedType === 'string' && actualType !== 'string') {
|
|
105
|
-
throw new Error(`Parameter '${key}' must be a string, got ${actualType}`);
|
|
106
|
-
}
|
|
107
|
-
if (expectedType === 'number' && actualType !== 'number') {
|
|
108
|
-
throw new Error(`Parameter '${key}' must be a number, got ${actualType}`);
|
|
109
|
-
}
|
|
110
|
-
if (expectedType === 'boolean' && actualType !== 'boolean') {
|
|
111
|
-
throw new Error(`Parameter '${key}' must be a boolean, got ${actualType}`);
|
|
112
|
-
}
|
|
113
|
-
if (expectedType === 'object' && (actualType !== 'object' || Array.isArray(value))) {
|
|
114
|
-
throw new Error(`Parameter '${key}' must be an object, got ${actualType}`);
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Executes the tool with the given parameters
|
|
123
|
-
* This method must be implemented by subclasses
|
|
124
|
-
* @param {object} parameters - The input parameters
|
|
125
|
-
* @returns {Promise<any>} - The tool execution result
|
|
126
|
-
*/
|
|
127
|
-
async execute(parameters) {
|
|
128
|
-
throw new Error("execute() method must be implemented by subclasses");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/**
|
|
132
|
-
* Wrapper method that handles validation, execution, and error handling
|
|
133
|
-
* @param {object} parameters - The input parameters
|
|
134
|
-
* @returns {Promise<object>} - Formatted MCP response
|
|
135
|
-
*/
|
|
136
|
-
async run(parameters = {}) {
|
|
137
|
-
const toolName = this.constructor.definition.name;
|
|
138
|
-
const enableDebug = this.config.isFeatureEnabled('enableDebugMode');
|
|
139
|
-
|
|
140
|
-
try {
|
|
141
|
-
if (enableDebug) {
|
|
142
|
-
console.error(`[Tool:${toolName}] Executing with parameters:`, JSON.stringify(parameters));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Validate input parameters if validation is enabled
|
|
146
|
-
if (this.getConfig('enableInputValidation', true)) {
|
|
147
|
-
this.validateParameters(parameters);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Execute the tool with timeout if configured
|
|
151
|
-
const timeout = this.getConfig('timeout', 30000);
|
|
152
|
-
const result = await this._executeWithTimeout(parameters, timeout);
|
|
153
|
-
|
|
154
|
-
if (enableDebug) {
|
|
155
|
-
console.error(`[Tool:${toolName}] Execution successful`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Return formatted MCP response
|
|
159
|
-
return {
|
|
160
|
-
content: [{
|
|
161
|
-
type: "text",
|
|
162
|
-
text: JSON.stringify(result)
|
|
163
|
-
}]
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
} catch (error) {
|
|
167
|
-
const enableDetailedErrors = this.getConfig('enableDetailedErrors', true);
|
|
168
|
-
|
|
169
|
-
if (enableDetailedErrors || enableDebug) {
|
|
170
|
-
console.error(`[Tool:${toolName}] Error during execution:`, error.message);
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// Throw properly formatted MCP error
|
|
174
|
-
throw {
|
|
175
|
-
code: -32000,
|
|
176
|
-
message: `Tool '${toolName}' execution failed: ${error.message}`,
|
|
177
|
-
data: enableDetailedErrors ? {
|
|
178
|
-
tool_name: toolName,
|
|
179
|
-
parameters: parameters,
|
|
180
|
-
original_error: error.message,
|
|
181
|
-
config: this.toolConfig
|
|
182
|
-
} : {
|
|
183
|
-
tool_name: toolName,
|
|
184
|
-
original_error: error.message
|
|
185
|
-
}
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
/**
|
|
191
|
-
* Execute the tool with a timeout
|
|
192
|
-
* @param {object} parameters - The input parameters
|
|
193
|
-
* @param {number} timeout - Timeout in milliseconds
|
|
194
|
-
* @returns {Promise<any>} - Execution result
|
|
195
|
-
*/
|
|
196
|
-
async _executeWithTimeout(parameters, timeout) {
|
|
197
|
-
return new Promise(async (resolve, reject) => {
|
|
198
|
-
const timeoutId = setTimeout(() => {
|
|
199
|
-
reject(new Error(`Tool execution timed out after ${timeout}ms`));
|
|
200
|
-
}, timeout);
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
const result = await this.execute(parameters);
|
|
204
|
-
clearTimeout(timeoutId);
|
|
205
|
-
resolve(result);
|
|
206
|
-
} catch (error) {
|
|
207
|
-
clearTimeout(timeoutId);
|
|
208
|
-
reject(error);
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
/**
|
|
214
|
-
* Gets the tool definition
|
|
215
|
-
* @returns {object} - The tool definition
|
|
216
|
-
*/
|
|
217
|
-
static getDefinition() {
|
|
218
|
-
return this.definition;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Gets the tool name from the definition
|
|
223
|
-
* @returns {string} - The tool name
|
|
224
|
-
*/
|
|
225
|
-
static getName() {
|
|
226
|
-
return this.definition.name;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
module.exports = ToolBase;
|