@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,440 @@
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 Network Tool - Monitor and analyze network requests
25
+ * Inspired by Playwright MCP network capabilities
26
+ */
27
+ class BrowserNetworkTool extends ToolBase {
28
+ static definition = {
29
+ name: "browser_network",
30
+ description: "Monitor network requests, analyze performance, and get detailed network information. Supports filtering and real-time monitoring.",
31
+ input_schema: {
32
+ type: "object",
33
+ properties: {
34
+ browserId: {
35
+ type: "string",
36
+ description: "The ID of the browser instance"
37
+ },
38
+ action: {
39
+ type: "string",
40
+ enum: ["list", "monitor", "clear", "filter", "performance"],
41
+ description: "The network action to perform"
42
+ },
43
+ filter: {
44
+ type: "object",
45
+ properties: {
46
+ url: { type: "string", description: "URL pattern to filter (regex supported)" },
47
+ method: { type: "string", description: "HTTP method to filter (GET, POST, etc.)" },
48
+ status: { type: "number", description: "HTTP status code to filter" },
49
+ resourceType: {
50
+ type: "string",
51
+ enum: ["Document", "Stylesheet", "Image", "Media", "Font", "Script", "TextTrack", "XHR", "Fetch", "EventSource", "WebSocket", "Manifest", "Other"],
52
+ description: "Resource type to filter"
53
+ }
54
+ },
55
+ description: "Filter criteria for network requests"
56
+ },
57
+ includeResponseBody: {
58
+ type: "boolean",
59
+ default: false,
60
+ description: "Whether to include response body in results (for list action)"
61
+ },
62
+ includeRequestBody: {
63
+ type: "boolean",
64
+ default: false,
65
+ description: "Whether to include request body in results (for list action)"
66
+ },
67
+ limit: {
68
+ type: "number",
69
+ default: 50,
70
+ description: "Maximum number of requests to return"
71
+ }
72
+ },
73
+ required: ["browserId", "action"]
74
+ },
75
+ output_schema: {
76
+ type: "object",
77
+ properties: {
78
+ success: { type: "boolean", description: "Whether the operation was successful" },
79
+ action: { type: "string", description: "The action that was performed" },
80
+ requests: {
81
+ type: "array",
82
+ items: {
83
+ type: "object",
84
+ properties: {
85
+ url: { type: "string" },
86
+ method: { type: "string" },
87
+ status: { type: "number" },
88
+ statusText: { type: "string" },
89
+ resourceType: { type: "string" },
90
+ timing: { type: "object" },
91
+ headers: { type: "object" },
92
+ size: { type: "number" }
93
+ }
94
+ },
95
+ description: "List of network requests"
96
+ },
97
+ summary: {
98
+ type: "object",
99
+ properties: {
100
+ totalRequests: { type: "number" },
101
+ failedRequests: { type: "number" },
102
+ totalSize: { type: "number" },
103
+ averageTime: { type: "number" }
104
+ },
105
+ description: "Network summary statistics"
106
+ },
107
+ browserId: { type: "string", description: "Browser instance ID" }
108
+ },
109
+ required: ["success", "action", "browserId"]
110
+ }
111
+ };
112
+
113
+ constructor() {
114
+ super();
115
+ this.networkData = new Map(); // browserId -> network data
116
+ }
117
+
118
+ async execute(parameters) {
119
+ const {
120
+ browserId,
121
+ action,
122
+ filter = {},
123
+ includeResponseBody = false,
124
+ includeRequestBody = false,
125
+ limit = 50
126
+ } = parameters;
127
+
128
+ const browser = browserService.getBrowserInstance(browserId);
129
+ if (!browser) {
130
+ throw new Error(`Browser instance '${browserId}' not found`);
131
+ }
132
+
133
+ const client = browser.client;
134
+
135
+ let result = {
136
+ success: false,
137
+ action: action,
138
+ browserId: browserId
139
+ };
140
+
141
+ switch (action) {
142
+ case 'monitor':
143
+ await this.startNetworkMonitoring(client, browserId);
144
+ result.success = true;
145
+ result.message = 'Network monitoring started';
146
+ break;
147
+
148
+ case 'list':
149
+ const requests = await this.listRequests(browserId, filter, includeRequestBody, includeResponseBody, limit);
150
+ result.success = true;
151
+ result.requests = requests.requests;
152
+ result.summary = requests.summary;
153
+ break;
154
+
155
+ case 'clear':
156
+ this.clearNetworkData(browserId);
157
+ result.success = true;
158
+ result.message = 'Network data cleared';
159
+ break;
160
+
161
+ case 'filter':
162
+ const filteredRequests = await this.filterRequests(browserId, filter, limit);
163
+ result.success = true;
164
+ result.requests = filteredRequests.requests;
165
+ result.summary = filteredRequests.summary;
166
+ break;
167
+
168
+ case 'performance':
169
+ const perfData = await this.getPerformanceData(browserId);
170
+ result.success = true;
171
+ result.performance = perfData;
172
+ break;
173
+
174
+ default:
175
+ throw new Error(`Unsupported network action: ${action}`);
176
+ }
177
+
178
+ return result;
179
+ }
180
+
181
+ /**
182
+ * Start monitoring network requests
183
+ */
184
+ async startNetworkMonitoring(client, browserId) {
185
+ // Enable network domain
186
+ await client.Network.enable();
187
+
188
+ // Initialize storage for this browser
189
+ if (!this.networkData.has(browserId)) {
190
+ this.networkData.set(browserId, {
191
+ requests: [],
192
+ responses: new Map(),
193
+ startTime: Date.now()
194
+ });
195
+ }
196
+
197
+ const networkStore = this.networkData.get(browserId);
198
+
199
+ // Listen for network events
200
+ client.Network.requestWillBeSent((params) => {
201
+ const request = {
202
+ requestId: params.requestId,
203
+ url: params.request.url,
204
+ method: params.request.method,
205
+ headers: params.request.headers,
206
+ postData: params.request.postData,
207
+ timestamp: params.timestamp,
208
+ resourceType: params.type,
209
+ initiator: params.initiator,
210
+ startTime: Date.now()
211
+ };
212
+
213
+ networkStore.requests.push(request);
214
+ });
215
+
216
+ client.Network.responseReceived((params) => {
217
+ const response = {
218
+ requestId: params.requestId,
219
+ url: params.response.url,
220
+ status: params.response.status,
221
+ statusText: params.response.statusText,
222
+ headers: params.response.headers,
223
+ mimeType: params.response.mimeType,
224
+ timestamp: params.timestamp,
225
+ encodedDataLength: params.response.encodedDataLength,
226
+ fromCache: params.response.fromDiskCache || params.response.fromServiceWorker
227
+ };
228
+
229
+ networkStore.responses.set(params.requestId, response);
230
+ });
231
+
232
+ client.Network.loadingFinished((params) => {
233
+ const request = networkStore.requests.find(r => r.requestId === params.requestId);
234
+ const response = networkStore.responses.get(params.requestId);
235
+
236
+ if (request && response) {
237
+ request.endTime = Date.now();
238
+ request.duration = request.endTime - request.startTime;
239
+ request.response = response;
240
+ request.encodedDataLength = params.encodedDataLength;
241
+ }
242
+ });
243
+
244
+ client.Network.loadingFailed((params) => {
245
+ const request = networkStore.requests.find(r => r.requestId === params.requestId);
246
+ if (request) {
247
+ request.failed = true;
248
+ request.errorText = params.errorText;
249
+ request.endTime = Date.now();
250
+ request.duration = request.endTime - request.startTime;
251
+ }
252
+ });
253
+ }
254
+
255
+ /**
256
+ * List network requests with optional filtering
257
+ */
258
+ async listRequests(browserId, filter, includeRequestBody, includeResponseBody, limit) {
259
+ const networkStore = this.networkData.get(browserId);
260
+ if (!networkStore) {
261
+ return { requests: [], summary: this.createSummary([]) };
262
+ }
263
+
264
+ let requests = [...networkStore.requests];
265
+
266
+ // Apply filters
267
+ requests = this.applyFilters(requests, filter);
268
+
269
+ // Limit results
270
+ requests = requests.slice(-limit);
271
+
272
+ // Format requests
273
+ const formattedRequests = await Promise.all(
274
+ requests.map(req => this.formatRequest(req, includeRequestBody, includeResponseBody))
275
+ );
276
+
277
+ return {
278
+ requests: formattedRequests,
279
+ summary: this.createSummary(formattedRequests)
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Filter existing requests
285
+ */
286
+ async filterRequests(browserId, filter, limit) {
287
+ return this.listRequests(browserId, filter, false, false, limit);
288
+ }
289
+
290
+ /**
291
+ * Get performance data
292
+ */
293
+ async getPerformanceData(browserId) {
294
+ const networkStore = this.networkData.get(browserId);
295
+ if (!networkStore) {
296
+ return { error: 'No network data available' };
297
+ }
298
+
299
+ const requests = networkStore.requests.filter(r => r.response && !r.failed);
300
+
301
+ if (requests.length === 0) {
302
+ return { error: 'No completed requests found' };
303
+ }
304
+
305
+ const totalSize = requests.reduce((sum, req) => sum + (req.encodedDataLength || 0), 0);
306
+ const totalTime = requests.reduce((sum, req) => sum + (req.duration || 0), 0);
307
+ const avgTime = totalTime / requests.length;
308
+
309
+ const resourceTypes = {};
310
+ requests.forEach(req => {
311
+ const type = req.resourceType || 'Other';
312
+ if (!resourceTypes[type]) {
313
+ resourceTypes[type] = { count: 0, size: 0 };
314
+ }
315
+ resourceTypes[type].count++;
316
+ resourceTypes[type].size += req.encodedDataLength || 0;
317
+ });
318
+
319
+ const statusCodes = {};
320
+ requests.forEach(req => {
321
+ const status = req.response?.status || 'Unknown';
322
+ statusCodes[status] = (statusCodes[status] || 0) + 1;
323
+ });
324
+
325
+ return {
326
+ totalRequests: requests.length,
327
+ totalSize: totalSize,
328
+ averageResponseTime: Math.round(avgTime),
329
+ resourceTypes: resourceTypes,
330
+ statusCodes: statusCodes,
331
+ slowestRequests: requests
332
+ .sort((a, b) => (b.duration || 0) - (a.duration || 0))
333
+ .slice(0, 5)
334
+ .map(req => ({
335
+ url: req.url,
336
+ duration: req.duration,
337
+ size: req.encodedDataLength
338
+ })),
339
+ largestRequests: requests
340
+ .sort((a, b) => (b.encodedDataLength || 0) - (a.encodedDataLength || 0))
341
+ .slice(0, 5)
342
+ .map(req => ({
343
+ url: req.url,
344
+ size: req.encodedDataLength,
345
+ duration: req.duration
346
+ }))
347
+ };
348
+ }
349
+
350
+ /**
351
+ * Apply filters to requests
352
+ */
353
+ applyFilters(requests, filter) {
354
+ return requests.filter(req => {
355
+ if (filter.url) {
356
+ const urlRegex = new RegExp(filter.url, 'i');
357
+ if (!urlRegex.test(req.url)) return false;
358
+ }
359
+
360
+ if (filter.method && req.method !== filter.method.toUpperCase()) {
361
+ return false;
362
+ }
363
+
364
+ if (filter.status && req.response?.status !== filter.status) {
365
+ return false;
366
+ }
367
+
368
+ if (filter.resourceType && req.resourceType !== filter.resourceType) {
369
+ return false;
370
+ }
371
+
372
+ return true;
373
+ });
374
+ }
375
+
376
+ /**
377
+ * Format a single request for output
378
+ */
379
+ async formatRequest(request, includeRequestBody, includeResponseBody) {
380
+ const formatted = {
381
+ url: request.url,
382
+ method: request.method,
383
+ resourceType: request.resourceType,
384
+ status: request.response?.status,
385
+ statusText: request.response?.statusText,
386
+ duration: request.duration,
387
+ size: request.encodedDataLength,
388
+ failed: request.failed || false,
389
+ fromCache: request.response?.fromCache || false,
390
+ timestamp: request.timestamp
391
+ };
392
+
393
+ if (includeRequestBody && request.postData) {
394
+ formatted.requestBody = request.postData;
395
+ }
396
+
397
+ if (request.headers) {
398
+ formatted.requestHeaders = request.headers;
399
+ }
400
+
401
+ if (request.response?.headers) {
402
+ formatted.responseHeaders = request.response.headers;
403
+ }
404
+
405
+ if (request.errorText) {
406
+ formatted.error = request.errorText;
407
+ }
408
+
409
+ return formatted;
410
+ }
411
+
412
+ /**
413
+ * Create summary statistics
414
+ */
415
+ createSummary(requests) {
416
+ const completed = requests.filter(r => !r.failed);
417
+ const failed = requests.filter(r => r.failed);
418
+
419
+ const totalSize = completed.reduce((sum, req) => sum + (req.size || 0), 0);
420
+ const totalTime = completed.reduce((sum, req) => sum + (req.duration || 0), 0);
421
+ const avgTime = completed.length > 0 ? totalTime / completed.length : 0;
422
+
423
+ return {
424
+ totalRequests: requests.length,
425
+ completedRequests: completed.length,
426
+ failedRequests: failed.length,
427
+ totalSize: totalSize,
428
+ averageTime: Math.round(avgTime)
429
+ };
430
+ }
431
+
432
+ /**
433
+ * Clear network data for a browser
434
+ */
435
+ clearNetworkData(browserId) {
436
+ this.networkData.delete(browserId);
437
+ }
438
+ }
439
+
440
+ module.exports = BrowserNetworkTool;