@probebrowser/trace-mcp 1.0.3 → 1.1.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.
- package/dist/server.js +51 -15
- package/dist/tools.js +196 -4
- package/package.json +8 -3
- package/src/server.ts +57 -19
- 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/dist/server.js
CHANGED
|
@@ -3,16 +3,48 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
3
3
|
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
4
4
|
import { Trace, ALL_TOOLS } from '@probebrowser/sdk';
|
|
5
5
|
import { TOOLS } from './tools.js';
|
|
6
|
+
// Tools hidden by default to stay within IDE 100-tool limits.
|
|
7
|
+
// Set TRACE_ALL_TOOLS=true to expose everything.
|
|
8
|
+
const NON_CORE_TOOLS = new Set([
|
|
9
|
+
// Node.js debugger (7) - browser-focused IDEs rarely need these
|
|
10
|
+
'trace_connect_node_debugger', 'trace_node_list_scripts', 'trace_node_get_source',
|
|
11
|
+
'trace_node_set_breakpoint', 'trace_node_get_variables', 'trace_node_step_debugger',
|
|
12
|
+
'trace_node_evaluate',
|
|
13
|
+
// Cache Storage (3) - niche
|
|
14
|
+
'trace_get_caches', 'trace_get_cache_contents', 'trace_delete_cache',
|
|
15
|
+
// Code/filesystem (11) - IDE already provides these
|
|
16
|
+
'trace_read_file', 'trace_search_code', 'trace_get_file_tree', 'trace_get_project_info',
|
|
17
|
+
'trace_get_error_context', 'trace_git_blame', 'trace_git_recent_changes',
|
|
18
|
+
'trace_get_imports', 'trace_find_usages', 'trace_get_related_files', 'trace_get_env_vars',
|
|
19
|
+
// Advanced debugger (6) - power-user only
|
|
20
|
+
'trace_capture_execution_state', 'trace_get_execution_history',
|
|
21
|
+
'trace_watch_for_changes', 'trace_check_changes', 'trace_trace_variable_origin',
|
|
22
|
+
'trace_debug_and_analyze',
|
|
23
|
+
// Advanced source (5) - power-user only
|
|
24
|
+
'trace_blackbox_script', 'trace_list_blackbox_patterns', 'trace_list_original_sources',
|
|
25
|
+
'trace_get_original_location', 'trace_map_call_stack',
|
|
26
|
+
// Advanced DOM (3)
|
|
27
|
+
'trace_find_ghost_blockers', 'trace_find_hidden_interactive_elements', 'trace_inspect_mode',
|
|
28
|
+
// Tracing (3) - niche
|
|
29
|
+
'trace_start_trace', 'trace_get_trace_spans', 'trace_get_error_rate',
|
|
30
|
+
// Timeline deep-dive (2) - niche
|
|
31
|
+
'trace_diff_from_snapshot', 'trace_get_events_in_window',
|
|
32
|
+
]);
|
|
33
|
+
import { zodToJsonSchema as _zodToJsonSchema } from 'zod-to-json-schema';
|
|
6
34
|
// Schema conversion helper
|
|
7
35
|
function zodToJsonSchema(schema) {
|
|
36
|
+
const jsonSchema = _zodToJsonSchema(schema, { target: 'openApi3' });
|
|
37
|
+
// MCP expects { type: 'object', properties: {...}, required: [...] }
|
|
8
38
|
return {
|
|
9
39
|
type: 'object',
|
|
10
|
-
properties: {},
|
|
40
|
+
properties: jsonSchema.properties || {},
|
|
41
|
+
required: jsonSchema.required || [],
|
|
11
42
|
};
|
|
12
43
|
}
|
|
13
44
|
export class TraceMcpServer {
|
|
14
45
|
server;
|
|
15
46
|
trace;
|
|
47
|
+
connected = false;
|
|
16
48
|
constructor() {
|
|
17
49
|
this.server = new Server({
|
|
18
50
|
name: 'trace-mcp-server',
|
|
@@ -38,17 +70,20 @@ export class TraceMcpServer {
|
|
|
38
70
|
// 1. TOOLS
|
|
39
71
|
// ============================================
|
|
40
72
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
41
|
-
const
|
|
73
|
+
const allTools = process.env.TRACE_ALL_TOOLS === 'true';
|
|
74
|
+
const definedTools = Object.entries(TOOLS)
|
|
75
|
+
.filter(([name]) => allTools || !NON_CORE_TOOLS.has(name))
|
|
76
|
+
.map(([name, def]) => ({
|
|
42
77
|
name,
|
|
43
78
|
description: def.description,
|
|
44
79
|
inputSchema: zodToJsonSchema(def.schema),
|
|
45
80
|
}));
|
|
46
81
|
const definedNames = new Set(Object.keys(TOOLS));
|
|
47
82
|
const distinctAllTools = new Set(ALL_TOOLS);
|
|
48
|
-
// Add dynamic tools
|
|
83
|
+
// Add dynamic tools (fallback for any SDK tools not in TOOLS)
|
|
49
84
|
for (const tool of distinctAllTools) {
|
|
50
85
|
const mcpName = `trace_${tool}`;
|
|
51
|
-
if (!definedNames.has(mcpName)) {
|
|
86
|
+
if (!definedNames.has(mcpName) && (allTools || !NON_CORE_TOOLS.has(mcpName))) {
|
|
52
87
|
definedTools.push({
|
|
53
88
|
name: mcpName,
|
|
54
89
|
description: `Execute ${tool} (Dynamic Tool)`,
|
|
@@ -72,7 +107,7 @@ export class TraceMcpServer {
|
|
|
72
107
|
}
|
|
73
108
|
if (!method)
|
|
74
109
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
75
|
-
const isConnected =
|
|
110
|
+
const isConnected = this.connected;
|
|
76
111
|
try {
|
|
77
112
|
let result;
|
|
78
113
|
// 1. Handle Connect Tool
|
|
@@ -80,6 +115,7 @@ export class TraceMcpServer {
|
|
|
80
115
|
const url = String(request.params.arguments?.url);
|
|
81
116
|
// Re-init trace if headless mode changes? For now just connect.
|
|
82
117
|
await this.trace.connect(url);
|
|
118
|
+
this.connected = true;
|
|
83
119
|
return { content: [{ type: 'text', text: `Connected to ${url}` }] };
|
|
84
120
|
}
|
|
85
121
|
if (!isConnected) {
|
|
@@ -92,17 +128,17 @@ export class TraceMcpServer {
|
|
|
92
128
|
return { content: [{ type: 'text', text: 'Error: TRACE_API_KEY is required for AI features.' }], isError: true };
|
|
93
129
|
}
|
|
94
130
|
const prompt = String(request.params.arguments?.prompt || request.params.arguments?.q || 'Analyze page');
|
|
95
|
-
// Delegate to SDK's
|
|
96
|
-
const
|
|
131
|
+
// Delegate to SDK's query which runs AI-powered deep debug
|
|
132
|
+
const debugResult = await this.trace.query(prompt);
|
|
97
133
|
// Return the conclusion and steps
|
|
98
134
|
return {
|
|
99
135
|
content: [
|
|
100
|
-
{ type: 'text', text: `###
|
|
136
|
+
{ type: 'text', text: `### Summary\n${debugResult.analysis.summary}\n\n### Steps Taken\n${debugResult.execution.results.map((r) => `- ${r.tool}: ${r.success ? 'Success' : 'Failed'}`).join('\n')}` }
|
|
101
137
|
]
|
|
102
138
|
};
|
|
103
139
|
}
|
|
104
140
|
// 3. Handle Standard SDK Tools
|
|
105
|
-
result = await this.trace.
|
|
141
|
+
result = await this.trace.tool(toolDef.method, (request.params.arguments || {}));
|
|
106
142
|
return {
|
|
107
143
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
108
144
|
};
|
|
@@ -161,12 +197,12 @@ export class TraceMcpServer {
|
|
|
161
197
|
});
|
|
162
198
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
163
199
|
const uri = request.params.uri;
|
|
164
|
-
if (!this.
|
|
200
|
+
if (!this.connected) {
|
|
165
201
|
throw new Error('Not connected. Call trace_connect first.');
|
|
166
202
|
}
|
|
167
203
|
// --- Console ---
|
|
168
204
|
if (uri === 'trace://console/errors') {
|
|
169
|
-
const logs = await this.trace.
|
|
205
|
+
const logs = await this.trace.tool('get_console_errors', {});
|
|
170
206
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs, null, 2) }] };
|
|
171
207
|
}
|
|
172
208
|
if (uri === 'trace://console/logs') {
|
|
@@ -175,21 +211,21 @@ export class TraceMcpServer {
|
|
|
175
211
|
// For now, let's return the summary as it contains counts.
|
|
176
212
|
// Ideally SDK should expose 'get_all_logs'.
|
|
177
213
|
// Fallback: use get_console_errors
|
|
178
|
-
const logs = await this.trace.
|
|
214
|
+
const logs = await this.trace.tool('get_console_summary', {});
|
|
179
215
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs, null, 2) }] };
|
|
180
216
|
}
|
|
181
217
|
// --- Network ---
|
|
182
218
|
if (uri === 'trace://network/failed') {
|
|
183
|
-
const reqs = await this.trace.
|
|
219
|
+
const reqs = await this.trace.tool('get_network_failed', {});
|
|
184
220
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reqs, null, 2) }] };
|
|
185
221
|
}
|
|
186
222
|
if (uri === 'trace://network/all') {
|
|
187
|
-
const reqs = await this.trace.
|
|
223
|
+
const reqs = await this.trace.tool('get_network_summary', {});
|
|
188
224
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reqs, null, 2) }] };
|
|
189
225
|
}
|
|
190
226
|
// --- DOM ---
|
|
191
227
|
if (uri === 'trace://dom/tree') {
|
|
192
|
-
const tree = await this.trace.
|
|
228
|
+
const tree = await this.trace.tool('get_dom_tree', { depth: 2 });
|
|
193
229
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(tree, null, 2) }] };
|
|
194
230
|
}
|
|
195
231
|
// --- Screenshot ---
|
package/dist/tools.js
CHANGED
|
@@ -100,8 +100,37 @@ export const TOOLS = {
|
|
|
100
100
|
requestId: z.string().describe('The ID of the request to replay'),
|
|
101
101
|
}),
|
|
102
102
|
},
|
|
103
|
+
trace_set_network_throttling: {
|
|
104
|
+
method: 'set_network_throttling',
|
|
105
|
+
description: 'Set network throttling to a preset (slow3G, fast3G, offline).',
|
|
106
|
+
schema: z.object({
|
|
107
|
+
preset: z.string().describe('Preset: slow3G, fast3G, or offline'),
|
|
108
|
+
}),
|
|
109
|
+
},
|
|
110
|
+
trace_set_custom_network_throttling: {
|
|
111
|
+
method: 'set_custom_network_throttling',
|
|
112
|
+
description: 'Set custom network throttling parameters.',
|
|
113
|
+
schema: z.object({
|
|
114
|
+
downloadThroughput: z.number().describe('Download speed in bytes/sec'),
|
|
115
|
+
uploadThroughput: z.number().describe('Upload speed in bytes/sec'),
|
|
116
|
+
latency: z.number().describe('Latency in ms'),
|
|
117
|
+
}),
|
|
118
|
+
},
|
|
119
|
+
trace_disable_network_throttling: {
|
|
120
|
+
method: 'disable_network_throttling',
|
|
121
|
+
description: 'Disable network throttling.',
|
|
122
|
+
schema: z.object({}),
|
|
123
|
+
},
|
|
124
|
+
// ============================================
|
|
125
|
+
// DIAGNOSTIC (1)
|
|
103
126
|
// ============================================
|
|
104
|
-
|
|
127
|
+
trace_full_page_diagnostic: {
|
|
128
|
+
method: 'full_page_diagnostic',
|
|
129
|
+
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.',
|
|
130
|
+
schema: z.object({}),
|
|
131
|
+
},
|
|
132
|
+
// ============================================
|
|
133
|
+
// DOM & UI TOOLS (22)
|
|
105
134
|
// ============================================
|
|
106
135
|
trace_inspect_element: {
|
|
107
136
|
method: 'inspect_element',
|
|
@@ -117,6 +146,13 @@ export const TOOLS = {
|
|
|
117
146
|
selector: z.string().describe('CSS selector'),
|
|
118
147
|
}),
|
|
119
148
|
},
|
|
149
|
+
trace_find_by_text: {
|
|
150
|
+
method: 'find_by_text',
|
|
151
|
+
description: 'Find elements containing specific visible text. Best for finding buttons, links, labels by their text content.',
|
|
152
|
+
schema: z.object({
|
|
153
|
+
text: z.string().describe('Text to search for (case-insensitive, partial match)'),
|
|
154
|
+
}),
|
|
155
|
+
},
|
|
120
156
|
trace_get_element_styles: {
|
|
121
157
|
method: 'get_element_styles',
|
|
122
158
|
description: 'Get full computed style object.',
|
|
@@ -210,8 +246,28 @@ export const TOOLS = {
|
|
|
210
246
|
selector: z.string().describe('CSS selector'),
|
|
211
247
|
}),
|
|
212
248
|
},
|
|
249
|
+
trace_check_layout_issues: {
|
|
250
|
+
method: 'check_layout_issues',
|
|
251
|
+
description: 'Find ALL layout issues: overlapping elements, hidden buttons/links, text overflow. Use when debugging visual bugs.',
|
|
252
|
+
schema: z.object({}),
|
|
253
|
+
},
|
|
254
|
+
trace_find_overlapping_elements: {
|
|
255
|
+
method: 'find_overlapping_elements',
|
|
256
|
+
description: 'Find elements that overlap and block each other (like invisible overlays covering buttons).',
|
|
257
|
+
schema: z.object({}),
|
|
258
|
+
},
|
|
259
|
+
trace_find_ghost_blockers: {
|
|
260
|
+
method: 'find_ghost_blockers',
|
|
261
|
+
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?"',
|
|
262
|
+
schema: z.object({}),
|
|
263
|
+
},
|
|
264
|
+
trace_find_hidden_interactive_elements: {
|
|
265
|
+
method: 'find_hidden_interactive_elements',
|
|
266
|
+
description: 'Find buttons, links, inputs that are hidden but should be visible.',
|
|
267
|
+
schema: z.object({}),
|
|
268
|
+
},
|
|
213
269
|
// ============================================
|
|
214
|
-
// DEBUGGER TOOLS (
|
|
270
|
+
// DEBUGGER TOOLS (27)
|
|
215
271
|
// ============================================
|
|
216
272
|
trace_get_debugger_state: {
|
|
217
273
|
method: 'get_debugger_state',
|
|
@@ -260,6 +316,15 @@ export const TOOLS = {
|
|
|
260
316
|
expression: z.string().describe('JS code to evaluate'),
|
|
261
317
|
}),
|
|
262
318
|
},
|
|
319
|
+
trace_set_conditional_breakpoint: {
|
|
320
|
+
method: 'set_conditional_breakpoint',
|
|
321
|
+
description: 'Set a breakpoint that only triggers when a condition is met.',
|
|
322
|
+
schema: z.object({
|
|
323
|
+
url: z.string().describe('Script URL pattern'),
|
|
324
|
+
line: z.number().describe('Line number'),
|
|
325
|
+
condition: z.string().describe('JS condition expression'),
|
|
326
|
+
}),
|
|
327
|
+
},
|
|
263
328
|
trace_set_exception_breakpoint: {
|
|
264
329
|
method: 'set_exception_breakpoint',
|
|
265
330
|
description: 'Pause on All or Uncaught exceptions.',
|
|
@@ -339,7 +404,7 @@ export const TOOLS = {
|
|
|
339
404
|
description: 'Continue execution.',
|
|
340
405
|
schema: z.object({}),
|
|
341
406
|
},
|
|
342
|
-
|
|
407
|
+
trace_set_dom_breakpoint_by_selector: {
|
|
343
408
|
method: 'set_dom_breakpoint_by_selector',
|
|
344
409
|
description: 'Break on node modification.',
|
|
345
410
|
schema: z.object({
|
|
@@ -361,8 +426,21 @@ export const TOOLS = {
|
|
|
361
426
|
enable: z.boolean().describe('True to enable, false to disable'),
|
|
362
427
|
}),
|
|
363
428
|
},
|
|
429
|
+
trace_get_variable_preview: {
|
|
430
|
+
method: 'get_variable_preview',
|
|
431
|
+
description: 'Get current variable values while debugging.',
|
|
432
|
+
schema: z.object({}),
|
|
433
|
+
},
|
|
434
|
+
trace_debug_and_analyze: {
|
|
435
|
+
method: 'debug_and_analyze',
|
|
436
|
+
description: 'Combined tool: set breakpoint, capture state, and analyze.',
|
|
437
|
+
schema: z.object({
|
|
438
|
+
url: z.string().describe('Script URL'),
|
|
439
|
+
line: z.number().describe('Line number'),
|
|
440
|
+
}),
|
|
441
|
+
},
|
|
364
442
|
// ============================================
|
|
365
|
-
// SOURCE TOOLS (
|
|
443
|
+
// SOURCE TOOLS (11)
|
|
366
444
|
// ============================================
|
|
367
445
|
trace_list_scripts: {
|
|
368
446
|
method: 'list_scripts',
|
|
@@ -414,6 +492,23 @@ export const TOOLS = {
|
|
|
414
492
|
scriptId: z.string().describe('Script ID'),
|
|
415
493
|
}),
|
|
416
494
|
},
|
|
495
|
+
trace_map_call_stack: {
|
|
496
|
+
method: 'map_call_stack',
|
|
497
|
+
description: 'Map the current call stack to original source locations using source maps.',
|
|
498
|
+
schema: z.object({}),
|
|
499
|
+
},
|
|
500
|
+
trace_list_blackbox_patterns: {
|
|
501
|
+
method: 'list_blackbox_patterns',
|
|
502
|
+
description: 'List current blackboxed script patterns (ignored during debugging).',
|
|
503
|
+
schema: z.object({}),
|
|
504
|
+
},
|
|
505
|
+
trace_analyze_fetch_origin: {
|
|
506
|
+
method: 'analyze_fetch_origin',
|
|
507
|
+
description: 'Analyze where a fetch/XHR request originated in the code.',
|
|
508
|
+
schema: z.object({
|
|
509
|
+
url: z.string().describe('URL pattern to match'),
|
|
510
|
+
}),
|
|
511
|
+
},
|
|
417
512
|
// ============================================
|
|
418
513
|
// PERFORMANCE TOOLS (4)
|
|
419
514
|
// ============================================
|
|
@@ -583,6 +678,20 @@ export const TOOLS = {
|
|
|
583
678
|
cacheName: z.string(),
|
|
584
679
|
}),
|
|
585
680
|
},
|
|
681
|
+
trace_clear_indexeddb: {
|
|
682
|
+
method: 'clear_indexeddb',
|
|
683
|
+
description: 'Clear an IndexedDB database.',
|
|
684
|
+
schema: z.object({
|
|
685
|
+
database: z.string().describe('Database name'),
|
|
686
|
+
}),
|
|
687
|
+
},
|
|
688
|
+
trace_delete_cache: {
|
|
689
|
+
method: 'delete_cache',
|
|
690
|
+
description: 'Delete a cache from Cache Storage.',
|
|
691
|
+
schema: z.object({
|
|
692
|
+
cacheName: z.string().describe('Cache name'),
|
|
693
|
+
}),
|
|
694
|
+
},
|
|
586
695
|
// ============================================
|
|
587
696
|
// SECURITY TOOLS (3)
|
|
588
697
|
// ============================================
|
|
@@ -741,6 +850,89 @@ export const TOOLS = {
|
|
|
741
850
|
}),
|
|
742
851
|
},
|
|
743
852
|
// ============================================
|
|
853
|
+
// FULLSTACK DEBUGGING TOOLS (4)
|
|
854
|
+
// ============================================
|
|
855
|
+
trace_test_api_endpoint: {
|
|
856
|
+
method: 'test_api_endpoint',
|
|
857
|
+
description: 'Make a request to the configured backend API. WARNING: may bypass CORS/auth restrictions.',
|
|
858
|
+
schema: z.object({
|
|
859
|
+
path: z.string().describe('API path (e.g., /users, /products/123)'),
|
|
860
|
+
method: z.enum(['GET', 'POST', 'PUT', 'DELETE', 'PATCH']).describe('HTTP method'),
|
|
861
|
+
body: z.any().optional().describe('Request body for POST/PUT/PATCH'),
|
|
862
|
+
headers: z.record(z.string()).optional().describe('Additional headers'),
|
|
863
|
+
}),
|
|
864
|
+
},
|
|
865
|
+
trace_replay_failed_api: {
|
|
866
|
+
method: 'replay_failed_api',
|
|
867
|
+
description: 'Replay a failed API request from the network log with optional modifications.',
|
|
868
|
+
schema: z.object({
|
|
869
|
+
requestId: z.string().describe('ID of the failed request to replay'),
|
|
870
|
+
modifications: z.any().optional().describe('Optional modifications: { headers, body, queryParams }'),
|
|
871
|
+
}),
|
|
872
|
+
},
|
|
873
|
+
trace_trace_request_flow: {
|
|
874
|
+
method: 'trace_request_flow',
|
|
875
|
+
description: 'Trace a request from frontend -> backend -> database. Correlates network request with API logs and DB queries.',
|
|
876
|
+
schema: z.object({
|
|
877
|
+
url: z.string().describe('URL pattern of the request to trace'),
|
|
878
|
+
correlationId: z.string().optional().describe('Optional X-Request-ID or correlation header'),
|
|
879
|
+
}),
|
|
880
|
+
},
|
|
881
|
+
trace_get_fullstack_config: {
|
|
882
|
+
method: 'get_fullstack_config',
|
|
883
|
+
description: 'Get the current full-stack debugging configuration (backend URL, DB type).',
|
|
884
|
+
schema: z.object({}),
|
|
885
|
+
},
|
|
886
|
+
// ============================================
|
|
887
|
+
// NODE.JS SERVER DEBUGGING TOOLS (7)
|
|
888
|
+
// ============================================
|
|
889
|
+
trace_connect_node_debugger: {
|
|
890
|
+
method: 'connect_node_debugger',
|
|
891
|
+
description: 'Connect to Node.js inspector (requires server started with --inspect). Auto-discovers on port 9229.',
|
|
892
|
+
schema: z.object({
|
|
893
|
+
port: z.number().optional().describe('Inspector port (default: 9229)'),
|
|
894
|
+
}),
|
|
895
|
+
},
|
|
896
|
+
trace_node_list_scripts: {
|
|
897
|
+
method: 'node_list_scripts',
|
|
898
|
+
description: 'List all server-side scripts loaded by Node.js (excludes node_modules and builtins).',
|
|
899
|
+
schema: z.object({}),
|
|
900
|
+
},
|
|
901
|
+
trace_node_get_source: {
|
|
902
|
+
method: 'node_get_source',
|
|
903
|
+
description: 'Read the source code of a server-side Node.js script.',
|
|
904
|
+
schema: z.object({
|
|
905
|
+
scriptId: z.string().describe('Script ID from node_list_scripts'),
|
|
906
|
+
}),
|
|
907
|
+
},
|
|
908
|
+
trace_node_set_breakpoint: {
|
|
909
|
+
method: 'node_set_breakpoint',
|
|
910
|
+
description: 'Set a breakpoint in server-side Node.js code.',
|
|
911
|
+
schema: z.object({
|
|
912
|
+
url: z.string().describe('Script URL or filename pattern'),
|
|
913
|
+
line: z.number().describe('Line number'),
|
|
914
|
+
}),
|
|
915
|
+
},
|
|
916
|
+
trace_node_get_variables: {
|
|
917
|
+
method: 'node_get_variables',
|
|
918
|
+
description: 'Get server-side variable values when Node.js debugger is paused.',
|
|
919
|
+
schema: z.object({}),
|
|
920
|
+
},
|
|
921
|
+
trace_node_step_debugger: {
|
|
922
|
+
method: 'node_step_debugger',
|
|
923
|
+
description: 'Step through server-side Node.js code.',
|
|
924
|
+
schema: z.object({
|
|
925
|
+
action: z.enum(['stepOver', 'stepInto', 'stepOut', 'resume']).describe('Step action'),
|
|
926
|
+
}),
|
|
927
|
+
},
|
|
928
|
+
trace_node_evaluate: {
|
|
929
|
+
method: 'node_evaluate',
|
|
930
|
+
description: 'Evaluate an expression in the Node.js server context.',
|
|
931
|
+
schema: z.object({
|
|
932
|
+
expression: z.string().describe('JavaScript expression to evaluate in Node.js'),
|
|
933
|
+
}),
|
|
934
|
+
},
|
|
935
|
+
// ============================================
|
|
744
936
|
// CONNECTION & META (2)
|
|
745
937
|
// ============================================
|
|
746
938
|
trace_connect: {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@probebrowser/trace-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "Trace MCP - Bridge between AI Agents and Trace",
|
|
5
5
|
"homepage": "https://trace.probebrowser.com/",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
"scripts": {
|
|
12
12
|
"build": "tsc",
|
|
13
13
|
"start": "node dist/index.js",
|
|
14
|
+
"test": "vitest run",
|
|
15
|
+
"test:watch": "vitest",
|
|
16
|
+
"test:coverage": "vitest run --coverage",
|
|
14
17
|
"prepublishOnly": "npm run build"
|
|
15
18
|
},
|
|
16
19
|
"dependencies": {
|
|
@@ -20,11 +23,13 @@
|
|
|
20
23
|
"@types/express": "^5.0.6",
|
|
21
24
|
"cors": "^2.8.6",
|
|
22
25
|
"express": "^5.2.1",
|
|
23
|
-
"zod": "^3.23.0"
|
|
26
|
+
"zod": "^3.23.0",
|
|
27
|
+
"zod-to-json-schema": "^3.25.1"
|
|
24
28
|
},
|
|
25
29
|
"devDependencies": {
|
|
26
30
|
"@types/node": "^20.12.0",
|
|
27
|
-
"typescript": "^5.4.0"
|
|
31
|
+
"typescript": "^5.4.0",
|
|
32
|
+
"vitest": "^1.6.0"
|
|
28
33
|
},
|
|
29
34
|
"publishConfig": {
|
|
30
35
|
"access": "public"
|
package/src/server.ts
CHANGED
|
@@ -11,19 +11,52 @@ import {
|
|
|
11
11
|
} from '@modelcontextprotocol/sdk/types.js';
|
|
12
12
|
import { Trace, ALL_TOOLS } from '@probebrowser/sdk';
|
|
13
13
|
import { TOOLS } from './tools.js';
|
|
14
|
+
|
|
15
|
+
// Tools hidden by default to stay within IDE 100-tool limits.
|
|
16
|
+
// Set TRACE_ALL_TOOLS=true to expose everything.
|
|
17
|
+
const NON_CORE_TOOLS = new Set([
|
|
18
|
+
// Node.js debugger (7) - browser-focused IDEs rarely need these
|
|
19
|
+
'trace_connect_node_debugger', 'trace_node_list_scripts', 'trace_node_get_source',
|
|
20
|
+
'trace_node_set_breakpoint', 'trace_node_get_variables', 'trace_node_step_debugger',
|
|
21
|
+
'trace_node_evaluate',
|
|
22
|
+
// Cache Storage (3) - niche
|
|
23
|
+
'trace_get_caches', 'trace_get_cache_contents', 'trace_delete_cache',
|
|
24
|
+
// Code/filesystem (11) - IDE already provides these
|
|
25
|
+
'trace_read_file', 'trace_search_code', 'trace_get_file_tree', 'trace_get_project_info',
|
|
26
|
+
'trace_get_error_context', 'trace_git_blame', 'trace_git_recent_changes',
|
|
27
|
+
'trace_get_imports', 'trace_find_usages', 'trace_get_related_files', 'trace_get_env_vars',
|
|
28
|
+
// Advanced debugger (6) - power-user only
|
|
29
|
+
'trace_capture_execution_state', 'trace_get_execution_history',
|
|
30
|
+
'trace_watch_for_changes', 'trace_check_changes', 'trace_trace_variable_origin',
|
|
31
|
+
'trace_debug_and_analyze',
|
|
32
|
+
// Advanced source (5) - power-user only
|
|
33
|
+
'trace_blackbox_script', 'trace_list_blackbox_patterns', 'trace_list_original_sources',
|
|
34
|
+
'trace_get_original_location', 'trace_map_call_stack',
|
|
35
|
+
// Advanced DOM (3)
|
|
36
|
+
'trace_find_ghost_blockers', 'trace_find_hidden_interactive_elements', 'trace_inspect_mode',
|
|
37
|
+
// Tracing (3) - niche
|
|
38
|
+
'trace_start_trace', 'trace_get_trace_spans', 'trace_get_error_rate',
|
|
39
|
+
// Timeline deep-dive (2) - niche
|
|
40
|
+
'trace_diff_from_snapshot', 'trace_get_events_in_window',
|
|
41
|
+
]);
|
|
14
42
|
import { z } from 'zod';
|
|
43
|
+
import { zodToJsonSchema as _zodToJsonSchema } from 'zod-to-json-schema';
|
|
15
44
|
|
|
16
45
|
// Schema conversion helper
|
|
17
46
|
function zodToJsonSchema(schema: z.ZodType<any>): any {
|
|
47
|
+
const jsonSchema = _zodToJsonSchema(schema, { target: 'openApi3' }) as any;
|
|
48
|
+
// MCP expects { type: 'object', properties: {...}, required: [...] }
|
|
18
49
|
return {
|
|
19
50
|
type: 'object',
|
|
20
|
-
properties: {},
|
|
51
|
+
properties: jsonSchema.properties || {},
|
|
52
|
+
required: jsonSchema.required || [],
|
|
21
53
|
};
|
|
22
54
|
}
|
|
23
55
|
|
|
24
56
|
export class TraceMcpServer {
|
|
25
57
|
private server: Server;
|
|
26
58
|
private trace: Trace;
|
|
59
|
+
private connected = false;
|
|
27
60
|
|
|
28
61
|
constructor() {
|
|
29
62
|
this.server = new Server(
|
|
@@ -57,19 +90,23 @@ export class TraceMcpServer {
|
|
|
57
90
|
// 1. TOOLS
|
|
58
91
|
// ============================================
|
|
59
92
|
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
const allTools = process.env.TRACE_ALL_TOOLS === 'true';
|
|
94
|
+
|
|
95
|
+
const definedTools: Tool[] = Object.entries(TOOLS)
|
|
96
|
+
.filter(([name]) => allTools || !NON_CORE_TOOLS.has(name))
|
|
97
|
+
.map(([name, def]) => ({
|
|
98
|
+
name,
|
|
99
|
+
description: def.description,
|
|
100
|
+
inputSchema: zodToJsonSchema(def.schema),
|
|
101
|
+
}));
|
|
65
102
|
|
|
66
103
|
const definedNames = new Set(Object.keys(TOOLS));
|
|
67
104
|
const distinctAllTools = new Set(ALL_TOOLS);
|
|
68
105
|
|
|
69
|
-
// Add dynamic tools
|
|
106
|
+
// Add dynamic tools (fallback for any SDK tools not in TOOLS)
|
|
70
107
|
for (const tool of distinctAllTools) {
|
|
71
108
|
const mcpName = `trace_${tool}`;
|
|
72
|
-
if (!definedNames.has(mcpName)) {
|
|
109
|
+
if (!definedNames.has(mcpName) && (allTools || !NON_CORE_TOOLS.has(mcpName))) {
|
|
73
110
|
definedTools.push({
|
|
74
111
|
name: mcpName,
|
|
75
112
|
description: `Execute ${tool} (Dynamic Tool)`,
|
|
@@ -97,7 +134,7 @@ export class TraceMcpServer {
|
|
|
97
134
|
|
|
98
135
|
if (!method) throw new Error(`Unknown tool: ${toolName}`);
|
|
99
136
|
|
|
100
|
-
const isConnected =
|
|
137
|
+
const isConnected = this.connected;
|
|
101
138
|
|
|
102
139
|
try {
|
|
103
140
|
let result;
|
|
@@ -107,6 +144,7 @@ export class TraceMcpServer {
|
|
|
107
144
|
const url = String(request.params.arguments?.url);
|
|
108
145
|
// Re-init trace if headless mode changes? For now just connect.
|
|
109
146
|
await this.trace.connect(url);
|
|
147
|
+
this.connected = true;
|
|
110
148
|
return { content: [{ type: 'text', text: `Connected to ${url}` }] };
|
|
111
149
|
}
|
|
112
150
|
|
|
@@ -123,19 +161,19 @@ export class TraceMcpServer {
|
|
|
123
161
|
|
|
124
162
|
const prompt = String(request.params.arguments?.prompt || request.params.arguments?.q || 'Analyze page');
|
|
125
163
|
|
|
126
|
-
// Delegate to SDK's
|
|
127
|
-
const
|
|
164
|
+
// Delegate to SDK's query which runs AI-powered deep debug
|
|
165
|
+
const debugResult = await this.trace.query(prompt);
|
|
128
166
|
|
|
129
167
|
// Return the conclusion and steps
|
|
130
168
|
return {
|
|
131
169
|
content: [
|
|
132
|
-
{ type: 'text', text: `###
|
|
170
|
+
{ type: 'text', text: `### Summary\n${debugResult.analysis.summary}\n\n### Steps Taken\n${debugResult.execution.results.map((r: any) => `- ${r.tool}: ${r.success ? 'Success' : 'Failed'}`).join('\n')}` }
|
|
133
171
|
]
|
|
134
172
|
};
|
|
135
173
|
}
|
|
136
174
|
|
|
137
175
|
// 3. Handle Standard SDK Tools
|
|
138
|
-
result = await this.trace.
|
|
176
|
+
result = await this.trace.tool(toolDef.method, (request.params.arguments || {}) as Record<string, unknown>);
|
|
139
177
|
|
|
140
178
|
return {
|
|
141
179
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
@@ -196,13 +234,13 @@ export class TraceMcpServer {
|
|
|
196
234
|
|
|
197
235
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
198
236
|
const uri = request.params.uri;
|
|
199
|
-
if (!this.
|
|
237
|
+
if (!this.connected) {
|
|
200
238
|
throw new Error('Not connected. Call trace_connect first.');
|
|
201
239
|
}
|
|
202
240
|
|
|
203
241
|
// --- Console ---
|
|
204
242
|
if (uri === 'trace://console/errors') {
|
|
205
|
-
const logs = await this.trace.
|
|
243
|
+
const logs = await this.trace.tool('get_console_errors', {});
|
|
206
244
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs, null, 2) }] };
|
|
207
245
|
}
|
|
208
246
|
if (uri === 'trace://console/logs') {
|
|
@@ -211,23 +249,23 @@ export class TraceMcpServer {
|
|
|
211
249
|
// For now, let's return the summary as it contains counts.
|
|
212
250
|
// Ideally SDK should expose 'get_all_logs'.
|
|
213
251
|
// Fallback: use get_console_errors
|
|
214
|
-
const logs = await this.trace.
|
|
252
|
+
const logs = await this.trace.tool('get_console_summary', {});
|
|
215
253
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs, null, 2) }] };
|
|
216
254
|
}
|
|
217
255
|
|
|
218
256
|
// --- Network ---
|
|
219
257
|
if (uri === 'trace://network/failed') {
|
|
220
|
-
const reqs = await this.trace.
|
|
258
|
+
const reqs = await this.trace.tool('get_network_failed', {});
|
|
221
259
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reqs, null, 2) }] };
|
|
222
260
|
}
|
|
223
261
|
if (uri === 'trace://network/all') {
|
|
224
|
-
const reqs = await this.trace.
|
|
262
|
+
const reqs = await this.trace.tool('get_network_summary', {});
|
|
225
263
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reqs, null, 2) }] };
|
|
226
264
|
}
|
|
227
265
|
|
|
228
266
|
// --- DOM ---
|
|
229
267
|
if (uri === 'trace://dom/tree') {
|
|
230
|
-
const tree = await this.trace.
|
|
268
|
+
const tree = await this.trace.tool('get_dom_tree', { depth: 2 });
|
|
231
269
|
return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(tree, null, 2) }] };
|
|
232
270
|
}
|
|
233
271
|
|