@probelabs/probe 0.6.0-rc100
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 +583 -0
- package/bin/.gitkeep +0 -0
- package/bin/probe +158 -0
- package/bin/probe-binary +0 -0
- package/build/agent/ProbeAgent.d.ts +199 -0
- package/build/agent/ProbeAgent.js +1486 -0
- package/build/agent/acp/README.md +347 -0
- package/build/agent/acp/connection.js +237 -0
- package/build/agent/acp/connection.test.js +311 -0
- package/build/agent/acp/examples/simple-client.js +212 -0
- package/build/agent/acp/examples/tool-lifecycle.js +230 -0
- package/build/agent/acp/final-test.js +173 -0
- package/build/agent/acp/index.js +5 -0
- package/build/agent/acp/integration.test.js +385 -0
- package/build/agent/acp/manual-test.js +410 -0
- package/build/agent/acp/protocol-test.js +190 -0
- package/build/agent/acp/server.js +448 -0
- package/build/agent/acp/server.test.js +371 -0
- package/build/agent/acp/test-runner.js +216 -0
- package/build/agent/acp/test-utils/README.md +315 -0
- package/build/agent/acp/test-utils/acp-tester.js +484 -0
- package/build/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/build/agent/acp/tools.js +368 -0
- package/build/agent/acp/tools.test.js +334 -0
- package/build/agent/acp/types.js +218 -0
- package/build/agent/acp/types.test.js +327 -0
- package/build/agent/appTracer.js +360 -0
- package/build/agent/fileSpanExporter.js +169 -0
- package/build/agent/index.js +7426 -0
- package/build/agent/mcp/client.js +338 -0
- package/build/agent/mcp/config.js +313 -0
- package/build/agent/mcp/index.js +64 -0
- package/build/agent/mcp/xmlBridge.js +371 -0
- package/build/agent/mockProvider.js +53 -0
- package/build/agent/probeTool.js +257 -0
- package/build/agent/schemaUtils.js +1726 -0
- package/build/agent/simpleTelemetry.js +267 -0
- package/build/agent/telemetry.js +225 -0
- package/build/agent/tokenCounter.js +395 -0
- package/build/agent/tools.js +163 -0
- package/build/cli.js +49 -0
- package/build/delegate.js +267 -0
- package/build/directory-resolver.js +237 -0
- package/build/downloader.js +750 -0
- package/build/extract.js +149 -0
- package/build/index.js +70 -0
- package/build/mcp/index.js +514 -0
- package/build/mcp/index.ts +608 -0
- package/build/query.js +116 -0
- package/build/search.js +247 -0
- package/build/tools/common.js +410 -0
- package/build/tools/index.js +40 -0
- package/build/tools/langchain.js +88 -0
- package/build/tools/system-message.js +121 -0
- package/build/tools/vercel.js +271 -0
- package/build/utils/file-lister.js +193 -0
- package/build/utils.js +128 -0
- package/cjs/agent/ProbeAgent.cjs +5829 -0
- package/cjs/index.cjs +6217 -0
- package/cjs/package.json +3 -0
- package/index.d.ts +401 -0
- package/package.json +114 -0
- package/scripts/postinstall.js +172 -0
- package/src/agent/ProbeAgent.d.ts +199 -0
- package/src/agent/ProbeAgent.js +1486 -0
- package/src/agent/acp/README.md +347 -0
- package/src/agent/acp/connection.js +237 -0
- package/src/agent/acp/connection.test.js +311 -0
- package/src/agent/acp/examples/simple-client.js +212 -0
- package/src/agent/acp/examples/tool-lifecycle.js +230 -0
- package/src/agent/acp/final-test.js +173 -0
- package/src/agent/acp/index.js +5 -0
- package/src/agent/acp/integration.test.js +385 -0
- package/src/agent/acp/manual-test.js +410 -0
- package/src/agent/acp/protocol-test.js +190 -0
- package/src/agent/acp/server.js +448 -0
- package/src/agent/acp/server.test.js +371 -0
- package/src/agent/acp/test-runner.js +216 -0
- package/src/agent/acp/test-utils/README.md +315 -0
- package/src/agent/acp/test-utils/acp-tester.js +484 -0
- package/src/agent/acp/test-utils/mock-acp-client.js +434 -0
- package/src/agent/acp/tools.js +368 -0
- package/src/agent/acp/tools.test.js +334 -0
- package/src/agent/acp/types.js +218 -0
- package/src/agent/acp/types.test.js +327 -0
- package/src/agent/appTracer.js +360 -0
- package/src/agent/fileSpanExporter.js +169 -0
- package/src/agent/index.js +813 -0
- package/src/agent/mcp/client.js +338 -0
- package/src/agent/mcp/config.js +313 -0
- package/src/agent/mcp/index.js +64 -0
- package/src/agent/mcp/xmlBridge.js +371 -0
- package/src/agent/mockProvider.js +53 -0
- package/src/agent/probeTool.js +257 -0
- package/src/agent/schemaUtils.js +1726 -0
- package/src/agent/simpleTelemetry.js +267 -0
- package/src/agent/telemetry.js +225 -0
- package/src/agent/tokenCounter.js +395 -0
- package/src/agent/tools.js +163 -0
- package/src/cli.js +49 -0
- package/src/delegate.js +267 -0
- package/src/directory-resolver.js +237 -0
- package/src/downloader.js +750 -0
- package/src/extract.js +149 -0
- package/src/index.js +70 -0
- package/src/mcp/index.ts +608 -0
- package/src/query.js +116 -0
- package/src/search.js +247 -0
- package/src/tools/common.js +410 -0
- package/src/tools/index.js +40 -0
- package/src/tools/langchain.js +88 -0
- package/src/tools/system-message.js +121 -0
- package/src/tools/vercel.js +271 -0
- package/src/utils/file-lister.js +193 -0
- package/src/utils.js +128 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Enhanced MCP Client with support for all transport types
|
|
3
|
+
* Compatible with Claude's MCP configuration format
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
7
|
+
import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
|
|
8
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
9
|
+
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
10
|
+
import { loadMCPConfiguration, parseEnabledServers } from './config.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Create transport based on configuration
|
|
14
|
+
* @param {Object} serverConfig - Server configuration
|
|
15
|
+
* @returns {Object} Transport instance
|
|
16
|
+
*/
|
|
17
|
+
export function createTransport(serverConfig) {
|
|
18
|
+
const { transport, command, args, url, env } = serverConfig;
|
|
19
|
+
|
|
20
|
+
switch (transport) {
|
|
21
|
+
case 'stdio':
|
|
22
|
+
return new StdioClientTransport({
|
|
23
|
+
command,
|
|
24
|
+
args: args || [],
|
|
25
|
+
env: env ? { ...process.env, ...env } : undefined
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
case 'sse':
|
|
29
|
+
if (!url) {
|
|
30
|
+
throw new Error('SSE transport requires a URL');
|
|
31
|
+
}
|
|
32
|
+
return new SSEClientTransport(new URL(url));
|
|
33
|
+
|
|
34
|
+
case 'websocket':
|
|
35
|
+
case 'ws':
|
|
36
|
+
if (!url) {
|
|
37
|
+
throw new Error('WebSocket transport requires a URL');
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
return new WebSocketClientTransport(new URL(url));
|
|
41
|
+
} catch (error) {
|
|
42
|
+
throw new Error(`Invalid WebSocket URL: ${url}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case 'http':
|
|
46
|
+
case 'streamable':
|
|
47
|
+
// For HTTP, we'll use a custom implementation since the SDK
|
|
48
|
+
// doesn't provide a direct HTTP transport yet
|
|
49
|
+
if (!url) {
|
|
50
|
+
throw new Error('HTTP transport requires a URL');
|
|
51
|
+
}
|
|
52
|
+
// Return a custom HTTP transport wrapper
|
|
53
|
+
return createHttpTransport(url);
|
|
54
|
+
|
|
55
|
+
default:
|
|
56
|
+
throw new Error(`Unknown transport type: ${transport}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a custom HTTP transport wrapper
|
|
62
|
+
* This simulates MCP over HTTP REST endpoints
|
|
63
|
+
*/
|
|
64
|
+
function createHttpTransport(url) {
|
|
65
|
+
// This is a simplified HTTP transport
|
|
66
|
+
// In practice, you'd implement the full MCP protocol over HTTP
|
|
67
|
+
return {
|
|
68
|
+
async start() {
|
|
69
|
+
// Initialize HTTP connection
|
|
70
|
+
const response = await fetch(`${url}/initialize`, {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({
|
|
74
|
+
protocolVersion: '2024-11-05',
|
|
75
|
+
capabilities: {}
|
|
76
|
+
})
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
throw new Error(`HTTP initialization failed: ${response.statusText}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return response.json();
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async send(message) {
|
|
87
|
+
const response = await fetch(`${url}/message`, {
|
|
88
|
+
method: 'POST',
|
|
89
|
+
headers: { 'Content-Type': 'application/json' },
|
|
90
|
+
body: JSON.stringify(message)
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (!response.ok) {
|
|
94
|
+
throw new Error(`HTTP request failed: ${response.statusText}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return response.json();
|
|
98
|
+
},
|
|
99
|
+
|
|
100
|
+
async close() {
|
|
101
|
+
// Close HTTP connection
|
|
102
|
+
await fetch(`${url}/close`, {
|
|
103
|
+
method: 'POST'
|
|
104
|
+
}).catch(() => {
|
|
105
|
+
// Ignore close errors
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* MCP Client Manager - manages multiple MCP server connections
|
|
113
|
+
*/
|
|
114
|
+
export class MCPClientManager {
|
|
115
|
+
constructor(options = {}) {
|
|
116
|
+
this.clients = new Map();
|
|
117
|
+
this.tools = new Map();
|
|
118
|
+
this.debug = options.debug || process.env.DEBUG_MCP === '1';
|
|
119
|
+
this.config = null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Initialize MCP clients from configuration
|
|
124
|
+
* @param {Object} config - Optional configuration override
|
|
125
|
+
*/
|
|
126
|
+
async initialize(config = null) {
|
|
127
|
+
// Load configuration
|
|
128
|
+
this.config = config || loadMCPConfiguration();
|
|
129
|
+
const servers = parseEnabledServers(this.config);
|
|
130
|
+
|
|
131
|
+
if (this.debug) {
|
|
132
|
+
console.error(`[MCP] Found ${servers.length} enabled servers`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Connect to each enabled server
|
|
136
|
+
const connectionPromises = servers.map(server =>
|
|
137
|
+
this.connectToServer(server).catch(error => {
|
|
138
|
+
console.error(`[MCP] Failed to connect to ${server.name}:`, error.message);
|
|
139
|
+
return null;
|
|
140
|
+
})
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const results = await Promise.all(connectionPromises);
|
|
144
|
+
const connectedCount = results.filter(Boolean).length;
|
|
145
|
+
|
|
146
|
+
if (this.debug) {
|
|
147
|
+
console.error(`[MCP] Successfully connected to ${connectedCount}/${servers.length} servers`);
|
|
148
|
+
console.error(`[MCP] Total tools available: ${this.tools.size}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
connected: connectedCount,
|
|
153
|
+
total: servers.length,
|
|
154
|
+
tools: Array.from(this.tools.keys())
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Connect to a single MCP server
|
|
160
|
+
* @param {Object} serverConfig - Server configuration
|
|
161
|
+
*/
|
|
162
|
+
async connectToServer(serverConfig) {
|
|
163
|
+
const { name } = serverConfig;
|
|
164
|
+
|
|
165
|
+
try {
|
|
166
|
+
if (this.debug) {
|
|
167
|
+
console.error(`[MCP] Connecting to ${name} via ${serverConfig.transport}...`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Create transport
|
|
171
|
+
const transport = createTransport(serverConfig);
|
|
172
|
+
|
|
173
|
+
// Create client
|
|
174
|
+
const client = new Client(
|
|
175
|
+
{
|
|
176
|
+
name: `probe-client-${name}`,
|
|
177
|
+
version: '1.0.0'
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
capabilities: {}
|
|
181
|
+
}
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Connect
|
|
185
|
+
await client.connect(transport);
|
|
186
|
+
|
|
187
|
+
// Store client
|
|
188
|
+
this.clients.set(name, {
|
|
189
|
+
client,
|
|
190
|
+
transport,
|
|
191
|
+
config: serverConfig
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// Fetch and register tools
|
|
195
|
+
const toolsResponse = await client.listTools();
|
|
196
|
+
|
|
197
|
+
if (toolsResponse && toolsResponse.tools) {
|
|
198
|
+
for (const tool of toolsResponse.tools) {
|
|
199
|
+
// Add server prefix to avoid conflicts
|
|
200
|
+
const qualifiedName = `${name}_${tool.name}`;
|
|
201
|
+
this.tools.set(qualifiedName, {
|
|
202
|
+
...tool,
|
|
203
|
+
serverName: name,
|
|
204
|
+
originalName: tool.name
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
if (this.debug) {
|
|
208
|
+
console.error(`[MCP] Registered tool: ${qualifiedName}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (this.debug) {
|
|
214
|
+
console.error(`[MCP] Connected to ${name} with ${toolsResponse?.tools?.length || 0} tools`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error(`[MCP] Error connecting to ${name}:`, error.message);
|
|
220
|
+
return false;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Call a tool on its respective server
|
|
226
|
+
* @param {string} toolName - Qualified tool name (server_tool)
|
|
227
|
+
* @param {Object} args - Tool arguments
|
|
228
|
+
*/
|
|
229
|
+
async callTool(toolName, args) {
|
|
230
|
+
const tool = this.tools.get(toolName);
|
|
231
|
+
if (!tool) {
|
|
232
|
+
throw new Error(`Unknown tool: ${toolName}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const clientInfo = this.clients.get(tool.serverName);
|
|
236
|
+
if (!clientInfo) {
|
|
237
|
+
throw new Error(`Server ${tool.serverName} not connected`);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
try {
|
|
241
|
+
if (this.debug) {
|
|
242
|
+
console.error(`[MCP] Calling ${toolName} with args:`, args);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const result = await clientInfo.client.callTool({
|
|
246
|
+
name: tool.originalName,
|
|
247
|
+
arguments: args
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
return result;
|
|
251
|
+
} catch (error) {
|
|
252
|
+
console.error(`[MCP] Error calling tool ${toolName}:`, error);
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Get all available tools with their schemas
|
|
259
|
+
* @returns {Object} Map of tool name to tool definition
|
|
260
|
+
*/
|
|
261
|
+
getTools() {
|
|
262
|
+
const tools = {};
|
|
263
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
264
|
+
tools[name] = {
|
|
265
|
+
description: tool.description,
|
|
266
|
+
inputSchema: tool.inputSchema,
|
|
267
|
+
serverName: tool.serverName
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
return tools;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get tools formatted for Vercel AI SDK
|
|
275
|
+
* @returns {Object} Tools in Vercel AI SDK format
|
|
276
|
+
*/
|
|
277
|
+
getVercelTools() {
|
|
278
|
+
const tools = {};
|
|
279
|
+
|
|
280
|
+
for (const [name, tool] of this.tools.entries()) {
|
|
281
|
+
// Create a wrapper that calls the MCP tool
|
|
282
|
+
tools[name] = {
|
|
283
|
+
description: tool.description,
|
|
284
|
+
inputSchema: tool.inputSchema,
|
|
285
|
+
execute: async (args) => {
|
|
286
|
+
const result = await this.callTool(name, args);
|
|
287
|
+
// Extract text content from MCP response
|
|
288
|
+
if (result.content && result.content[0]) {
|
|
289
|
+
return result.content[0].text;
|
|
290
|
+
}
|
|
291
|
+
return JSON.stringify(result);
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
return tools;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Disconnect all clients
|
|
301
|
+
*/
|
|
302
|
+
async disconnect() {
|
|
303
|
+
const disconnectPromises = [];
|
|
304
|
+
|
|
305
|
+
for (const [name, clientInfo] of this.clients.entries()) {
|
|
306
|
+
disconnectPromises.push(
|
|
307
|
+
clientInfo.client.close()
|
|
308
|
+
.then(() => {
|
|
309
|
+
if (this.debug) {
|
|
310
|
+
console.error(`[MCP] Disconnected from ${name}`);
|
|
311
|
+
}
|
|
312
|
+
})
|
|
313
|
+
.catch(error => {
|
|
314
|
+
console.error(`[MCP] Error disconnecting from ${name}:`, error);
|
|
315
|
+
})
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await Promise.all(disconnectPromises);
|
|
320
|
+
this.clients.clear();
|
|
321
|
+
this.tools.clear();
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Create and initialize MCP client manager with default configuration
|
|
327
|
+
*/
|
|
328
|
+
export async function createMCPManager(options = {}) {
|
|
329
|
+
const manager = new MCPClientManager(options);
|
|
330
|
+
await manager.initialize();
|
|
331
|
+
return manager;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
export default {
|
|
335
|
+
MCPClientManager,
|
|
336
|
+
createMCPManager,
|
|
337
|
+
createTransport
|
|
338
|
+
};
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Configuration Manager
|
|
3
|
+
* Handles loading and parsing MCP server configurations similar to Claude
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
7
|
+
import { join, dirname } from 'path';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { fileURLToPath } from 'url';
|
|
10
|
+
|
|
11
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
12
|
+
const __dirname = dirname(__filename);
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default MCP configuration structure
|
|
16
|
+
*/
|
|
17
|
+
const DEFAULT_CONFIG = {
|
|
18
|
+
mcpServers: {
|
|
19
|
+
// Example probe server configuration
|
|
20
|
+
'probe-local': {
|
|
21
|
+
command: 'node',
|
|
22
|
+
args: [join(__dirname, '../../../examples/chat/mcpServer.js')],
|
|
23
|
+
transport: 'stdio',
|
|
24
|
+
enabled: false
|
|
25
|
+
},
|
|
26
|
+
'probe-npm': {
|
|
27
|
+
command: 'npx',
|
|
28
|
+
args: ['-y', '@probelabs/probe@latest', 'mcp'],
|
|
29
|
+
transport: 'stdio',
|
|
30
|
+
enabled: false
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Load MCP configuration from a specific file path
|
|
37
|
+
* @param {string} configPath - Path to MCP configuration file
|
|
38
|
+
* @returns {Object} Configuration object
|
|
39
|
+
* @throws {Error} If file doesn't exist or is invalid
|
|
40
|
+
*/
|
|
41
|
+
export function loadMCPConfigurationFromPath(configPath) {
|
|
42
|
+
if (!configPath) {
|
|
43
|
+
throw new Error('Config path is required');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!existsSync(configPath)) {
|
|
47
|
+
throw new Error(`MCP configuration file not found: ${configPath}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
const content = readFileSync(configPath, 'utf8');
|
|
52
|
+
const config = JSON.parse(content);
|
|
53
|
+
|
|
54
|
+
if (process.env.DEBUG === '1') {
|
|
55
|
+
console.error(`[MCP] Loaded configuration from: ${configPath}`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Merge with environment variable overrides
|
|
59
|
+
return mergeWithEnvironment(config);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
throw new Error(`Failed to parse MCP config from ${configPath}: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Load MCP configuration from various sources (DEPRECATED - use loadMCPConfigurationFromPath for explicit paths)
|
|
67
|
+
* Priority order:
|
|
68
|
+
* 1. Environment variable MCP_CONFIG_PATH
|
|
69
|
+
* 2. Local project .mcp/config.json
|
|
70
|
+
* 3. Home directory ~/.config/probe/mcp.json
|
|
71
|
+
* 4. Home directory ~/.mcp/config.json (Claude compatible)
|
|
72
|
+
* 5. Default configuration
|
|
73
|
+
* @deprecated Use loadMCPConfigurationFromPath for explicit path loading or pass config directly
|
|
74
|
+
*/
|
|
75
|
+
export function loadMCPConfiguration() {
|
|
76
|
+
const configPaths = [
|
|
77
|
+
// Environment variable path
|
|
78
|
+
process.env.MCP_CONFIG_PATH,
|
|
79
|
+
// Local project paths
|
|
80
|
+
join(process.cwd(), '.mcp', 'config.json'),
|
|
81
|
+
join(process.cwd(), 'mcp.config.json'),
|
|
82
|
+
// Home directory paths
|
|
83
|
+
join(homedir(), '.config', 'probe', 'mcp.json'),
|
|
84
|
+
join(homedir(), '.mcp', 'config.json'),
|
|
85
|
+
// Claude-style config location
|
|
86
|
+
join(homedir(), 'Library', 'Application Support', 'Claude', 'mcp_config.json'),
|
|
87
|
+
].filter(Boolean);
|
|
88
|
+
|
|
89
|
+
let config = null;
|
|
90
|
+
|
|
91
|
+
// Try to load configuration from paths
|
|
92
|
+
for (const configPath of configPaths) {
|
|
93
|
+
if (existsSync(configPath)) {
|
|
94
|
+
try {
|
|
95
|
+
const content = readFileSync(configPath, 'utf8');
|
|
96
|
+
config = JSON.parse(content);
|
|
97
|
+
if (process.env.DEBUG === '1') {
|
|
98
|
+
console.error(`[MCP] Loaded configuration from: ${configPath}`);
|
|
99
|
+
}
|
|
100
|
+
break;
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`[MCP] Failed to parse config from ${configPath}:`, error.message);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Merge with environment variable overrides
|
|
108
|
+
config = mergeWithEnvironment(config || DEFAULT_CONFIG);
|
|
109
|
+
|
|
110
|
+
return config;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Merge configuration with environment variables
|
|
115
|
+
* Supports:
|
|
116
|
+
* - MCP_SERVERS_<NAME>_COMMAND: Command for server
|
|
117
|
+
* - MCP_SERVERS_<NAME>_ARGS: Comma-separated args
|
|
118
|
+
* - MCP_SERVERS_<NAME>_TRANSPORT: Transport type
|
|
119
|
+
* - MCP_SERVERS_<NAME>_URL: URL for HTTP/WebSocket transports
|
|
120
|
+
* - MCP_SERVERS_<NAME>_ENABLED: Enable/disable server
|
|
121
|
+
*/
|
|
122
|
+
function mergeWithEnvironment(config) {
|
|
123
|
+
const serverPattern = /^MCP_SERVERS_([A-Z0-9_]+)_(.+)$/;
|
|
124
|
+
|
|
125
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
126
|
+
const match = key.match(serverPattern);
|
|
127
|
+
if (match) {
|
|
128
|
+
const [, serverName, property] = match;
|
|
129
|
+
const normalizedName = serverName.toLowerCase().replace(/_/g, '-');
|
|
130
|
+
|
|
131
|
+
if (!config.mcpServers) {
|
|
132
|
+
config.mcpServers = {};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (!config.mcpServers[normalizedName]) {
|
|
136
|
+
config.mcpServers[normalizedName] = {};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
switch (property) {
|
|
140
|
+
case 'COMMAND':
|
|
141
|
+
config.mcpServers[normalizedName].command = value;
|
|
142
|
+
break;
|
|
143
|
+
case 'ARGS':
|
|
144
|
+
config.mcpServers[normalizedName].args = value.split(',').map(arg => arg.trim());
|
|
145
|
+
break;
|
|
146
|
+
case 'TRANSPORT':
|
|
147
|
+
config.mcpServers[normalizedName].transport = value.toLowerCase();
|
|
148
|
+
break;
|
|
149
|
+
case 'URL':
|
|
150
|
+
config.mcpServers[normalizedName].url = value;
|
|
151
|
+
break;
|
|
152
|
+
case 'ENABLED':
|
|
153
|
+
config.mcpServers[normalizedName].enabled = value === 'true' || value === '1';
|
|
154
|
+
break;
|
|
155
|
+
case 'ENV':
|
|
156
|
+
// Support custom environment variables for the server
|
|
157
|
+
try {
|
|
158
|
+
config.mcpServers[normalizedName].env = JSON.parse(value);
|
|
159
|
+
} catch {
|
|
160
|
+
config.mcpServers[normalizedName].env = { [property]: value };
|
|
161
|
+
}
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return config;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Parse MCP server configuration to extract enabled servers
|
|
172
|
+
* @param {Object} config - Full MCP configuration
|
|
173
|
+
* @returns {Array} Array of server configurations ready for connection
|
|
174
|
+
*/
|
|
175
|
+
export function parseEnabledServers(config) {
|
|
176
|
+
const servers = [];
|
|
177
|
+
|
|
178
|
+
if (!config || !config.mcpServers) {
|
|
179
|
+
return servers;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
for (const [name, serverConfig] of Object.entries(config.mcpServers)) {
|
|
183
|
+
// Skip disabled servers
|
|
184
|
+
if (serverConfig.enabled === false) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const server = {
|
|
189
|
+
name,
|
|
190
|
+
...serverConfig
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
// Set default transport if not specified
|
|
194
|
+
if (!server.transport) {
|
|
195
|
+
if (server.url) {
|
|
196
|
+
// Infer transport from URL
|
|
197
|
+
if (server.url.startsWith('ws://') || server.url.startsWith('wss://')) {
|
|
198
|
+
server.transport = 'websocket';
|
|
199
|
+
} else if (server.url.includes('/sse')) {
|
|
200
|
+
server.transport = 'sse';
|
|
201
|
+
} else {
|
|
202
|
+
server.transport = 'http';
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
server.transport = 'stdio';
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Validate required fields based on transport
|
|
210
|
+
if (server.transport === 'stdio') {
|
|
211
|
+
if (!server.command) {
|
|
212
|
+
console.error(`[MCP] Server ${name} missing required 'command' for stdio transport`);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
} else if (['websocket', 'sse', 'http'].includes(server.transport)) {
|
|
216
|
+
if (!server.url) {
|
|
217
|
+
console.error(`[MCP] Server ${name} missing required 'url' for ${server.transport} transport`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
servers.push(server);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
return servers;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Create a sample MCP configuration file
|
|
230
|
+
*/
|
|
231
|
+
export function createSampleConfig() {
|
|
232
|
+
return {
|
|
233
|
+
mcpServers: {
|
|
234
|
+
'probe': {
|
|
235
|
+
command: 'npx',
|
|
236
|
+
args: ['-y', '@probelabs/probe@latest', 'mcp'],
|
|
237
|
+
transport: 'stdio',
|
|
238
|
+
enabled: true,
|
|
239
|
+
description: 'Probe code search MCP server'
|
|
240
|
+
},
|
|
241
|
+
'filesystem': {
|
|
242
|
+
command: 'npx',
|
|
243
|
+
args: ['-y', '@modelcontextprotocol/server-filesystem', process.cwd()],
|
|
244
|
+
transport: 'stdio',
|
|
245
|
+
enabled: false,
|
|
246
|
+
description: 'Filesystem operations MCP server'
|
|
247
|
+
},
|
|
248
|
+
'github': {
|
|
249
|
+
command: 'npx',
|
|
250
|
+
args: ['-y', '@modelcontextprotocol/server-github'],
|
|
251
|
+
transport: 'stdio',
|
|
252
|
+
enabled: false,
|
|
253
|
+
description: 'GitHub API MCP server',
|
|
254
|
+
env: {
|
|
255
|
+
GITHUB_TOKEN: 'your-github-token'
|
|
256
|
+
}
|
|
257
|
+
},
|
|
258
|
+
'postgres': {
|
|
259
|
+
command: 'npx',
|
|
260
|
+
args: ['-y', '@modelcontextprotocol/server-postgres'],
|
|
261
|
+
transport: 'stdio',
|
|
262
|
+
enabled: false,
|
|
263
|
+
description: 'PostgreSQL database MCP server',
|
|
264
|
+
env: {
|
|
265
|
+
DATABASE_URL: 'postgresql://user:pass@localhost/db'
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
'custom-http': {
|
|
269
|
+
url: 'http://localhost:3000/mcp',
|
|
270
|
+
transport: 'http',
|
|
271
|
+
enabled: false,
|
|
272
|
+
description: 'Custom HTTP MCP server'
|
|
273
|
+
},
|
|
274
|
+
'custom-websocket': {
|
|
275
|
+
url: 'ws://localhost:8080',
|
|
276
|
+
transport: 'websocket',
|
|
277
|
+
enabled: false,
|
|
278
|
+
description: 'Custom WebSocket MCP server'
|
|
279
|
+
}
|
|
280
|
+
},
|
|
281
|
+
// Global settings
|
|
282
|
+
settings: {
|
|
283
|
+
timeout: 30000,
|
|
284
|
+
retryCount: 3,
|
|
285
|
+
debug: false
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Save configuration to file
|
|
292
|
+
* @param {Object} config - Configuration to save
|
|
293
|
+
* @param {string} path - Path to save to
|
|
294
|
+
*/
|
|
295
|
+
export function saveConfig(config, path) {
|
|
296
|
+
const dir = dirname(path);
|
|
297
|
+
|
|
298
|
+
// Create directory if it doesn't exist
|
|
299
|
+
if (!existsSync(dir)) {
|
|
300
|
+
mkdirSync(dir, { recursive: true });
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
writeFileSync(path, JSON.stringify(config, null, 2), 'utf8');
|
|
304
|
+
console.log(`[MCP] Configuration saved to: ${path}`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
export default {
|
|
308
|
+
loadMCPConfiguration,
|
|
309
|
+
loadMCPConfigurationFromPath,
|
|
310
|
+
parseEnabledServers,
|
|
311
|
+
createSampleConfig,
|
|
312
|
+
saveConfig
|
|
313
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) integration for ProbeAgent
|
|
3
|
+
*
|
|
4
|
+
* This module provides:
|
|
5
|
+
* - MCP client management for connecting to MCP servers
|
|
6
|
+
* - XML/JSON hybrid tool interface
|
|
7
|
+
* - Configuration management
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Re-export main classes and functions
|
|
11
|
+
export { MCPClientManager, createMCPManager, createTransport } from './client.js';
|
|
12
|
+
export {
|
|
13
|
+
loadMCPConfiguration,
|
|
14
|
+
loadMCPConfigurationFromPath,
|
|
15
|
+
parseEnabledServers,
|
|
16
|
+
createSampleConfig,
|
|
17
|
+
saveConfig
|
|
18
|
+
} from './config.js';
|
|
19
|
+
export {
|
|
20
|
+
MCPXmlBridge,
|
|
21
|
+
mcpToolToXmlDefinition,
|
|
22
|
+
parseXmlMcpToolCall,
|
|
23
|
+
parseHybridXmlToolCall,
|
|
24
|
+
createHybridSystemMessage
|
|
25
|
+
} from './xmlBridge.js';
|
|
26
|
+
|
|
27
|
+
// Import for default export
|
|
28
|
+
import { MCPClientManager, createMCPManager, createTransport } from './client.js';
|
|
29
|
+
import {
|
|
30
|
+
loadMCPConfiguration,
|
|
31
|
+
loadMCPConfigurationFromPath,
|
|
32
|
+
parseEnabledServers,
|
|
33
|
+
createSampleConfig,
|
|
34
|
+
saveConfig
|
|
35
|
+
} from './config.js';
|
|
36
|
+
import {
|
|
37
|
+
MCPXmlBridge,
|
|
38
|
+
mcpToolToXmlDefinition,
|
|
39
|
+
parseXmlMcpToolCall,
|
|
40
|
+
parseHybridXmlToolCall,
|
|
41
|
+
createHybridSystemMessage
|
|
42
|
+
} from './xmlBridge.js';
|
|
43
|
+
|
|
44
|
+
// Default export for convenience
|
|
45
|
+
export default {
|
|
46
|
+
// Client
|
|
47
|
+
MCPClientManager,
|
|
48
|
+
createMCPManager,
|
|
49
|
+
createTransport,
|
|
50
|
+
|
|
51
|
+
// Config
|
|
52
|
+
loadMCPConfiguration,
|
|
53
|
+
loadMCPConfigurationFromPath,
|
|
54
|
+
parseEnabledServers,
|
|
55
|
+
createSampleConfig,
|
|
56
|
+
saveConfig,
|
|
57
|
+
|
|
58
|
+
// XML Bridge
|
|
59
|
+
MCPXmlBridge,
|
|
60
|
+
mcpToolToXmlDefinition,
|
|
61
|
+
parseXmlMcpToolCall,
|
|
62
|
+
parseHybridXmlToolCall,
|
|
63
|
+
createHybridSystemMessage
|
|
64
|
+
};
|