@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/dist/server.js +18 -12
- package/dist/tools.js +196 -4
- package/package.json +10 -5
- package/src/server.ts +18 -12
- package/src/tools.ts +199 -4
- package/tests/resources.test.ts +150 -0
- package/tests/server.test.ts +163 -0
- package/tests/tools.test.ts +288 -0
- package/vitest.config.ts +17 -0
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
|
-
//
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
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
|
+
});
|