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