@probelabs/probe 0.6.0-rc165 → 0.6.0-rc167
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/README.md +39 -0
- package/build/agent/ProbeAgent.js +316 -7
- package/build/agent/engines/enhanced-claude-code.js +595 -0
- package/build/agent/engines/enhanced-vercel.js +83 -0
- package/build/agent/engines/vercel.js +62 -0
- package/build/agent/index.js +1335 -166
- package/build/agent/mcp/built-in-server.js +464 -0
- package/build/agent/tools.js +25 -0
- package/cjs/agent/ProbeAgent.cjs +9023 -6703
- package/cjs/index.cjs +9074 -6754
- package/package.json +2 -1
- package/src/agent/ProbeAgent.js +316 -7
- package/src/agent/engines/enhanced-claude-code.js +595 -0
- package/src/agent/engines/enhanced-vercel.js +83 -0
- package/src/agent/engines/vercel.js +62 -0
- package/src/agent/mcp/built-in-server.js +464 -0
- package/src/agent/tools.js +25 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in MCP Server for Probe
|
|
3
|
+
* Runs in the same process as ProbeAgent, eliminating spawn overhead
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { createServer } from 'http';
|
|
7
|
+
import { EventEmitter } from 'events';
|
|
8
|
+
import { Server as MCPServer } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import {
|
|
10
|
+
CallToolRequestSchema,
|
|
11
|
+
ListToolsRequestSchema
|
|
12
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Built-in MCP Server that runs in-process
|
|
16
|
+
*/
|
|
17
|
+
export class BuiltInMCPServer extends EventEmitter {
|
|
18
|
+
constructor(agent, options = {}) {
|
|
19
|
+
super();
|
|
20
|
+
this.agent = agent;
|
|
21
|
+
this.port = options.port || 0; // 0 = ephemeral port
|
|
22
|
+
this.host = options.host || '127.0.0.1';
|
|
23
|
+
this.httpServer = null;
|
|
24
|
+
this.mcpServer = null;
|
|
25
|
+
this.connections = new Set();
|
|
26
|
+
this.debug = options.debug || false;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Start the built-in MCP server
|
|
31
|
+
*/
|
|
32
|
+
async start() {
|
|
33
|
+
// Create HTTP server for SSE/HTTP transport
|
|
34
|
+
this.httpServer = createServer();
|
|
35
|
+
|
|
36
|
+
// Handle SSE connections
|
|
37
|
+
this.httpServer.on('request', (req, res) => {
|
|
38
|
+
this.handleRequest(req, res);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Create MCP server
|
|
42
|
+
this.mcpServer = new MCPServer({
|
|
43
|
+
name: 'probe-builtin',
|
|
44
|
+
version: '1.0.0'
|
|
45
|
+
}, {
|
|
46
|
+
capabilities: {
|
|
47
|
+
tools: {}
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Register MCP handlers
|
|
52
|
+
this.registerHandlers();
|
|
53
|
+
|
|
54
|
+
// Start listening on ephemeral port
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
this.httpServer.listen(this.port, this.host, () => {
|
|
57
|
+
const address = this.httpServer.address();
|
|
58
|
+
this.port = address.port;
|
|
59
|
+
|
|
60
|
+
if (this.debug) {
|
|
61
|
+
console.log(`[MCP] Built-in server started at http://${this.host}:${this.port}`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
this.emit('ready', { host: this.host, port: this.port });
|
|
65
|
+
resolve({ host: this.host, port: this.port });
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
this.httpServer.on('error', reject);
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Handle HTTP requests (SSE and JSON-RPC)
|
|
74
|
+
*/
|
|
75
|
+
handleRequest(req, res) {
|
|
76
|
+
const { method, url } = req;
|
|
77
|
+
|
|
78
|
+
// CORS headers for local development
|
|
79
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
80
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
81
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
82
|
+
|
|
83
|
+
if (method === 'OPTIONS') {
|
|
84
|
+
res.writeHead(204);
|
|
85
|
+
res.end();
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Handle SSE endpoint
|
|
90
|
+
if (url === '/sse' && method === 'GET') {
|
|
91
|
+
this.handleSSE(req, res);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Handle JSON-RPC endpoint
|
|
96
|
+
if (url === '/rpc' && method === 'POST') {
|
|
97
|
+
this.handleJSONRPC(req, res);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Handle stdio-like protocol over HTTP
|
|
102
|
+
if (url === '/mcp' && method === 'POST') {
|
|
103
|
+
this.handleMCPProtocol(req, res);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Health check
|
|
108
|
+
if (url === '/health') {
|
|
109
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
110
|
+
res.end(JSON.stringify({
|
|
111
|
+
status: 'ok',
|
|
112
|
+
server: 'probe-builtin-mcp',
|
|
113
|
+
tools: this.getToolCount()
|
|
114
|
+
}));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// 404 for unknown endpoints
|
|
119
|
+
res.writeHead(404);
|
|
120
|
+
res.end('Not Found');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle Server-Sent Events connection
|
|
125
|
+
*/
|
|
126
|
+
handleSSE(req, res) {
|
|
127
|
+
res.writeHead(200, {
|
|
128
|
+
'Content-Type': 'text/event-stream',
|
|
129
|
+
'Cache-Control': 'no-cache',
|
|
130
|
+
'Connection': 'keep-alive'
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// Send initial connection event
|
|
134
|
+
res.write('event: connected\n');
|
|
135
|
+
res.write(`data: ${JSON.stringify({ type: 'connected', server: 'probe-builtin' })}\n\n`);
|
|
136
|
+
|
|
137
|
+
// Store connection
|
|
138
|
+
this.connections.add(res);
|
|
139
|
+
|
|
140
|
+
// Clean up on close
|
|
141
|
+
req.on('close', () => {
|
|
142
|
+
this.connections.delete(res);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Handle JSON-RPC requests
|
|
148
|
+
*/
|
|
149
|
+
async handleJSONRPC(req, res) {
|
|
150
|
+
let body = '';
|
|
151
|
+
|
|
152
|
+
req.on('data', chunk => {
|
|
153
|
+
body += chunk.toString();
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
req.on('end', async () => {
|
|
157
|
+
try {
|
|
158
|
+
const request = JSON.parse(body);
|
|
159
|
+
const response = await this.processRequest(request);
|
|
160
|
+
|
|
161
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
162
|
+
res.end(JSON.stringify(response));
|
|
163
|
+
} catch (error) {
|
|
164
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
165
|
+
res.end(JSON.stringify({
|
|
166
|
+
jsonrpc: '2.0',
|
|
167
|
+
error: {
|
|
168
|
+
code: -32700,
|
|
169
|
+
message: 'Parse error',
|
|
170
|
+
data: error.message
|
|
171
|
+
},
|
|
172
|
+
id: null
|
|
173
|
+
}));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Handle MCP protocol messages
|
|
180
|
+
*/
|
|
181
|
+
async handleMCPProtocol(req, res) {
|
|
182
|
+
let body = '';
|
|
183
|
+
|
|
184
|
+
req.on('data', chunk => {
|
|
185
|
+
body += chunk.toString();
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
req.on('end', async () => {
|
|
189
|
+
try {
|
|
190
|
+
const message = JSON.parse(body);
|
|
191
|
+
|
|
192
|
+
// Process through MCP server handlers
|
|
193
|
+
let response;
|
|
194
|
+
|
|
195
|
+
if (message.method === 'tools/list') {
|
|
196
|
+
response = await this.handleListTools();
|
|
197
|
+
} else if (message.method === 'tools/call') {
|
|
198
|
+
response = await this.handleCallTool(message.params);
|
|
199
|
+
} else {
|
|
200
|
+
response = {
|
|
201
|
+
error: {
|
|
202
|
+
code: -32601,
|
|
203
|
+
message: 'Method not found'
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
209
|
+
res.end(JSON.stringify(response));
|
|
210
|
+
} catch (error) {
|
|
211
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
212
|
+
res.end(JSON.stringify({
|
|
213
|
+
error: {
|
|
214
|
+
code: -32603,
|
|
215
|
+
message: 'Internal error',
|
|
216
|
+
data: error.message
|
|
217
|
+
}
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Process JSON-RPC request
|
|
225
|
+
*/
|
|
226
|
+
async processRequest(request) {
|
|
227
|
+
const { jsonrpc, method, params, id } = request;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
let result;
|
|
231
|
+
|
|
232
|
+
switch (method) {
|
|
233
|
+
case 'tools/list':
|
|
234
|
+
result = await this.handleListTools();
|
|
235
|
+
break;
|
|
236
|
+
|
|
237
|
+
case 'tools/call':
|
|
238
|
+
result = await this.handleCallTool(params);
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
default:
|
|
242
|
+
return {
|
|
243
|
+
jsonrpc: '2.0',
|
|
244
|
+
error: {
|
|
245
|
+
code: -32601,
|
|
246
|
+
message: 'Method not found'
|
|
247
|
+
},
|
|
248
|
+
id
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return {
|
|
253
|
+
jsonrpc: '2.0',
|
|
254
|
+
result,
|
|
255
|
+
id
|
|
256
|
+
};
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return {
|
|
259
|
+
jsonrpc: '2.0',
|
|
260
|
+
error: {
|
|
261
|
+
code: -32603,
|
|
262
|
+
message: 'Internal error',
|
|
263
|
+
data: error.message
|
|
264
|
+
},
|
|
265
|
+
id
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Register MCP protocol handlers
|
|
272
|
+
*/
|
|
273
|
+
registerHandlers() {
|
|
274
|
+
// Handle list tools request
|
|
275
|
+
this.mcpServer.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
276
|
+
return this.handleListTools();
|
|
277
|
+
});
|
|
278
|
+
|
|
279
|
+
// Handle tool execution
|
|
280
|
+
this.mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
281
|
+
return this.handleCallTool(request.params);
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Handle list tools request
|
|
287
|
+
*/
|
|
288
|
+
async handleListTools() {
|
|
289
|
+
const tools = [];
|
|
290
|
+
|
|
291
|
+
// Get tools from agent
|
|
292
|
+
if (this.agent && this.agent.allowedTools) {
|
|
293
|
+
const toolDefs = {
|
|
294
|
+
search: {
|
|
295
|
+
description: 'Search for code patterns using semantic search',
|
|
296
|
+
inputSchema: {
|
|
297
|
+
type: 'object',
|
|
298
|
+
properties: {
|
|
299
|
+
query: { type: 'string', description: 'Search query' },
|
|
300
|
+
path: { type: 'string', description: 'Directory to search', default: '.' },
|
|
301
|
+
maxResults: { type: 'integer', default: 10 }
|
|
302
|
+
},
|
|
303
|
+
required: ['query']
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
extract: {
|
|
307
|
+
description: 'Extract code from specific file location',
|
|
308
|
+
inputSchema: {
|
|
309
|
+
type: 'object',
|
|
310
|
+
properties: {
|
|
311
|
+
path: { type: 'string', description: 'File path with optional line number' }
|
|
312
|
+
},
|
|
313
|
+
required: ['path']
|
|
314
|
+
}
|
|
315
|
+
},
|
|
316
|
+
listFiles: {
|
|
317
|
+
description: 'List files in a directory',
|
|
318
|
+
inputSchema: {
|
|
319
|
+
type: 'object',
|
|
320
|
+
properties: {
|
|
321
|
+
path: { type: 'string', description: 'Directory path' },
|
|
322
|
+
pattern: { type: 'string', description: 'File pattern' }
|
|
323
|
+
},
|
|
324
|
+
required: ['path']
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
searchFiles: {
|
|
328
|
+
description: 'Search for files by name pattern',
|
|
329
|
+
inputSchema: {
|
|
330
|
+
type: 'object',
|
|
331
|
+
properties: {
|
|
332
|
+
pattern: { type: 'string', description: 'File name pattern' },
|
|
333
|
+
path: { type: 'string', description: 'Directory to search' }
|
|
334
|
+
},
|
|
335
|
+
required: ['pattern']
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
query: {
|
|
339
|
+
description: 'Query code using AST patterns',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
query: { type: 'string', description: 'AST query' },
|
|
344
|
+
path: { type: 'string', description: 'Directory to search' }
|
|
345
|
+
},
|
|
346
|
+
required: ['query']
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
};
|
|
350
|
+
|
|
351
|
+
for (const [name, def] of Object.entries(toolDefs)) {
|
|
352
|
+
if (this.agent.allowedTools.isEnabled(name)) {
|
|
353
|
+
tools.push({
|
|
354
|
+
name: `mcp__probe__${name}`,
|
|
355
|
+
description: def.description,
|
|
356
|
+
inputSchema: def.inputSchema
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
return { tools };
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Handle tool execution
|
|
367
|
+
*/
|
|
368
|
+
async handleCallTool(params) {
|
|
369
|
+
const { name, arguments: args } = params;
|
|
370
|
+
|
|
371
|
+
// Extract tool name from MCP format
|
|
372
|
+
const toolName = name.replace('mcp__probe__', '');
|
|
373
|
+
|
|
374
|
+
// Check if tool is enabled
|
|
375
|
+
if (!this.agent.allowedTools.isEnabled(toolName)) {
|
|
376
|
+
throw new Error(`Tool ${name} is not enabled`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// Get tool implementation
|
|
380
|
+
const tool = this.agent.toolImplementations[toolName];
|
|
381
|
+
if (!tool) {
|
|
382
|
+
throw new Error(`Tool ${name} not found`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
try {
|
|
386
|
+
// Execute tool directly (no spawning!)
|
|
387
|
+
const result = await tool.execute(args);
|
|
388
|
+
|
|
389
|
+
return {
|
|
390
|
+
content: [{
|
|
391
|
+
type: 'text',
|
|
392
|
+
text: typeof result === 'string' ? result : JSON.stringify(result, null, 2)
|
|
393
|
+
}]
|
|
394
|
+
};
|
|
395
|
+
} catch (error) {
|
|
396
|
+
return {
|
|
397
|
+
content: [{
|
|
398
|
+
type: 'text',
|
|
399
|
+
text: `Error executing ${name}: ${error.message}`
|
|
400
|
+
}],
|
|
401
|
+
isError: true
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Get the number of available tools
|
|
408
|
+
*/
|
|
409
|
+
getToolCount() {
|
|
410
|
+
if (!this.agent || !this.agent.allowedTools) {
|
|
411
|
+
return 0;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const tools = ['search', 'extract', 'listFiles', 'searchFiles', 'query'];
|
|
415
|
+
return tools.filter(name => this.agent.allowedTools.isEnabled(name)).length;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Broadcast message to all SSE connections
|
|
420
|
+
*/
|
|
421
|
+
broadcast(event, data) {
|
|
422
|
+
const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
423
|
+
|
|
424
|
+
for (const connection of this.connections) {
|
|
425
|
+
connection.write(message);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Stop the server
|
|
431
|
+
*/
|
|
432
|
+
async stop() {
|
|
433
|
+
// Close all SSE connections
|
|
434
|
+
for (const connection of this.connections) {
|
|
435
|
+
connection.end();
|
|
436
|
+
}
|
|
437
|
+
this.connections.clear();
|
|
438
|
+
|
|
439
|
+
// Close HTTP server
|
|
440
|
+
if (this.httpServer) {
|
|
441
|
+
return new Promise((resolve) => {
|
|
442
|
+
this.httpServer.close(() => {
|
|
443
|
+
if (this.debug) {
|
|
444
|
+
console.log('[MCP] Built-in server stopped');
|
|
445
|
+
}
|
|
446
|
+
resolve();
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Get server configuration for MCP clients
|
|
454
|
+
*/
|
|
455
|
+
getConfig() {
|
|
456
|
+
return {
|
|
457
|
+
transport: 'http',
|
|
458
|
+
url: `http://${this.host}:${this.port}/mcp`,
|
|
459
|
+
// Alternative transports:
|
|
460
|
+
// sse: `http://${this.host}:${this.port}/sse`,
|
|
461
|
+
// rpc: `http://${this.host}:${this.port}/rpc`
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
}
|
package/build/agent/tools.js
CHANGED
|
@@ -154,6 +154,31 @@ User: Find all markdown files in the docs directory, but only at the top level.
|
|
|
154
154
|
</examples>
|
|
155
155
|
`;
|
|
156
156
|
|
|
157
|
+
// Define the readImage tool XML definition
|
|
158
|
+
export const readImageToolDefinition = `
|
|
159
|
+
## readImage
|
|
160
|
+
Description: Read and load an image file so it can be viewed by the AI. Use this when you need to analyze, describe, or work with image content. Images from user messages are automatically loaded, but use this tool to explicitly read images mentioned in tool outputs or when you need to examine specific image files.
|
|
161
|
+
|
|
162
|
+
Parameters:
|
|
163
|
+
- path: (required) The path to the image file to read. Supports png, jpg, jpeg, webp, bmp, and svg formats.
|
|
164
|
+
|
|
165
|
+
Usage Example:
|
|
166
|
+
|
|
167
|
+
<examples>
|
|
168
|
+
|
|
169
|
+
User: Can you describe what's in screenshot.png?
|
|
170
|
+
<readImage>
|
|
171
|
+
<path>screenshot.png</path>
|
|
172
|
+
</readImage>
|
|
173
|
+
|
|
174
|
+
User: Analyze the diagram in docs/architecture.svg
|
|
175
|
+
<readImage>
|
|
176
|
+
<path>docs/architecture.svg</path>
|
|
177
|
+
</readImage>
|
|
178
|
+
|
|
179
|
+
</examples>
|
|
180
|
+
`;
|
|
181
|
+
|
|
157
182
|
/**
|
|
158
183
|
* Enhanced XML parser that handles thinking tags and attempt_complete shorthand
|
|
159
184
|
* This function removes any <thinking></thinking> tags from the input string
|