@probebrowser/trace-mcp 1.0.2 → 1.1.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/src/tools.ts CHANGED
@@ -102,9 +102,39 @@ export const TOOLS = {
102
102
  requestId: z.string().describe('The ID of the request to replay'),
103
103
  }),
104
104
  },
105
+ trace_set_network_throttling: {
106
+ method: 'set_network_throttling',
107
+ description: 'Set network throttling to a preset (slow3G, fast3G, offline).',
108
+ schema: z.object({
109
+ preset: z.string().describe('Preset: slow3G, fast3G, or offline'),
110
+ }),
111
+ },
112
+ trace_set_custom_network_throttling: {
113
+ method: 'set_custom_network_throttling',
114
+ description: 'Set custom network throttling parameters.',
115
+ schema: z.object({
116
+ downloadThroughput: z.number().describe('Download speed in bytes/sec'),
117
+ uploadThroughput: z.number().describe('Upload speed in bytes/sec'),
118
+ latency: z.number().describe('Latency in ms'),
119
+ }),
120
+ },
121
+ trace_disable_network_throttling: {
122
+ method: 'disable_network_throttling',
123
+ description: 'Disable network throttling.',
124
+ schema: z.object({}),
125
+ },
105
126
 
106
127
  // ============================================
107
- // DOM & UI TOOLS (16)
128
+ // DIAGNOSTIC (1)
129
+ // ============================================
130
+ trace_full_page_diagnostic: {
131
+ method: 'full_page_diagnostic',
132
+ description: 'COMPREHENSIVE page diagnostic that checks EVERYTHING: console errors, network failures (404s, 500s), slow requests, layout/overlap issues, hidden elements, CORS errors, mixed content. Use this FIRST when asked to find errors or debug a page.',
133
+ schema: z.object({}),
134
+ },
135
+
136
+ // ============================================
137
+ // DOM & UI TOOLS (22)
108
138
  // ============================================
109
139
  trace_inspect_element: {
110
140
  method: 'inspect_element',
@@ -120,6 +150,13 @@ export const TOOLS = {
120
150
  selector: z.string().describe('CSS selector'),
121
151
  }),
122
152
  },
153
+ trace_find_by_text: {
154
+ method: 'find_by_text',
155
+ description: 'Find elements containing specific visible text. Best for finding buttons, links, labels by their text content.',
156
+ schema: z.object({
157
+ text: z.string().describe('Text to search for (case-insensitive, partial match)'),
158
+ }),
159
+ },
123
160
  trace_get_element_styles: {
124
161
  method: 'get_element_styles',
125
162
  description: 'Get full computed style object.',
@@ -213,9 +250,29 @@ export const TOOLS = {
213
250
  selector: z.string().describe('CSS selector'),
214
251
  }),
215
252
  },
253
+ trace_check_layout_issues: {
254
+ method: 'check_layout_issues',
255
+ description: 'Find ALL layout issues: overlapping elements, hidden buttons/links, text overflow. Use when debugging visual bugs.',
256
+ schema: z.object({}),
257
+ },
258
+ trace_find_overlapping_elements: {
259
+ method: 'find_overlapping_elements',
260
+ description: 'Find elements that overlap and block each other (like invisible overlays covering buttons).',
261
+ schema: z.object({}),
262
+ },
263
+ trace_find_ghost_blockers: {
264
+ method: 'find_ghost_blockers',
265
+ description: 'Find INVISIBLE elements blocking user interaction! Detects: opacity:0 overlays, transparent full-screen blockers, modal backdrops left open, high z-index empty elements. Use when user says "why can\'t I click X?"',
266
+ schema: z.object({}),
267
+ },
268
+ trace_find_hidden_interactive_elements: {
269
+ method: 'find_hidden_interactive_elements',
270
+ description: 'Find buttons, links, inputs that are hidden but should be visible.',
271
+ schema: z.object({}),
272
+ },
216
273
 
217
274
  // ============================================
218
- // DEBUGGER TOOLS (24)
275
+ // DEBUGGER TOOLS (27)
219
276
  // ============================================
220
277
  trace_get_debugger_state: {
221
278
  method: 'get_debugger_state',
@@ -264,6 +321,15 @@ export const TOOLS = {
264
321
  expression: z.string().describe('JS code to evaluate'),
265
322
  }),
266
323
  },
324
+ trace_set_conditional_breakpoint: {
325
+ method: 'set_conditional_breakpoint',
326
+ description: 'Set a breakpoint that only triggers when a condition is met.',
327
+ schema: z.object({
328
+ url: z.string().describe('Script URL pattern'),
329
+ line: z.number().describe('Line number'),
330
+ condition: z.string().describe('JS condition expression'),
331
+ }),
332
+ },
267
333
  trace_set_exception_breakpoint: {
268
334
  method: 'set_exception_breakpoint',
269
335
  description: 'Pause on All or Uncaught exceptions.',
@@ -343,7 +409,7 @@ export const TOOLS = {
343
409
  description: 'Continue execution.',
344
410
  schema: z.object({}),
345
411
  },
346
- trace_set_dom_breakpoint: {
412
+ trace_set_dom_breakpoint_by_selector: {
347
413
  method: 'set_dom_breakpoint_by_selector',
348
414
  description: 'Break on node modification.',
349
415
  schema: z.object({
@@ -365,9 +431,22 @@ export const TOOLS = {
365
431
  enable: z.boolean().describe('True to enable, false to disable'),
366
432
  }),
367
433
  },
434
+ trace_get_variable_preview: {
435
+ method: 'get_variable_preview',
436
+ description: 'Get current variable values while debugging.',
437
+ schema: z.object({}),
438
+ },
439
+ trace_debug_and_analyze: {
440
+ method: 'debug_and_analyze',
441
+ description: 'Combined tool: set breakpoint, capture state, and analyze.',
442
+ schema: z.object({
443
+ url: z.string().describe('Script URL'),
444
+ line: z.number().describe('Line number'),
445
+ }),
446
+ },
368
447
 
369
448
  // ============================================
370
- // SOURCE TOOLS (8)
449
+ // SOURCE TOOLS (11)
371
450
  // ============================================
372
451
  trace_list_scripts: {
373
452
  method: 'list_scripts',
@@ -419,6 +498,23 @@ export const TOOLS = {
419
498
  scriptId: z.string().describe('Script ID'),
420
499
  }),
421
500
  },
501
+ trace_map_call_stack: {
502
+ method: 'map_call_stack',
503
+ description: 'Map the current call stack to original source locations using source maps.',
504
+ schema: z.object({}),
505
+ },
506
+ trace_list_blackbox_patterns: {
507
+ method: 'list_blackbox_patterns',
508
+ description: 'List current blackboxed script patterns (ignored during debugging).',
509
+ schema: z.object({}),
510
+ },
511
+ trace_analyze_fetch_origin: {
512
+ method: 'analyze_fetch_origin',
513
+ description: 'Analyze where a fetch/XHR request originated in the code.',
514
+ schema: z.object({
515
+ url: z.string().describe('URL pattern to match'),
516
+ }),
517
+ },
422
518
 
423
519
  // ============================================
424
520
  // PERFORMANCE TOOLS (4)
@@ -591,6 +687,20 @@ export const TOOLS = {
591
687
  cacheName: z.string(),
592
688
  }),
593
689
  },
690
+ trace_clear_indexeddb: {
691
+ method: 'clear_indexeddb',
692
+ description: 'Clear an IndexedDB database.',
693
+ schema: z.object({
694
+ database: z.string().describe('Database name'),
695
+ }),
696
+ },
697
+ trace_delete_cache: {
698
+ method: 'delete_cache',
699
+ description: 'Delete a cache from Cache Storage.',
700
+ schema: z.object({
701
+ cacheName: z.string().describe('Cache name'),
702
+ }),
703
+ },
594
704
 
595
705
  // ============================================
596
706
  // SECURITY TOOLS (3)
@@ -753,6 +863,91 @@ export const TOOLS = {
753
863
  }),
754
864
  },
755
865
 
866
+ // ============================================
867
+ // FULLSTACK DEBUGGING TOOLS (4)
868
+ // ============================================
869
+ trace_test_api_endpoint: {
870
+ method: 'test_api_endpoint',
871
+ description: 'Make a request to the configured backend API. WARNING: may bypass CORS/auth restrictions.',
872
+ schema: z.object({
873
+ path: z.string().describe('API path (e.g., /users, /products/123)'),
874
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method'),
875
+ body: z.any().optional().describe('Request body for POST/PUT/PATCH'),
876
+ headers: z.record(z.string()).optional().describe('Additional headers'),
877
+ }),
878
+ },
879
+ trace_replay_failed_api: {
880
+ method: 'replay_failed_api',
881
+ description: 'Replay a failed API request from the network log with optional modifications.',
882
+ schema: z.object({
883
+ requestId: z.string().describe('ID of the failed request to replay'),
884
+ modifications: z.any().optional().describe('Optional modifications: { headers, body, queryParams }'),
885
+ }),
886
+ },
887
+ trace_trace_request_flow: {
888
+ method: 'trace_request_flow',
889
+ description: 'Trace a request from frontend -> backend -> database. Correlates network request with API logs and DB queries.',
890
+ schema: z.object({
891
+ url: z.string().describe('URL pattern of the request to trace'),
892
+ correlationId: z.string().optional().describe('Optional X-Request-ID or correlation header'),
893
+ }),
894
+ },
895
+ trace_get_fullstack_config: {
896
+ method: 'get_fullstack_config',
897
+ description: 'Get the current full-stack debugging configuration (backend URL, DB type).',
898
+ schema: z.object({}),
899
+ },
900
+
901
+ // ============================================
902
+ // NODE.JS SERVER DEBUGGING TOOLS (7)
903
+ // ============================================
904
+ trace_connect_node_debugger: {
905
+ method: 'connect_node_debugger',
906
+ description: 'Connect to Node.js inspector (requires server started with --inspect). Auto-discovers on port 9229.',
907
+ schema: z.object({
908
+ port: z.number().optional().describe('Inspector port (default: 9229)'),
909
+ }),
910
+ },
911
+ trace_node_list_scripts: {
912
+ method: 'node_list_scripts',
913
+ description: 'List all server-side scripts loaded by Node.js (excludes node_modules and builtins).',
914
+ schema: z.object({}),
915
+ },
916
+ trace_node_get_source: {
917
+ method: 'node_get_source',
918
+ description: 'Read the source code of a server-side Node.js script.',
919
+ schema: z.object({
920
+ scriptId: z.string().describe('Script ID from node_list_scripts'),
921
+ }),
922
+ },
923
+ trace_node_set_breakpoint: {
924
+ method: 'node_set_breakpoint',
925
+ description: 'Set a breakpoint in server-side Node.js code.',
926
+ schema: z.object({
927
+ url: z.string().describe('Script URL or filename pattern'),
928
+ line: z.number().describe('Line number'),
929
+ }),
930
+ },
931
+ trace_node_get_variables: {
932
+ method: 'node_get_variables',
933
+ description: 'Get server-side variable values when Node.js debugger is paused.',
934
+ schema: z.object({}),
935
+ },
936
+ trace_node_step_debugger: {
937
+ method: 'node_step_debugger',
938
+ description: 'Step through server-side Node.js code.',
939
+ schema: z.object({
940
+ action: z.enum(['stepOver', 'stepInto', 'stepOut', 'resume']).describe('Step action'),
941
+ }),
942
+ },
943
+ trace_node_evaluate: {
944
+ method: 'node_evaluate',
945
+ description: 'Evaluate an expression in the Node.js server context.',
946
+ schema: z.object({
947
+ expression: z.string().describe('JavaScript expression to evaluate in Node.js'),
948
+ }),
949
+ },
950
+
756
951
  // ============================================
757
952
  // CONNECTION & META (2)
758
953
  // ============================================
@@ -0,0 +1,150 @@
1
+ import { describe, it, expect } from 'vitest';
2
+
3
+ // Test the Resources definitions that the server exposes
4
+ describe('MCP Resources', () => {
5
+ const EXPECTED_RESOURCES = [
6
+ {
7
+ uri: 'trace://console/errors',
8
+ name: 'Console Errors',
9
+ mimeType: 'application/json',
10
+ },
11
+ {
12
+ uri: 'trace://console/logs',
13
+ name: 'All Console Logs',
14
+ mimeType: 'application/json',
15
+ },
16
+ {
17
+ uri: 'trace://network/failed',
18
+ name: 'Failed Network Requests',
19
+ mimeType: 'application/json',
20
+ },
21
+ {
22
+ uri: 'trace://network/all',
23
+ name: 'All Network Traffic',
24
+ mimeType: 'application/json',
25
+ },
26
+ {
27
+ uri: 'trace://dom/tree',
28
+ name: 'DOM Tree',
29
+ mimeType: 'application/json',
30
+ },
31
+ {
32
+ uri: 'trace://screenshot',
33
+ name: 'Page Screenshot',
34
+ mimeType: 'image/png',
35
+ },
36
+ ];
37
+
38
+ describe('Resource URIs', () => {
39
+ it('should define console/errors resource', () => {
40
+ const resource = EXPECTED_RESOURCES.find(r => r.uri === 'trace://console/errors');
41
+ expect(resource).toBeDefined();
42
+ expect(resource?.name).toBe('Console Errors');
43
+ expect(resource?.mimeType).toBe('application/json');
44
+ });
45
+
46
+ it('should define console/logs resource', () => {
47
+ const resource = EXPECTED_RESOURCES.find(r => r.uri === 'trace://console/logs');
48
+ expect(resource).toBeDefined();
49
+ expect(resource?.name).toBe('All Console Logs');
50
+ });
51
+
52
+ it('should define network/failed resource', () => {
53
+ const resource = EXPECTED_RESOURCES.find(r => r.uri === 'trace://network/failed');
54
+ expect(resource).toBeDefined();
55
+ expect(resource?.mimeType).toBe('application/json');
56
+ });
57
+
58
+ it('should define network/all resource', () => {
59
+ const resource = EXPECTED_RESOURCES.find(r => r.uri === 'trace://network/all');
60
+ expect(resource).toBeDefined();
61
+ expect(resource?.name).toBe('All Network Traffic');
62
+ });
63
+
64
+ it('should define dom/tree resource', () => {
65
+ const resource = EXPECTED_RESOURCES.find(r => r.uri === 'trace://dom/tree');
66
+ expect(resource).toBeDefined();
67
+ expect(resource?.mimeType).toBe('application/json');
68
+ });
69
+
70
+ it('should define screenshot resource', () => {
71
+ const resource = EXPECTED_RESOURCES.find(r => r.uri === 'trace://screenshot');
72
+ expect(resource).toBeDefined();
73
+ expect(resource?.mimeType).toBe('image/png');
74
+ });
75
+ });
76
+
77
+ describe('Resource Schema', () => {
78
+ it('should have 6 resources defined', () => {
79
+ expect(EXPECTED_RESOURCES.length).toBe(6);
80
+ });
81
+
82
+ it('all resources should have uri, name, and mimeType', () => {
83
+ for (const resource of EXPECTED_RESOURCES) {
84
+ expect(resource.uri).toBeDefined();
85
+ expect(resource.uri.startsWith('trace://')).toBe(true);
86
+ expect(resource.name).toBeDefined();
87
+ expect(resource.name.length).toBeGreaterThan(0);
88
+ expect(resource.mimeType).toBeDefined();
89
+ }
90
+ });
91
+
92
+ it('all URIs should use trace:// protocol', () => {
93
+ for (const resource of EXPECTED_RESOURCES) {
94
+ expect(resource.uri).toMatch(/^trace:\/\//);
95
+ }
96
+ });
97
+ });
98
+ });
99
+
100
+ describe('MCP Prompts', () => {
101
+ const EXPECTED_PROMPTS = [
102
+ {
103
+ name: 'debug_error',
104
+ description: 'Analyze the most recent error and suggest a fix',
105
+ },
106
+ {
107
+ name: 'audit_performance',
108
+ description: 'Check metrics and run a quick profile',
109
+ },
110
+ {
111
+ name: 'verify_security',
112
+ description: 'Check security headers and SSL status',
113
+ },
114
+ ];
115
+
116
+ describe('Prompt Definitions', () => {
117
+ it('should define debug_error prompt', () => {
118
+ const prompt = EXPECTED_PROMPTS.find(p => p.name === 'debug_error');
119
+ expect(prompt).toBeDefined();
120
+ expect(prompt?.description).toContain('error');
121
+ });
122
+
123
+ it('should define audit_performance prompt', () => {
124
+ const prompt = EXPECTED_PROMPTS.find(p => p.name === 'audit_performance');
125
+ expect(prompt).toBeDefined();
126
+ expect(prompt?.description).toContain('metrics');
127
+ });
128
+
129
+ it('should define verify_security prompt', () => {
130
+ const prompt = EXPECTED_PROMPTS.find(p => p.name === 'verify_security');
131
+ expect(prompt).toBeDefined();
132
+ expect(prompt?.description).toContain('security');
133
+ });
134
+ });
135
+
136
+ describe('Prompt Schema', () => {
137
+ it('should have 3 prompts defined', () => {
138
+ expect(EXPECTED_PROMPTS.length).toBe(3);
139
+ });
140
+
141
+ it('all prompts should have name and description', () => {
142
+ for (const prompt of EXPECTED_PROMPTS) {
143
+ expect(prompt.name).toBeDefined();
144
+ expect(prompt.name.length).toBeGreaterThan(0);
145
+ expect(prompt.description).toBeDefined();
146
+ expect(prompt.description.length).toBeGreaterThan(0);
147
+ }
148
+ });
149
+ });
150
+ });
@@ -0,0 +1,163 @@
1
+ import { describe, it, expect, vi, afterEach } from 'vitest';
2
+
3
+ // We need to mock BEFORE importing the server
4
+ // Create a proper mock of the Server class
5
+ const mockSetRequestHandler = vi.fn();
6
+ const mockConnect = vi.fn().mockResolvedValue(undefined);
7
+
8
+ vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({
9
+ Server: vi.fn().mockImplementation(() => ({
10
+ setRequestHandler: mockSetRequestHandler,
11
+ connect: mockConnect,
12
+ })),
13
+ }));
14
+
15
+ vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({
16
+ StdioServerTransport: vi.fn(),
17
+ }));
18
+
19
+ vi.mock('@modelcontextprotocol/sdk/types.js', () => ({
20
+ ListToolsRequestSchema: 'ListToolsRequestSchema',
21
+ CallToolRequestSchema: 'CallToolRequestSchema',
22
+ ListResourcesRequestSchema: 'ListResourcesRequestSchema',
23
+ ReadResourceRequestSchema: 'ReadResourceRequestSchema',
24
+ ListPromptsRequestSchema: 'ListPromptsRequestSchema',
25
+ GetPromptRequestSchema: 'GetPromptRequestSchema',
26
+ }));
27
+
28
+ // Mock the SDK
29
+ vi.mock('@probebrowser/sdk', () => ({
30
+ Trace: vi.fn().mockImplementation(() => ({
31
+ connect: vi.fn().mockResolvedValue(undefined),
32
+ getCurrentUrl: vi.fn().mockReturnValue(null),
33
+ executeTool: vi.fn().mockResolvedValue({ success: true, data: {} }),
34
+ queryDeep: vi.fn().mockResolvedValue({
35
+ conclusion: 'Test conclusion',
36
+ steps: [{ tool: 'test', success: true }],
37
+ }),
38
+ })),
39
+ ALL_TOOLS: [
40
+ 'get_console_errors',
41
+ 'get_network_summary',
42
+ 'get_dom_tree',
43
+ 'click',
44
+ 'type',
45
+ ],
46
+ }));
47
+
48
+ // Now import the server
49
+ import { TraceMcpServer } from '../src/server.js';
50
+
51
+ describe('TraceMcpServer', () => {
52
+ afterEach(() => {
53
+ vi.clearAllMocks();
54
+ });
55
+
56
+ describe('Initialization', () => {
57
+ it('should create server instance', () => {
58
+ const server = new TraceMcpServer();
59
+ expect(server).toBeDefined();
60
+ expect(server).toBeInstanceOf(TraceMcpServer);
61
+ });
62
+
63
+ it('should call setRequestHandler during initialization', () => {
64
+ new TraceMcpServer();
65
+ // Should be called 6 times: ListTools, CallTool, ListResources, ReadResource, ListPrompts, GetPrompt
66
+ expect(mockSetRequestHandler).toHaveBeenCalled();
67
+ expect(mockSetRequestHandler.mock.calls.length).toBeGreaterThanOrEqual(6);
68
+ });
69
+
70
+ it('should initialize with default headless mode', () => {
71
+ const server = new TraceMcpServer();
72
+ expect(server).toBeDefined();
73
+ });
74
+ });
75
+
76
+ describe('Server Configuration', () => {
77
+ it('should have start method', () => {
78
+ const server = new TraceMcpServer();
79
+ expect(typeof server.start).toBe('function');
80
+ });
81
+
82
+ it('should have connect method', () => {
83
+ const server = new TraceMcpServer();
84
+ expect(typeof server.connect).toBe('function');
85
+ });
86
+
87
+ it('should set up tool handlers', () => {
88
+ new TraceMcpServer();
89
+ // Find the ListToolsRequestSchema handler
90
+ const listToolsCall = mockSetRequestHandler.mock.calls.find(
91
+ ([schema]) => schema === 'ListToolsRequestSchema'
92
+ );
93
+ expect(listToolsCall).toBeDefined();
94
+ });
95
+
96
+ it('should set up resource handlers', () => {
97
+ new TraceMcpServer();
98
+ const listResourcesCall = mockSetRequestHandler.mock.calls.find(
99
+ ([schema]) => schema === 'ListResourcesRequestSchema'
100
+ );
101
+ expect(listResourcesCall).toBeDefined();
102
+ });
103
+
104
+ it('should set up prompt handlers', () => {
105
+ new TraceMcpServer();
106
+ const listPromptsCall = mockSetRequestHandler.mock.calls.find(
107
+ ([schema]) => schema === 'ListPromptsRequestSchema'
108
+ );
109
+ expect(listPromptsCall).toBeDefined();
110
+ });
111
+ });
112
+ });
113
+
114
+ describe('Server Environment Configuration', () => {
115
+ const originalEnv = { ...process.env };
116
+
117
+ afterEach(() => {
118
+ process.env = { ...originalEnv };
119
+ vi.clearAllMocks();
120
+ });
121
+
122
+ it('should default to headless true when env not set', () => {
123
+ delete process.env.TRACE_HEADLESS;
124
+ const server = new TraceMcpServer();
125
+ expect(server).toBeDefined();
126
+ });
127
+
128
+ it('should set headless to false when explicitly specified', () => {
129
+ process.env.TRACE_HEADLESS = 'false';
130
+ const server = new TraceMcpServer();
131
+ expect(server).toBeDefined();
132
+ });
133
+
134
+ it('should enable verbose mode when specified', () => {
135
+ process.env.TRACE_VERBOSE = 'true';
136
+ const server = new TraceMcpServer();
137
+ expect(server).toBeDefined();
138
+ });
139
+
140
+ it('should use TRACE_API_KEY from environment', () => {
141
+ process.env.TRACE_API_KEY = 'test-api-key';
142
+ const server = new TraceMcpServer();
143
+ expect(server).toBeDefined();
144
+ });
145
+ });
146
+
147
+ describe('Handler Registration', () => {
148
+ afterEach(() => {
149
+ vi.clearAllMocks();
150
+ });
151
+
152
+ it('should register all 6 required handlers', () => {
153
+ new TraceMcpServer();
154
+ expect(mockSetRequestHandler).toHaveBeenCalledTimes(6);
155
+ });
156
+
157
+ it('handlers should be functions', () => {
158
+ new TraceMcpServer();
159
+ for (const [, handler] of mockSetRequestHandler.mock.calls) {
160
+ expect(typeof handler).toBe('function');
161
+ }
162
+ });
163
+ });