@probelabs/probe 0.6.0-rc166 → 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.
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Vercel AI SDK Engine - wraps existing ProbeAgent logic
3
+ * This maintains full backward compatibility
4
+ */
5
+
6
+ import { streamText } from 'ai';
7
+
8
+ /**
9
+ * Create a Vercel AI SDK engine
10
+ * @param {Object} agent - The ProbeAgent instance
11
+ * @returns {Object} Engine interface
12
+ */
13
+ export function createVercelEngine(agent) {
14
+ return {
15
+ /**
16
+ * Query the model using existing Vercel AI SDK implementation
17
+ * @param {string} prompt - The prompt to send
18
+ * @param {Object} options - Additional options
19
+ * @returns {AsyncIterable} Response stream
20
+ */
21
+ async *query(prompt, options = {}) {
22
+ // Build messages array
23
+ const messages = [
24
+ ...agent.history,
25
+ { role: 'user', content: prompt }
26
+ ];
27
+
28
+ // Use existing streamText with retry and fallback
29
+ const result = await agent.streamTextWithRetryAndFallback({
30
+ model: agent.provider(agent.model),
31
+ messages,
32
+ maxTokens: options.maxTokens || agent.maxResponseTokens,
33
+ temperature: options.temperature,
34
+ tools: options.tools,
35
+ toolChoice: options.toolChoice,
36
+ experimental_telemetry: options.telemetry
37
+ });
38
+
39
+ // Stream the response
40
+ for await (const chunk of result.textStream) {
41
+ yield { type: 'text', content: chunk };
42
+ }
43
+
44
+ // Handle tool calls if any
45
+ if (result.toolCalls && result.toolCalls.length > 0) {
46
+ yield { type: 'tool_calls', toolCalls: result.toolCalls };
47
+ }
48
+
49
+ // Handle finish reason
50
+ if (result.finishReason) {
51
+ yield { type: 'finish', reason: result.finishReason };
52
+ }
53
+ },
54
+
55
+ /**
56
+ * Optional cleanup
57
+ */
58
+ async close() {
59
+ // Nothing to cleanup for Vercel AI
60
+ }
61
+ };
62
+ }
@@ -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
+ }