@ricardodeazambuja/browser-mcp-server 1.0.3 → 1.4.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 (43) hide show
  1. package/CHANGELOG-v1.3.0.md +42 -0
  2. package/CHANGELOG-v1.4.0.md +8 -0
  3. package/README.md +271 -45
  4. package/package.json +11 -10
  5. package/plugins/.gitkeep +0 -0
  6. package/src/.gitkeep +0 -0
  7. package/src/browser.js +152 -0
  8. package/src/cdp.js +58 -0
  9. package/src/index.js +126 -0
  10. package/src/tools/.gitkeep +0 -0
  11. package/src/tools/console.js +139 -0
  12. package/src/tools/docs.js +1611 -0
  13. package/src/tools/index.js +60 -0
  14. package/src/tools/info.js +139 -0
  15. package/src/tools/interaction.js +126 -0
  16. package/src/tools/keyboard.js +27 -0
  17. package/src/tools/media.js +264 -0
  18. package/src/tools/mouse.js +104 -0
  19. package/src/tools/navigation.js +72 -0
  20. package/src/tools/network.js +552 -0
  21. package/src/tools/pages.js +149 -0
  22. package/src/tools/performance.js +517 -0
  23. package/src/tools/security.js +470 -0
  24. package/src/tools/storage.js +467 -0
  25. package/src/tools/system.js +196 -0
  26. package/src/utils.js +131 -0
  27. package/tests/.gitkeep +0 -0
  28. package/tests/fixtures/.gitkeep +0 -0
  29. package/tests/fixtures/test-media.html +35 -0
  30. package/tests/fixtures/test-network.html +48 -0
  31. package/tests/fixtures/test-performance.html +61 -0
  32. package/tests/fixtures/test-security.html +33 -0
  33. package/tests/fixtures/test-storage.html +76 -0
  34. package/tests/run-all.js +50 -0
  35. package/{test-browser-automation.js → tests/test-browser-automation.js} +44 -5
  36. package/{test-mcp.js → tests/test-mcp.js} +9 -4
  37. package/tests/test-media-tools.js +168 -0
  38. package/tests/test-network.js +212 -0
  39. package/tests/test-performance.js +254 -0
  40. package/tests/test-security.js +203 -0
  41. package/tests/test-storage.js +192 -0
  42. package/CHANGELOG-v1.0.2.md +0 -126
  43. package/browser-mcp-server-playwright.js +0 -792
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Performance Profiling Tools (CDP-based)
3
+ * CPU profiling, heap snapshots, memory metrics, web vitals, code coverage
4
+ */
5
+
6
+ const { connectToBrowser } = require('../browser');
7
+ const { getCDPSession } = require('../cdp');
8
+ const { debugLog } = require('../utils');
9
+
10
+ // Local state for performance tools
11
+ let profilingActive = false;
12
+ let coverageActive = false;
13
+
14
+ const definitions = [
15
+ {
16
+ name: 'browser_perf_start_profile',
17
+ description: 'Start CPU profiling to track JavaScript execution (see browser_docs)',
18
+ inputSchema: {
19
+ type: 'object',
20
+ properties: {
21
+ sampleInterval: {
22
+ type: 'number',
23
+ description: 'Microseconds between samples (default: 100)'
24
+ }
25
+ },
26
+ additionalProperties: false,
27
+ $schema: 'http://json-schema.org/draft-07/schema#'
28
+ }
29
+ },
30
+ {
31
+ name: 'browser_perf_stop_profile',
32
+ description: 'Stop CPU profiling and get profile data (see browser_docs)',
33
+ inputSchema: {
34
+ type: 'object',
35
+ properties: {},
36
+ additionalProperties: false,
37
+ $schema: 'http://json-schema.org/draft-07/schema#'
38
+ }
39
+ },
40
+ {
41
+ name: 'browser_perf_take_heap_snapshot',
42
+ description: 'Capture heap snapshot for memory analysis (see browser_docs)',
43
+ inputSchema: {
44
+ type: 'object',
45
+ properties: {
46
+ reportProgress: {
47
+ type: 'boolean',
48
+ description: 'Report progress events (default: false)'
49
+ }
50
+ },
51
+ additionalProperties: false,
52
+ $schema: 'http://json-schema.org/draft-07/schema#'
53
+ }
54
+ },
55
+ {
56
+ name: 'browser_perf_get_heap_usage',
57
+ description: 'Get current JavaScript heap usage statistics (see browser_docs)',
58
+ inputSchema: {
59
+ type: 'object',
60
+ properties: {},
61
+ additionalProperties: false,
62
+ $schema: 'http://json-schema.org/draft-07/schema#'
63
+ }
64
+ },
65
+ {
66
+ name: 'browser_perf_get_metrics',
67
+ description: 'Get runtime performance metrics (DOM nodes, event listeners, JS heap) (see browser_docs)',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {},
71
+ additionalProperties: false,
72
+ $schema: 'http://json-schema.org/draft-07/schema#'
73
+ }
74
+ },
75
+ {
76
+ name: 'browser_perf_get_performance_metrics',
77
+ description: 'Get web vitals and navigation timing (FCP, LCP, CLS, TTFB) (see browser_docs)',
78
+ inputSchema: {
79
+ type: 'object',
80
+ properties: {},
81
+ additionalProperties: false,
82
+ $schema: 'http://json-schema.org/draft-07/schema#'
83
+ }
84
+ },
85
+ {
86
+ name: 'browser_perf_start_coverage',
87
+ description: 'Start tracking CSS and JavaScript code coverage (see browser_docs)',
88
+ inputSchema: {
89
+ type: 'object',
90
+ properties: {
91
+ resetOnNavigation: {
92
+ type: 'boolean',
93
+ description: 'Reset coverage on navigation (default: true)'
94
+ }
95
+ },
96
+ additionalProperties: false,
97
+ $schema: 'http://json-schema.org/draft-07/schema#'
98
+ }
99
+ },
100
+ {
101
+ name: 'browser_perf_stop_coverage',
102
+ description: 'Stop coverage and get results showing used vs unused code (see browser_docs)',
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {},
106
+ additionalProperties: false,
107
+ $schema: 'http://json-schema.org/draft-07/schema#'
108
+ }
109
+ }
110
+ ];
111
+
112
+ const handlers = {
113
+ browser_perf_start_profile: async (args) => {
114
+ try {
115
+ if (profilingActive) {
116
+ return {
117
+ content: [{
118
+ type: 'text',
119
+ text: '⚠️ CPU profiling is already active.\n\nUse browser_perf_stop_profile to get results first.'
120
+ }]
121
+ };
122
+ }
123
+
124
+ const cdp = await getCDPSession();
125
+ const interval = args.sampleInterval || 100;
126
+
127
+ await cdp.send('Profiler.enable');
128
+ await cdp.send('Profiler.start', { samplingInterval: interval });
129
+ profilingActive = true;
130
+
131
+ debugLog(`Started CPU profiling with ${interval}μs sample interval`);
132
+
133
+ return {
134
+ content: [{
135
+ type: 'text',
136
+ text: `✅ CPU profiling started with sample interval: ${interval}μs\n\nProfiling JavaScript execution...\nUse browser_perf_stop_profile to get results.`
137
+ }]
138
+ };
139
+ } catch (error) {
140
+ debugLog(`CDP error in browser_perf_start_profile: ${error.message}`);
141
+ return {
142
+ content: [{
143
+ type: 'text',
144
+ text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- Browser doesn't support CPU profiling\n- CDP session disconnected\n- Page not fully loaded`
145
+ }],
146
+ isError: true
147
+ };
148
+ }
149
+ },
150
+
151
+ browser_perf_stop_profile: async (args) => {
152
+ try {
153
+ if (!profilingActive) {
154
+ return {
155
+ content: [{
156
+ type: 'text',
157
+ text: '⚠️ CPU profiling is not active.\n\nUse browser_perf_start_profile to start profiling first.'
158
+ }]
159
+ };
160
+ }
161
+
162
+ const cdp = await getCDPSession();
163
+ const { profile } = await cdp.send('Profiler.stop');
164
+ await cdp.send('Profiler.disable');
165
+ profilingActive = false;
166
+
167
+ debugLog('Stopped CPU profiling');
168
+
169
+ // Profile can be huge - provide summary instead of full data
170
+ const totalTime = profile.timeDeltas ? profile.timeDeltas.reduce((a, b) => a + b, 0) : 0;
171
+ const topFunctions = profile.nodes
172
+ .filter(n => n.callFrame && n.callFrame.functionName)
173
+ .slice(0, 15)
174
+ .map(n => ({
175
+ function: n.callFrame.functionName || '(anonymous)',
176
+ url: n.callFrame.url || '(internal)',
177
+ line: n.callFrame.lineNumber
178
+ }));
179
+
180
+ const summary = {
181
+ totalNodes: profile.nodes.length,
182
+ totalSamples: profile.samples ? profile.samples.length : 0,
183
+ durationMicroseconds: totalTime,
184
+ durationMs: (totalTime / 1000).toFixed(2),
185
+ topFunctions: topFunctions
186
+ };
187
+
188
+ return {
189
+ content: [{
190
+ type: 'text',
191
+ text: `✅ CPU Profile Results:\n\n${JSON.stringify(summary, null, 2)}\n\nNote: Full profile data (${profile.nodes.length} nodes) is too large to display.`
192
+ }]
193
+ };
194
+ } catch (error) {
195
+ profilingActive = false;
196
+ debugLog(`CDP error in browser_perf_stop_profile: ${error.message}`);
197
+ return {
198
+ content: [{
199
+ type: 'text',
200
+ text: `❌ CDP Error: ${error.message}\n\nProfiling has been stopped.`
201
+ }],
202
+ isError: true
203
+ };
204
+ }
205
+ },
206
+
207
+ browser_perf_take_heap_snapshot: async (args) => {
208
+ try {
209
+ const cdp = await getCDPSession();
210
+ const reportProgress = args.reportProgress || false;
211
+
212
+ debugLog('Taking heap snapshot...');
213
+
214
+ let chunks = [];
215
+ let chunkCount = 0;
216
+
217
+ // Listen for heap snapshot chunks
218
+ cdp.on('HeapProfiler.addHeapSnapshotChunk', (params) => {
219
+ chunks.push(params.chunk);
220
+ chunkCount++;
221
+ });
222
+
223
+ if (reportProgress) {
224
+ cdp.on('HeapProfiler.reportHeapSnapshotProgress', (params) => {
225
+ debugLog(`Heap snapshot progress: ${params.done}/${params.total}`);
226
+ });
227
+ }
228
+
229
+ await cdp.send('HeapProfiler.takeHeapSnapshot', { reportProgress });
230
+
231
+ // Remove listeners
232
+ cdp.removeAllListeners('HeapProfiler.addHeapSnapshotChunk');
233
+ if (reportProgress) {
234
+ cdp.removeAllListeners('HeapProfiler.reportHeapSnapshotProgress');
235
+ }
236
+
237
+ const fullSnapshot = chunks.join('');
238
+ const snapshotSize = fullSnapshot.length;
239
+
240
+ debugLog(`Heap snapshot complete: ${snapshotSize} bytes in ${chunkCount} chunks`);
241
+
242
+ return {
243
+ content: [{
244
+ type: 'text',
245
+ text: `✅ Heap Snapshot Captured\n\nSize: ${(snapshotSize / 1024).toFixed(2)} KB\nChunks: ${chunkCount}\n\nNote: Snapshot data is too large to display in full. Use Chrome DevTools to analyze heap snapshots in detail.`
246
+ }]
247
+ };
248
+ } catch (error) {
249
+ debugLog(`CDP error in browser_perf_take_heap_snapshot: ${error.message}`);
250
+ return {
251
+ content: [{
252
+ type: 'text',
253
+ text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- Browser doesn't support heap profiling\n- Not enough memory to capture snapshot`
254
+ }],
255
+ isError: true
256
+ };
257
+ }
258
+ },
259
+
260
+ browser_perf_get_heap_usage: async (args) => {
261
+ try {
262
+ const cdp = await getCDPSession();
263
+ const result = await cdp.send('Runtime.getHeapUsage');
264
+
265
+ const heapInfo = {
266
+ usedSize: result.usedSize,
267
+ usedSizeMB: (result.usedSize / 1024 / 1024).toFixed(2),
268
+ totalSize: result.totalSize,
269
+ totalSizeMB: (result.totalSize / 1024 / 1024).toFixed(2),
270
+ limit: result.limit,
271
+ limitMB: (result.limit / 1024 / 1024).toFixed(2),
272
+ usagePercent: ((result.usedSize / result.totalSize) * 100).toFixed(2)
273
+ };
274
+
275
+ return {
276
+ content: [{
277
+ type: 'text',
278
+ text: `📊 JavaScript Heap Usage:\n\n${JSON.stringify(heapInfo, null, 2)}`
279
+ }]
280
+ };
281
+ } catch (error) {
282
+ debugLog(`CDP error in browser_perf_get_heap_usage: ${error.message}`);
283
+ return {
284
+ content: [{
285
+ type: 'text',
286
+ text: `❌ CDP Error: ${error.message}`
287
+ }],
288
+ isError: true
289
+ };
290
+ }
291
+ },
292
+
293
+ browser_perf_get_metrics: async (args) => {
294
+ try {
295
+ const cdp = await getCDPSession();
296
+ await cdp.send('Performance.enable');
297
+ const { metrics } = await cdp.send('Performance.getMetrics');
298
+ await cdp.send('Performance.disable');
299
+
300
+ const formattedMetrics = metrics.map(m => ({
301
+ name: m.name,
302
+ value: m.value
303
+ }));
304
+
305
+ return {
306
+ content: [{
307
+ type: 'text',
308
+ text: `📊 Runtime Performance Metrics:\n\n${JSON.stringify(formattedMetrics, null, 2)}`
309
+ }]
310
+ };
311
+ } catch (error) {
312
+ debugLog(`CDP error in browser_perf_get_metrics: ${error.message}`);
313
+ return {
314
+ content: [{
315
+ type: 'text',
316
+ text: `❌ CDP Error: ${error.message}`
317
+ }],
318
+ isError: true
319
+ };
320
+ }
321
+ },
322
+
323
+ browser_perf_get_performance_metrics: async (args) => {
324
+ try {
325
+ const { page } = await connectToBrowser();
326
+
327
+ // Use Performance API to get web vitals and timing
328
+ const metrics = await page.evaluate(() => {
329
+ const result = {
330
+ navigation: {},
331
+ paint: {},
332
+ webVitals: {}
333
+ };
334
+
335
+ // Navigation Timing
336
+ if (performance.timing) {
337
+ const t = performance.timing;
338
+ result.navigation = {
339
+ domContentLoaded: t.domContentLoadedEventEnd - t.navigationStart,
340
+ loadComplete: t.loadEventEnd - t.navigationStart,
341
+ domInteractive: t.domInteractive - t.navigationStart,
342
+ ttfb: t.responseStart - t.navigationStart
343
+ };
344
+ }
345
+
346
+ // Paint Timing
347
+ if (performance.getEntriesByType) {
348
+ const paintEntries = performance.getEntriesByType('paint');
349
+ paintEntries.forEach(entry => {
350
+ result.paint[entry.name] = entry.startTime;
351
+ });
352
+ }
353
+
354
+ // Try to get LCP using PerformanceObserver (if available)
355
+ const lcpEntries = performance.getEntriesByType('largest-contentful-paint');
356
+ if (lcpEntries && lcpEntries.length > 0) {
357
+ result.webVitals.lcp = lcpEntries[lcpEntries.length - 1].startTime;
358
+ }
359
+
360
+ // Get CLS (Cumulative Layout Shift)
361
+ const clsEntries = performance.getEntriesByType('layout-shift');
362
+ if (clsEntries) {
363
+ result.webVitals.cls = clsEntries
364
+ .filter(entry => !entry.hadRecentInput)
365
+ .reduce((sum, entry) => sum + entry.value, 0);
366
+ }
367
+
368
+ return result;
369
+ });
370
+
371
+ return {
372
+ content: [{
373
+ type: 'text',
374
+ text: `📊 Web Performance Metrics:\n\n${JSON.stringify(metrics, null, 2)}\n\nNote: Some metrics may not be available depending on page state and browser support.`
375
+ }]
376
+ };
377
+ } catch (error) {
378
+ debugLog(`Error in browser_perf_get_performance_metrics: ${error.message}`);
379
+ return {
380
+ content: [{
381
+ type: 'text',
382
+ text: `❌ Error: ${error.message}`
383
+ }],
384
+ isError: true
385
+ };
386
+ }
387
+ },
388
+
389
+ browser_perf_start_coverage: async (args) => {
390
+ try {
391
+ if (coverageActive) {
392
+ return {
393
+ content: [{
394
+ type: 'text',
395
+ text: '⚠️ Code coverage is already active.\n\nUse browser_perf_stop_coverage to get results first.'
396
+ }]
397
+ };
398
+ }
399
+
400
+ const cdp = await getCDPSession();
401
+ const resetOnNavigation = args.resetOnNavigation !== false;
402
+
403
+ await cdp.send('Profiler.enable');
404
+ await cdp.send('Profiler.startPreciseCoverage', {
405
+ callCount: false,
406
+ detailed: true
407
+ });
408
+
409
+ await cdp.send('DOM.enable');
410
+ await cdp.send('CSS.enable');
411
+ await cdp.send('CSS.startRuleUsageTracking');
412
+
413
+ coverageActive = true;
414
+
415
+ debugLog('Started code coverage tracking');
416
+
417
+ return {
418
+ content: [{
419
+ type: 'text',
420
+ text: `✅ Code coverage started for CSS and JavaScript\n\nResetOnNavigation: ${resetOnNavigation}\n\nUse browser_perf_stop_coverage to get results.`
421
+ }]
422
+ };
423
+ } catch (error) {
424
+ debugLog(`CDP error in browser_perf_start_coverage: ${error.message}`);
425
+ return {
426
+ content: [{
427
+ type: 'text',
428
+ text: `❌ CDP Error: ${error.message}\n\nPossible causes:\n- Browser doesn't support code coverage\n- CDP session disconnected`
429
+ }],
430
+ isError: true
431
+ };
432
+ }
433
+ },
434
+
435
+ browser_perf_stop_coverage: async (args) => {
436
+ try {
437
+ if (!coverageActive) {
438
+ return {
439
+ content: [{
440
+ type: 'text',
441
+ text: '⚠️ Code coverage is not active.\n\nUse browser_perf_start_coverage to start tracking first.'
442
+ }]
443
+ };
444
+ }
445
+
446
+ const cdp = await getCDPSession();
447
+
448
+ // Get JavaScript coverage
449
+ const { result: jsCoverage } = await cdp.send('Profiler.takePreciseCoverage');
450
+ await cdp.send('Profiler.stopPreciseCoverage');
451
+ await cdp.send('Profiler.disable');
452
+
453
+ // Get CSS coverage
454
+ const { ruleUsage: cssCoverage } = await cdp.send('CSS.stopRuleUsageTracking');
455
+ await cdp.send('CSS.disable');
456
+ await cdp.send('DOM.disable');
457
+
458
+ coverageActive = false;
459
+
460
+ debugLog('Stopped code coverage tracking');
461
+
462
+ // Summarize coverage data
463
+ const jsSummary = jsCoverage.slice(0, 10).map(entry => {
464
+ const totalBytes = entry.functions.reduce((sum, fn) => {
465
+ return sum + fn.ranges.reduce((s, r) => s + (r.endOffset - r.startOffset), 0);
466
+ }, 0);
467
+ const usedBytes = entry.functions.reduce((sum, fn) => {
468
+ return sum + fn.ranges.filter(r => r.count > 0).reduce((s, r) => s + (r.endOffset - r.startOffset), 0);
469
+ }, 0);
470
+
471
+ return {
472
+ url: entry.url,
473
+ usedBytes,
474
+ totalBytes,
475
+ coverage: totalBytes > 0 ? ((usedBytes / totalBytes) * 100).toFixed(2) + '%' : 'N/A'
476
+ };
477
+ });
478
+
479
+ const cssSummary = cssCoverage.slice(0, 10).map(rule => ({
480
+ used: rule.used,
481
+ styleSheetId: rule.styleSheetId,
482
+ startOffset: rule.startOffset,
483
+ endOffset: rule.endOffset
484
+ }));
485
+
486
+ const result = {
487
+ javascript: {
488
+ filesAnalyzed: jsCoverage.length,
489
+ topFiles: jsSummary
490
+ },
491
+ css: {
492
+ rulesAnalyzed: cssCoverage.length,
493
+ topRules: cssSummary.slice(0, 5)
494
+ }
495
+ };
496
+
497
+ return {
498
+ content: [{
499
+ type: 'text',
500
+ text: `✅ Code Coverage Results:\n\n${JSON.stringify(result, null, 2)}\n\nNote: Showing top 10 files. Full coverage data available via CDP.`
501
+ }]
502
+ };
503
+ } catch (error) {
504
+ coverageActive = false;
505
+ debugLog(`CDP error in browser_perf_stop_coverage: ${error.message}`);
506
+ return {
507
+ content: [{
508
+ type: 'text',
509
+ text: `❌ CDP Error: ${error.message}\n\nCoverage tracking has been stopped.`
510
+ }],
511
+ isError: true
512
+ };
513
+ }
514
+ }
515
+ };
516
+
517
+ module.exports = { definitions, handlers };