@probebrowser/mcp-server 1.2.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/Dockerfile ADDED
@@ -0,0 +1,59 @@
1
+ FROM node:20-slim
2
+
3
+ WORKDIR /app
4
+
5
+ # Install build dependencies if needed (e.g. for native modules)
6
+ # RUN apt-get update && apt-get install -y python3 make g++
7
+
8
+ # Copy workspace configuration
9
+ COPY package.json .
10
+
11
+ # Copy package manifests to install dependencies first (caching)
12
+ COPY packages/sdk/package.json packages/sdk/
13
+ COPY packages/mcp-server/package.json packages/mcp-server/
14
+
15
+ # Install SDK dependencies
16
+ WORKDIR /app/packages/sdk
17
+ RUN npm install
18
+
19
+ # Install MCP Server dependencies
20
+ WORKDIR /app/packages/mcp-server
21
+ RUN npm install
22
+
23
+ # Copy source code
24
+ COPY packages/sdk /app/packages/sdk
25
+ COPY packages/mcp-server /app/packages/mcp-server
26
+
27
+ # Build SDK
28
+ WORKDIR /app/packages/sdk
29
+ RUN npm run build
30
+
31
+ # Build MCP Server
32
+ WORKDIR /app/packages/mcp-server
33
+ # Ensure local SDK is used
34
+ RUN npm install ../sdk
35
+ RUN npm run build
36
+
37
+ # Cleanup dev dependencies (optional optimization)
38
+ # RUN npm prune --production
39
+
40
+ # Runtime Configuration
41
+ ENV NODE_ENV=production
42
+ ENV TRANSPORT=sse
43
+ ENV PORT=3000
44
+ # IMPORTANT: Trace needs a browser.
45
+ # In Docker, you typically connect to a remote browser (browserless.io)
46
+ # or install Chromium. For this base image, we assume remote connect
47
+ # or headless with Puppeteer installing Chrome (which needs libs).
48
+ # Installing Chrome dependencies:
49
+ RUN apt-get update && apt-get install -y wget gnupg \
50
+ && wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
51
+ && sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
52
+ && apt-get update \
53
+ && apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
54
+ --no-install-recommends \
55
+ && rm -rf /var/lib/apt/lists/*
56
+
57
+ EXPOSE 3000
58
+
59
+ CMD ["node", "dist/index.js"]
package/dist/index.js ADDED
@@ -0,0 +1,29 @@
1
+ import express from 'express';
2
+ import cors from 'cors';
3
+ import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
4
+ import { TraceMcpServer } from './server.js';
5
+ async function main() {
6
+ const transportType = process.env.TRANSPORT || 'stdio';
7
+ if (transportType === 'sse') {
8
+ const app = express();
9
+ app.use(cors());
10
+ // Create MCP Server
11
+ const traceServer = new TraceMcpServer();
12
+ const transport = new SSEServerTransport('/messages', app); // Type cast might be needed if SDK types mismatch express
13
+ await traceServer.connect(transport);
14
+ const port = process.env.PORT || 3000;
15
+ app.listen(port, () => {
16
+ console.error(`Trace MCP Server (SSE) running on port ${port}`);
17
+ console.error(`Endpoint: http://localhost:${port}/sse`);
18
+ });
19
+ }
20
+ else {
21
+ // Stdio Mode
22
+ const traceServer = new TraceMcpServer();
23
+ await traceServer.start();
24
+ }
25
+ }
26
+ main().catch((error) => {
27
+ console.error('Fatal error:', error);
28
+ process.exit(1);
29
+ });
@@ -0,0 +1 @@
1
+ export {};
package/dist/server.js ADDED
@@ -0,0 +1,282 @@
1
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
2
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
+ import { Trace, ALL_TOOLS } from '@probebrowser/sdk';
5
+ import { TOOLS } from './tools.js';
6
+ // Schema conversion helper
7
+ function zodToJsonSchema(schema) {
8
+ return {
9
+ type: 'object',
10
+ properties: {}, // Simplified
11
+ };
12
+ }
13
+ export class TraceMcpServer {
14
+ server;
15
+ trace;
16
+ constructor() {
17
+ this.server = new Server({
18
+ name: 'trace-mcp-server',
19
+ version: '1.2.0',
20
+ }, {
21
+ capabilities: {
22
+ tools: {},
23
+ resources: {},
24
+ prompts: {},
25
+ },
26
+ });
27
+ // Initialize Trace SDK with Env Config
28
+ const headless = process.env.TRACE_HEADLESS !== 'false'; // Default to true
29
+ const verbose = process.env.TRACE_VERBOSE === 'true';
30
+ this.trace = new Trace({
31
+ headless,
32
+ verbose,
33
+ });
34
+ this.setupHandlers();
35
+ }
36
+ setupHandlers() {
37
+ // ============================================
38
+ // 1. TOOLS
39
+ // ============================================
40
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
41
+ const definedTools = Object.entries(TOOLS).map(([name, def]) => ({
42
+ name,
43
+ description: def.description,
44
+ inputSchema: zodToJsonSchema(def.schema),
45
+ }));
46
+ const definedNames = new Set(Object.keys(TOOLS));
47
+ const distinctAllTools = new Set(ALL_TOOLS);
48
+ // Add dynamic tools
49
+ for (const tool of distinctAllTools) {
50
+ const mcpName = `trace_${tool}`;
51
+ if (!definedNames.has(mcpName)) {
52
+ definedTools.push({
53
+ name: mcpName,
54
+ description: `Execute ${tool} (Dynamic Tool)`,
55
+ inputSchema: { type: 'object', properties: {} },
56
+ });
57
+ }
58
+ }
59
+ return { tools: definedTools };
60
+ });
61
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
62
+ const toolName = request.params.name;
63
+ let toolDef = TOOLS[toolName];
64
+ let method = toolDef?.method;
65
+ // Dynamic Tool Logic
66
+ if (!toolDef && toolName.startsWith('trace_')) {
67
+ const rawName = toolName.replace('trace_', '');
68
+ if (ALL_TOOLS.includes(rawName)) {
69
+ method = rawName;
70
+ toolDef = { method: rawName };
71
+ }
72
+ }
73
+ if (!method)
74
+ throw new Error(`Unknown tool: ${toolName}`);
75
+ const isConnected = !!this.trace.getCurrentUrl();
76
+ try {
77
+ let result;
78
+ // 1. Handle Connect Tool
79
+ if (toolDef.method === 'connect') {
80
+ const url = String(request.params.arguments?.url);
81
+ // Re-init trace if headless mode changes? For now just connect.
82
+ await this.trace.connect(url);
83
+ return { content: [{ type: 'text', text: `Connected to ${url}` }] };
84
+ }
85
+ if (!isConnected) {
86
+ return { content: [{ type: 'text', text: 'Error: No active page. Call trace_connect first.' }], isError: true };
87
+ }
88
+ // 2. Handle Azure AI Agent (Unified Deep Debug)
89
+ if (toolDef.method === 'deep_debug' || toolDef.method === 'trace_p') {
90
+ // Check for API Key
91
+ if (!process.env.TRACE_API_KEY) {
92
+ return { content: [{ type: 'text', text: 'Error: TRACE_API_KEY is required for AI features.' }], isError: true };
93
+ }
94
+ const prompt = String(request.params.arguments?.prompt || request.params.arguments?.q || 'Analyze page');
95
+ // Delegate to SDK's queryDeep which calls Azure /v1/agent-step
96
+ const analysis = await this.trace.queryDeep(prompt);
97
+ // Return the conclusion and steps
98
+ return {
99
+ content: [
100
+ { type: 'text', text: `### Conclusion\n${analysis.conclusion}\n\n### Steps Taken\n${analysis.steps.map(s => `- ${s.tool}: ${s.success ? 'Success' : 'Failed'}`).join('\n')}` }
101
+ ]
102
+ };
103
+ }
104
+ // 3. Handle Standard SDK Tools
105
+ result = await this.trace.executeTool(toolDef.method, request.params.arguments);
106
+ return {
107
+ content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
108
+ };
109
+ }
110
+ catch (error) {
111
+ return {
112
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
113
+ isError: true,
114
+ };
115
+ }
116
+ });
117
+ // ============================================
118
+ // 2. RESOURCES
119
+ // ============================================
120
+ this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
121
+ return {
122
+ resources: [
123
+ {
124
+ uri: 'trace://console/errors',
125
+ name: 'Console Errors',
126
+ mimeType: 'application/json',
127
+ description: 'List of all console errors and exceptions',
128
+ },
129
+ {
130
+ uri: 'trace://console/logs',
131
+ name: 'All Console Logs',
132
+ mimeType: 'application/json',
133
+ description: 'Full console history (info, warn, error)',
134
+ },
135
+ {
136
+ uri: 'trace://network/failed',
137
+ name: 'Failed Network Requests',
138
+ mimeType: 'application/json',
139
+ description: 'List of failed network requests (4xx, 5xx)',
140
+ },
141
+ {
142
+ uri: 'trace://network/all',
143
+ name: 'All Network Traffic',
144
+ mimeType: 'application/json',
145
+ description: 'Recent network requests summary',
146
+ },
147
+ {
148
+ uri: 'trace://dom/tree',
149
+ name: 'DOM Tree',
150
+ mimeType: 'application/json',
151
+ description: 'Simplified DOM structure',
152
+ },
153
+ {
154
+ uri: 'trace://screenshot',
155
+ name: 'Page Screenshot',
156
+ mimeType: 'image/png',
157
+ description: 'Current page screenshot',
158
+ },
159
+ ],
160
+ };
161
+ });
162
+ this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
163
+ const uri = request.params.uri;
164
+ if (!this.trace.getCurrentUrl()) {
165
+ throw new Error('Not connected. Call trace_connect first.');
166
+ }
167
+ // --- Console ---
168
+ if (uri === 'trace://console/errors') {
169
+ const logs = await this.trace.executeTool('get_console_errors', {});
170
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs, null, 2) }] };
171
+ }
172
+ if (uri === 'trace://console/logs') {
173
+ // We don't have get_console_logs explicitly in ALL_TOOLS list in previous step,
174
+ // but we can use get_console_summary or implement a getter.
175
+ // For now, let's return the summary as it contains counts.
176
+ // Ideally SDK should expose 'get_all_logs'.
177
+ // Fallback: use get_console_errors
178
+ const logs = await this.trace.executeTool('get_console_summary', {});
179
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(logs, null, 2) }] };
180
+ }
181
+ // --- Network ---
182
+ if (uri === 'trace://network/failed') {
183
+ const reqs = await this.trace.executeTool('get_network_failed', {});
184
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reqs, null, 2) }] };
185
+ }
186
+ if (uri === 'trace://network/all') {
187
+ const reqs = await this.trace.executeTool('get_network_summary', {});
188
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(reqs, null, 2) }] };
189
+ }
190
+ // --- DOM ---
191
+ if (uri === 'trace://dom/tree') {
192
+ const tree = await this.trace.executeTool('get_dom_tree', { depth: 2 });
193
+ return { contents: [{ uri, mimeType: 'application/json', text: JSON.stringify(tree, null, 2) }] };
194
+ }
195
+ // --- Screenshot ---
196
+ if (uri === 'trace://screenshot') {
197
+ // Try to capture snapshot via capture_execution_state which might have screenshot
198
+ // Or just return a placeholder if SDK doesn't support raw image output via tool interface easily
199
+ // For now, returning text explainer
200
+ return { contents: [{ uri, mimeType: 'text/plain', text: "Screenshot resource requires Trace SDK v1.4+" }] };
201
+ }
202
+ throw new Error(`Resource not found: ${uri}`);
203
+ });
204
+ // ============================================
205
+ // 3. PROMPTS
206
+ // ============================================
207
+ this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
208
+ return {
209
+ prompts: [
210
+ {
211
+ name: 'debug_error',
212
+ description: 'Analyze the most recent error and suggest a fix',
213
+ arguments: [],
214
+ },
215
+ {
216
+ name: 'audit_performance',
217
+ description: 'Check metrics and run a quick profile',
218
+ arguments: [],
219
+ },
220
+ {
221
+ name: 'verify_security',
222
+ description: 'Check security headers and SSL status',
223
+ arguments: [],
224
+ },
225
+ ],
226
+ };
227
+ });
228
+ this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
229
+ const promptName = request.params.name;
230
+ if (promptName === 'debug_error') {
231
+ return {
232
+ messages: [
233
+ {
234
+ role: 'user',
235
+ content: {
236
+ type: 'text',
237
+ text: 'Please check the console for errors, analyze the most critical one, and suggest a code fix.',
238
+ },
239
+ },
240
+ ],
241
+ };
242
+ }
243
+ if (promptName === 'audit_performance') {
244
+ return {
245
+ messages: [
246
+ {
247
+ role: 'user',
248
+ content: {
249
+ type: 'text',
250
+ text: 'Please capture performance metrics, take a heap snapshot, and tell me if there are any obvious bottlenecks.',
251
+ },
252
+ },
253
+ ],
254
+ };
255
+ }
256
+ if (promptName === 'verify_security') {
257
+ return {
258
+ messages: [
259
+ {
260
+ role: 'user',
261
+ content: {
262
+ type: 'text',
263
+ text: 'Please check the security info, verify HTTPS, and look for mixed content warnings.',
264
+ },
265
+ },
266
+ ],
267
+ };
268
+ }
269
+ throw new Error('Prompt not found');
270
+ });
271
+ }
272
+ async connect(transport) {
273
+ await this.server.connect(transport);
274
+ console.error('Trace MCP Server connected');
275
+ }
276
+ async start() {
277
+ // Default to Stdio for backward compatibility
278
+ const transport = new StdioServerTransport();
279
+ await this.server.connect(transport);
280
+ console.error('Trace MCP Server running on stdio');
281
+ }
282
+ }