@probelabs/probe 0.6.0-rc207 → 0.6.0-rc209
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/bin/binaries/probe-v0.6.0-rc209-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc209-x86_64-unknown-linux-musl.tar.gz +0 -0
- package/build/agent/ProbeAgent.d.ts +4 -2
- package/build/agent/ProbeAgent.js +2 -1
- package/build/agent/bashPermissions.js +88 -7
- package/build/agent/index.js +342 -24
- package/build/agent/mcp/client.js +236 -5
- package/build/agent/mcp/config.js +87 -0
- package/build/agent/mcp/xmlBridge.js +15 -5
- package/build/agent/simpleTelemetry.js +26 -0
- package/build/tools/bash.js +5 -3
- package/cjs/agent/ProbeAgent.cjs +314 -18
- package/cjs/agent/simpleTelemetry.cjs +22 -0
- package/cjs/index.cjs +336 -18
- package/index.d.ts +4 -2
- package/package.json +1 -1
- package/src/agent/ProbeAgent.d.ts +4 -2
- package/src/agent/ProbeAgent.js +2 -1
- package/src/agent/bashPermissions.js +88 -7
- package/src/agent/index.js +5 -5
- package/src/agent/mcp/client.js +236 -5
- package/src/agent/mcp/config.js +87 -0
- package/src/agent/mcp/xmlBridge.js +15 -5
- package/src/agent/simpleTelemetry.js +26 -0
- package/src/tools/bash.js +5 -3
- package/bin/binaries/probe-v0.6.0-rc207-aarch64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-aarch64-unknown-linux-musl.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-apple-darwin.tar.gz +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-pc-windows-msvc.zip +0 -0
- package/bin/binaries/probe-v0.6.0-rc207-x86_64-unknown-linux-musl.tar.gz +0 -0
|
@@ -9,6 +9,47 @@ import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js';
|
|
|
9
9
|
import { WebSocketClientTransport } from '@modelcontextprotocol/sdk/client/websocket.js';
|
|
10
10
|
import { loadMCPConfiguration, parseEnabledServers, DEFAULT_TIMEOUT } from './config.js';
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Check if a method is allowed based on server's method filter configuration
|
|
14
|
+
* Supports wildcard patterns (e.g., "*_read", "search_*", "prefix_*_suffix")
|
|
15
|
+
* @param {string} methodName - The method name to check
|
|
16
|
+
* @param {string[]|null} allowedMethods - Array of allowed method patterns (null = all allowed)
|
|
17
|
+
* @param {string[]|null} blockedMethods - Array of blocked method patterns (null = none blocked)
|
|
18
|
+
* @returns {boolean} Whether the method is allowed
|
|
19
|
+
*/
|
|
20
|
+
export function isMethodAllowed(methodName, allowedMethods, blockedMethods) {
|
|
21
|
+
/**
|
|
22
|
+
* Check if a method name matches a pattern
|
|
23
|
+
* Supports * wildcard which matches any characters
|
|
24
|
+
* @param {string} name - Method name to check
|
|
25
|
+
* @param {string} pattern - Pattern to match against (may contain *)
|
|
26
|
+
* @returns {boolean} Whether the name matches the pattern
|
|
27
|
+
*/
|
|
28
|
+
const matchesPattern = (name, pattern) => {
|
|
29
|
+
if (!pattern.includes('*')) {
|
|
30
|
+
return name === pattern;
|
|
31
|
+
}
|
|
32
|
+
// Convert pattern to regex: escape special chars, replace * with .*
|
|
33
|
+
const regexPattern = pattern
|
|
34
|
+
.replace(/[.+?^${}()|[\]\\]/g, '\\$&') // Escape special regex chars
|
|
35
|
+
.replace(/\*/g, '.*'); // Replace * with .*
|
|
36
|
+
return new RegExp(`^${regexPattern}$`).test(name);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// If allowedMethods is specified (whitelist mode), only those methods are allowed
|
|
40
|
+
if (allowedMethods && allowedMethods.length > 0) {
|
|
41
|
+
return allowedMethods.some(pattern => matchesPattern(methodName, pattern));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// If blockedMethods is specified (blacklist mode), all methods except those are allowed
|
|
45
|
+
if (blockedMethods && blockedMethods.length > 0) {
|
|
46
|
+
return !blockedMethods.some(pattern => matchesPattern(methodName, pattern));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// No filter specified - all methods are allowed
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
|
|
12
53
|
/**
|
|
13
54
|
* Create transport based on configuration
|
|
14
55
|
* @param {Object} serverConfig - Server configuration
|
|
@@ -117,6 +158,18 @@ export class MCPClientManager {
|
|
|
117
158
|
this.tools = new Map();
|
|
118
159
|
this.debug = options.debug || process.env.DEBUG_MCP === '1';
|
|
119
160
|
this.config = null;
|
|
161
|
+
this.tracer = options.tracer || null;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Record an MCP telemetry event if tracer is available
|
|
166
|
+
* @param {string} eventType - Event type (e.g., 'server.connect', 'tool.discovered')
|
|
167
|
+
* @param {Object} data - Event data
|
|
168
|
+
*/
|
|
169
|
+
recordMcpEvent(eventType, data = {}) {
|
|
170
|
+
if (this.tracer && typeof this.tracer.recordMcpEvent === 'function') {
|
|
171
|
+
this.tracer.recordMcpEvent(eventType, data);
|
|
172
|
+
}
|
|
120
173
|
}
|
|
121
174
|
|
|
122
175
|
/**
|
|
@@ -128,12 +181,24 @@ export class MCPClientManager {
|
|
|
128
181
|
this.config = config || loadMCPConfiguration();
|
|
129
182
|
const servers = parseEnabledServers(this.config);
|
|
130
183
|
|
|
184
|
+
// Record initialization start
|
|
185
|
+
this.recordMcpEvent('initialization.started', {
|
|
186
|
+
serverCount: servers.length,
|
|
187
|
+
serverNames: servers.map(s => s.name)
|
|
188
|
+
});
|
|
189
|
+
|
|
131
190
|
// Always log the number of servers found
|
|
132
191
|
console.error(`[MCP INFO] Found ${servers.length} enabled MCP server${servers.length !== 1 ? 's' : ''}`);
|
|
133
192
|
|
|
134
193
|
if (servers.length === 0) {
|
|
135
194
|
console.error('[MCP INFO] No MCP servers configured or enabled');
|
|
136
195
|
console.error('[MCP INFO] 0 MCP tools available');
|
|
196
|
+
this.recordMcpEvent('initialization.completed', {
|
|
197
|
+
connected: 0,
|
|
198
|
+
total: 0,
|
|
199
|
+
toolCount: 0,
|
|
200
|
+
tools: []
|
|
201
|
+
});
|
|
137
202
|
return {
|
|
138
203
|
connected: 0,
|
|
139
204
|
total: 0,
|
|
@@ -178,10 +243,19 @@ export class MCPClientManager {
|
|
|
178
243
|
});
|
|
179
244
|
}
|
|
180
245
|
|
|
246
|
+
// Record initialization completion
|
|
247
|
+
const toolNames = Array.from(this.tools.keys());
|
|
248
|
+
this.recordMcpEvent('initialization.completed', {
|
|
249
|
+
connected: connectedCount,
|
|
250
|
+
total: servers.length,
|
|
251
|
+
toolCount: this.tools.size,
|
|
252
|
+
tools: toolNames
|
|
253
|
+
});
|
|
254
|
+
|
|
181
255
|
return {
|
|
182
256
|
connected: connectedCount,
|
|
183
257
|
total: servers.length,
|
|
184
|
-
tools:
|
|
258
|
+
tools: toolNames
|
|
185
259
|
};
|
|
186
260
|
}
|
|
187
261
|
|
|
@@ -192,6 +266,14 @@ export class MCPClientManager {
|
|
|
192
266
|
async connectToServer(serverConfig) {
|
|
193
267
|
const { name } = serverConfig;
|
|
194
268
|
|
|
269
|
+
// Record connection attempt
|
|
270
|
+
this.recordMcpEvent('server.connecting', {
|
|
271
|
+
serverName: name,
|
|
272
|
+
transport: serverConfig.transport,
|
|
273
|
+
hasAllowedMethods: !!(serverConfig.allowedMethods && serverConfig.allowedMethods.length > 0),
|
|
274
|
+
hasBlockedMethods: !!(serverConfig.blockedMethods && serverConfig.blockedMethods.length > 0)
|
|
275
|
+
});
|
|
276
|
+
|
|
195
277
|
try {
|
|
196
278
|
if (this.debug) {
|
|
197
279
|
console.error(`[MCP DEBUG] Connecting to ${name} via ${serverConfig.transport}...`);
|
|
@@ -223,10 +305,34 @@ export class MCPClientManager {
|
|
|
223
305
|
|
|
224
306
|
// Fetch and register tools
|
|
225
307
|
const toolsResponse = await client.listTools();
|
|
226
|
-
const
|
|
308
|
+
const totalToolCount = toolsResponse?.tools?.length || 0;
|
|
309
|
+
let registeredCount = 0;
|
|
310
|
+
let filteredCount = 0;
|
|
311
|
+
const registeredTools = [];
|
|
312
|
+
const filteredTools = [];
|
|
227
313
|
|
|
228
314
|
if (toolsResponse && toolsResponse.tools) {
|
|
315
|
+
const { allowedMethods, blockedMethods } = serverConfig;
|
|
316
|
+
const allToolNames = toolsResponse.tools.map(t => t.name);
|
|
317
|
+
|
|
318
|
+
// Record tools discovered from server
|
|
319
|
+
this.recordMcpEvent('tools.discovered', {
|
|
320
|
+
serverName: name,
|
|
321
|
+
toolCount: totalToolCount,
|
|
322
|
+
tools: allToolNames
|
|
323
|
+
});
|
|
324
|
+
|
|
229
325
|
for (const tool of toolsResponse.tools) {
|
|
326
|
+
// Apply method filtering based on server config
|
|
327
|
+
if (!isMethodAllowed(tool.name, allowedMethods, blockedMethods)) {
|
|
328
|
+
filteredCount++;
|
|
329
|
+
filteredTools.push(tool.name);
|
|
330
|
+
if (this.debug) {
|
|
331
|
+
console.error(`[MCP DEBUG] Filtered out tool: ${tool.name} (not allowed by method filter)`);
|
|
332
|
+
}
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
|
|
230
336
|
// Add server prefix to avoid conflicts
|
|
231
337
|
const qualifiedName = `${name}_${tool.name}`;
|
|
232
338
|
this.tools.set(qualifiedName, {
|
|
@@ -234,14 +340,68 @@ export class MCPClientManager {
|
|
|
234
340
|
serverName: name,
|
|
235
341
|
originalName: tool.name
|
|
236
342
|
});
|
|
343
|
+
registeredCount++;
|
|
344
|
+
registeredTools.push(qualifiedName);
|
|
237
345
|
|
|
238
346
|
if (this.debug) {
|
|
239
347
|
console.error(`[MCP DEBUG] Registered tool: ${qualifiedName}`);
|
|
240
348
|
}
|
|
241
349
|
}
|
|
350
|
+
|
|
351
|
+
// Record method filtering results if any filtering was applied
|
|
352
|
+
if (filteredCount > 0) {
|
|
353
|
+
this.recordMcpEvent('tools.filtered', {
|
|
354
|
+
serverName: name,
|
|
355
|
+
filteredCount,
|
|
356
|
+
filteredTools,
|
|
357
|
+
allowedMethods: allowedMethods || [],
|
|
358
|
+
blockedMethods: blockedMethods || []
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Check for unmatched patterns in allowedMethods and warn users
|
|
363
|
+
if (allowedMethods && allowedMethods.length > 0) {
|
|
364
|
+
const unmatchedPatterns = allowedMethods.filter(pattern => {
|
|
365
|
+
// Check if this pattern matches at least one tool
|
|
366
|
+
return !allToolNames.some(toolName => isMethodAllowed(toolName, [pattern], null));
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
if (unmatchedPatterns.length > 0) {
|
|
370
|
+
console.error(`[MCP WARN] Server '${name}': The following allowedMethods patterns did not match any tools: ${unmatchedPatterns.join(', ')}`);
|
|
371
|
+
console.error(`[MCP WARN] Available methods from '${name}': ${allToolNames.join(', ')}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Check for unmatched patterns in blockedMethods and warn users
|
|
376
|
+
if (blockedMethods && blockedMethods.length > 0) {
|
|
377
|
+
const unmatchedPatterns = blockedMethods.filter(pattern => {
|
|
378
|
+
// Check if this pattern matches at least one tool
|
|
379
|
+
return !allToolNames.some(toolName => !isMethodAllowed(toolName, null, [pattern]));
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
if (unmatchedPatterns.length > 0) {
|
|
383
|
+
console.error(`[MCP WARN] Server '${name}': The following blockedMethods patterns did not match any tools: ${unmatchedPatterns.join(', ')}`);
|
|
384
|
+
console.error(`[MCP WARN] Available methods from '${name}': ${allToolNames.join(', ')}`);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Log connection result with filtering info
|
|
390
|
+
if (filteredCount > 0) {
|
|
391
|
+
console.error(`[MCP INFO] Connected to ${name}: ${registeredCount} tool${registeredCount !== 1 ? 's' : ''} loaded (${filteredCount} filtered out)`);
|
|
392
|
+
} else {
|
|
393
|
+
console.error(`[MCP INFO] Connected to ${name}: ${registeredCount} tool${registeredCount !== 1 ? 's' : ''} loaded`);
|
|
242
394
|
}
|
|
243
395
|
|
|
244
|
-
|
|
396
|
+
// Record successful connection
|
|
397
|
+
this.recordMcpEvent('server.connected', {
|
|
398
|
+
serverName: name,
|
|
399
|
+
transport: serverConfig.transport,
|
|
400
|
+
totalToolCount,
|
|
401
|
+
registeredCount,
|
|
402
|
+
filteredCount,
|
|
403
|
+
registeredTools
|
|
404
|
+
});
|
|
245
405
|
|
|
246
406
|
return true;
|
|
247
407
|
} catch (error) {
|
|
@@ -249,6 +409,14 @@ export class MCPClientManager {
|
|
|
249
409
|
if (this.debug) {
|
|
250
410
|
console.error(`[MCP DEBUG] Full error details:`, error);
|
|
251
411
|
}
|
|
412
|
+
|
|
413
|
+
// Record connection failure
|
|
414
|
+
this.recordMcpEvent('server.connection_failed', {
|
|
415
|
+
serverName: name,
|
|
416
|
+
transport: serverConfig.transport,
|
|
417
|
+
error: error.message
|
|
418
|
+
});
|
|
419
|
+
|
|
252
420
|
return false;
|
|
253
421
|
}
|
|
254
422
|
}
|
|
@@ -261,14 +429,32 @@ export class MCPClientManager {
|
|
|
261
429
|
async callTool(toolName, args) {
|
|
262
430
|
const tool = this.tools.get(toolName);
|
|
263
431
|
if (!tool) {
|
|
432
|
+
this.recordMcpEvent('tool.call_failed', {
|
|
433
|
+
toolName,
|
|
434
|
+
error: 'Unknown tool'
|
|
435
|
+
});
|
|
264
436
|
throw new Error(`Unknown tool: ${toolName}`);
|
|
265
437
|
}
|
|
266
438
|
|
|
267
439
|
const clientInfo = this.clients.get(tool.serverName);
|
|
268
440
|
if (!clientInfo) {
|
|
441
|
+
this.recordMcpEvent('tool.call_failed', {
|
|
442
|
+
toolName,
|
|
443
|
+
serverName: tool.serverName,
|
|
444
|
+
error: 'Server not connected'
|
|
445
|
+
});
|
|
269
446
|
throw new Error(`Server ${tool.serverName} not connected`);
|
|
270
447
|
}
|
|
271
448
|
|
|
449
|
+
const startTime = Date.now();
|
|
450
|
+
|
|
451
|
+
// Record tool call start
|
|
452
|
+
this.recordMcpEvent('tool.call_started', {
|
|
453
|
+
toolName,
|
|
454
|
+
serverName: tool.serverName,
|
|
455
|
+
originalToolName: tool.originalName
|
|
456
|
+
});
|
|
457
|
+
|
|
272
458
|
try {
|
|
273
459
|
if (this.debug) {
|
|
274
460
|
console.error(`[MCP DEBUG] Calling ${toolName} with args:`, JSON.stringify(args, null, 2));
|
|
@@ -288,24 +474,48 @@ export class MCPClientManager {
|
|
|
288
474
|
});
|
|
289
475
|
|
|
290
476
|
// Race between the actual call and timeout
|
|
477
|
+
// Pass timeout to SDK's callTool to override its default 60s timeout
|
|
291
478
|
const result = await Promise.race([
|
|
292
479
|
clientInfo.client.callTool({
|
|
293
480
|
name: tool.originalName,
|
|
294
481
|
arguments: args
|
|
295
|
-
}),
|
|
482
|
+
}, undefined, { timeout }),
|
|
296
483
|
timeoutPromise
|
|
297
484
|
]);
|
|
298
485
|
|
|
486
|
+
const durationMs = Date.now() - startTime;
|
|
487
|
+
|
|
299
488
|
if (this.debug) {
|
|
300
489
|
console.error(`[MCP DEBUG] Tool ${toolName} executed successfully`);
|
|
301
490
|
}
|
|
302
491
|
|
|
492
|
+
// Record successful tool call
|
|
493
|
+
this.recordMcpEvent('tool.call_completed', {
|
|
494
|
+
toolName,
|
|
495
|
+
serverName: tool.serverName,
|
|
496
|
+
originalToolName: tool.originalName,
|
|
497
|
+
durationMs
|
|
498
|
+
});
|
|
499
|
+
|
|
303
500
|
return result;
|
|
304
501
|
} catch (error) {
|
|
502
|
+
const durationMs = Date.now() - startTime;
|
|
503
|
+
|
|
305
504
|
console.error(`[MCP ERROR] Error calling tool ${toolName}:`, error.message);
|
|
306
505
|
if (this.debug) {
|
|
307
506
|
console.error(`[MCP DEBUG] Full error details:`, error);
|
|
308
507
|
}
|
|
508
|
+
|
|
509
|
+
// Record failed tool call
|
|
510
|
+
this.recordMcpEvent('tool.call_failed', {
|
|
511
|
+
toolName,
|
|
512
|
+
serverName: tool.serverName,
|
|
513
|
+
originalToolName: tool.originalName,
|
|
514
|
+
error: error.message,
|
|
515
|
+
durationMs,
|
|
516
|
+
isTimeout: error.message.includes('timeout')
|
|
517
|
+
});
|
|
518
|
+
|
|
309
519
|
throw error;
|
|
310
520
|
}
|
|
311
521
|
}
|
|
@@ -357,6 +567,7 @@ export class MCPClientManager {
|
|
|
357
567
|
*/
|
|
358
568
|
async disconnect() {
|
|
359
569
|
const disconnectPromises = [];
|
|
570
|
+
const serverNames = Array.from(this.clients.keys());
|
|
360
571
|
|
|
361
572
|
if (this.clients.size === 0) {
|
|
362
573
|
if (this.debug) {
|
|
@@ -365,6 +576,12 @@ export class MCPClientManager {
|
|
|
365
576
|
return;
|
|
366
577
|
}
|
|
367
578
|
|
|
579
|
+
// Record disconnection start
|
|
580
|
+
this.recordMcpEvent('disconnection.started', {
|
|
581
|
+
serverCount: this.clients.size,
|
|
582
|
+
serverNames
|
|
583
|
+
});
|
|
584
|
+
|
|
368
585
|
if (this.debug) {
|
|
369
586
|
console.error(`[MCP DEBUG] Disconnecting from ${this.clients.size} MCP server${this.clients.size !== 1 ? 's' : ''}...`);
|
|
370
587
|
}
|
|
@@ -376,9 +593,16 @@ export class MCPClientManager {
|
|
|
376
593
|
if (this.debug) {
|
|
377
594
|
console.error(`[MCP DEBUG] Disconnected from ${name}`);
|
|
378
595
|
}
|
|
596
|
+
this.recordMcpEvent('server.disconnected', {
|
|
597
|
+
serverName: name
|
|
598
|
+
});
|
|
379
599
|
})
|
|
380
600
|
.catch(error => {
|
|
381
601
|
console.error(`[MCP ERROR] Error disconnecting from ${name}:`, error.message);
|
|
602
|
+
this.recordMcpEvent('server.disconnect_failed', {
|
|
603
|
+
serverName: name,
|
|
604
|
+
error: error.message
|
|
605
|
+
});
|
|
382
606
|
})
|
|
383
607
|
);
|
|
384
608
|
}
|
|
@@ -387,6 +611,12 @@ export class MCPClientManager {
|
|
|
387
611
|
this.clients.clear();
|
|
388
612
|
this.tools.clear();
|
|
389
613
|
|
|
614
|
+
// Record disconnection completion
|
|
615
|
+
this.recordMcpEvent('disconnection.completed', {
|
|
616
|
+
serverCount: serverNames.length,
|
|
617
|
+
serverNames
|
|
618
|
+
});
|
|
619
|
+
|
|
390
620
|
if (this.debug) {
|
|
391
621
|
console.error('[MCP DEBUG] All MCP connections closed');
|
|
392
622
|
}
|
|
@@ -405,5 +635,6 @@ export async function createMCPManager(options = {}) {
|
|
|
405
635
|
export default {
|
|
406
636
|
MCPClientManager,
|
|
407
637
|
createMCPManager,
|
|
408
|
-
createTransport
|
|
638
|
+
createTransport,
|
|
639
|
+
isMethodAllowed
|
|
409
640
|
};
|
|
@@ -29,6 +29,61 @@ export function validateTimeout(value) {
|
|
|
29
29
|
return Math.min(num, MAX_TIMEOUT); // Cap at max timeout
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Validate and normalize method filter configuration
|
|
34
|
+
* @param {Object} serverConfig - Server configuration
|
|
35
|
+
* @param {string} serverName - Server name for logging
|
|
36
|
+
* @returns {Object} Object with allowedMethods and blockedMethods (null if not configured)
|
|
37
|
+
*/
|
|
38
|
+
export function validateMethodFilter(serverConfig, serverName = 'unknown') {
|
|
39
|
+
const result = { allowedMethods: null, blockedMethods: null };
|
|
40
|
+
const debug = process.env.DEBUG === '1' || process.env.DEBUG_MCP === '1';
|
|
41
|
+
|
|
42
|
+
// Check if both are specified - allowedMethods takes precedence
|
|
43
|
+
if (serverConfig.allowedMethods && serverConfig.blockedMethods) {
|
|
44
|
+
console.error(`[MCP WARN] Server '${serverName}' has both allowedMethods and blockedMethods - using allowedMethods only`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Process allowedMethods
|
|
48
|
+
if (serverConfig.allowedMethods) {
|
|
49
|
+
if (!Array.isArray(serverConfig.allowedMethods)) {
|
|
50
|
+
console.error(`[MCP WARN] Server '${serverName}' allowedMethods must be an array, ignoring`);
|
|
51
|
+
} else {
|
|
52
|
+
const validMethods = serverConfig.allowedMethods.filter(m => typeof m === 'string' && m.length > 0);
|
|
53
|
+
if (validMethods.length !== serverConfig.allowedMethods.length) {
|
|
54
|
+
console.error(`[MCP WARN] Server '${serverName}' allowedMethods contains non-string values, skipping those`);
|
|
55
|
+
}
|
|
56
|
+
if (validMethods.length > 0) {
|
|
57
|
+
result.allowedMethods = validMethods;
|
|
58
|
+
if (debug) {
|
|
59
|
+
console.error(`[MCP DEBUG] Server '${serverName}' allowedMethods: ${validMethods.join(', ')}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return result; // If allowedMethods is specified (even if invalid), don't process blockedMethods
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Process blockedMethods (only if allowedMethods not specified)
|
|
67
|
+
if (serverConfig.blockedMethods) {
|
|
68
|
+
if (!Array.isArray(serverConfig.blockedMethods)) {
|
|
69
|
+
console.error(`[MCP WARN] Server '${serverName}' blockedMethods must be an array, ignoring`);
|
|
70
|
+
} else {
|
|
71
|
+
const validMethods = serverConfig.blockedMethods.filter(m => typeof m === 'string' && m.length > 0);
|
|
72
|
+
if (validMethods.length !== serverConfig.blockedMethods.length) {
|
|
73
|
+
console.error(`[MCP WARN] Server '${serverName}' blockedMethods contains non-string values, skipping those`);
|
|
74
|
+
}
|
|
75
|
+
if (validMethods.length > 0) {
|
|
76
|
+
result.blockedMethods = validMethods;
|
|
77
|
+
if (debug) {
|
|
78
|
+
console.error(`[MCP DEBUG] Server '${serverName}' blockedMethods: ${validMethods.join(', ')}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
32
87
|
/**
|
|
33
88
|
* Default MCP configuration structure
|
|
34
89
|
*/
|
|
@@ -187,6 +242,16 @@ function mergeWithEnvironment(config) {
|
|
|
187
242
|
console.error(`[MCP WARN] Invalid timeout value for ${normalizedName}: ${value}`);
|
|
188
243
|
}
|
|
189
244
|
break;
|
|
245
|
+
case 'ALLOWLIST':
|
|
246
|
+
// Comma-separated list of allowed method names (supports wildcards)
|
|
247
|
+
// e.g., MCP_SERVERS_MYSERVER_ALLOWLIST=method1,method2,prefix_*
|
|
248
|
+
config.mcpServers[normalizedName].allowedMethods = value.split(',').map(m => m.trim()).filter(Boolean);
|
|
249
|
+
break;
|
|
250
|
+
case 'BLOCKLIST':
|
|
251
|
+
// Comma-separated list of blocked method names (supports wildcards)
|
|
252
|
+
// e.g., MCP_SERVERS_MYSERVER_BLOCKLIST=dangerous_*,risky_method
|
|
253
|
+
config.mcpServers[normalizedName].blockedMethods = value.split(',').map(m => m.trim()).filter(Boolean);
|
|
254
|
+
break;
|
|
190
255
|
}
|
|
191
256
|
}
|
|
192
257
|
}
|
|
@@ -256,6 +321,11 @@ export function parseEnabledServers(config) {
|
|
|
256
321
|
server.timeout = validatedTimeout;
|
|
257
322
|
}
|
|
258
323
|
|
|
324
|
+
// Validate and normalize method filter configuration
|
|
325
|
+
const methodFilter = validateMethodFilter(serverConfig, name);
|
|
326
|
+
server.allowedMethods = methodFilter.allowedMethods;
|
|
327
|
+
server.blockedMethods = methodFilter.blockedMethods;
|
|
328
|
+
|
|
259
329
|
servers.push(server);
|
|
260
330
|
}
|
|
261
331
|
|
|
@@ -321,6 +391,22 @@ export function createSampleConfig() {
|
|
|
321
391
|
enabled: false,
|
|
322
392
|
timeout: 120000,
|
|
323
393
|
description: 'Example server with custom 2-minute timeout (overrides global setting)'
|
|
394
|
+
},
|
|
395
|
+
'filtered-server-example': {
|
|
396
|
+
command: 'npx',
|
|
397
|
+
args: ['-y', '@example/mcp-server'],
|
|
398
|
+
transport: 'stdio',
|
|
399
|
+
enabled: false,
|
|
400
|
+
allowedMethods: ['safe_read', 'safe_query'],
|
|
401
|
+
description: 'Example server with method allowlist - only safe_read and safe_query are available'
|
|
402
|
+
},
|
|
403
|
+
'blocklist-server-example': {
|
|
404
|
+
command: 'npx',
|
|
405
|
+
args: ['-y', '@example/mcp-server'],
|
|
406
|
+
transport: 'stdio',
|
|
407
|
+
enabled: false,
|
|
408
|
+
blockedMethods: ['dangerous_delete', 'dangerous_*'],
|
|
409
|
+
description: 'Example server with method blocklist - all methods except dangerous ones (supports wildcards)'
|
|
324
410
|
}
|
|
325
411
|
},
|
|
326
412
|
// Global settings (apply to all servers unless overridden per-server)
|
|
@@ -356,6 +442,7 @@ export default {
|
|
|
356
442
|
createSampleConfig,
|
|
357
443
|
saveConfig,
|
|
358
444
|
validateTimeout,
|
|
445
|
+
validateMethodFilter,
|
|
359
446
|
DEFAULT_TIMEOUT,
|
|
360
447
|
MAX_TIMEOUT
|
|
361
448
|
};
|
|
@@ -127,6 +127,7 @@ export function parseXmlMcpToolCall(xmlString, mcpToolNames = []) {
|
|
|
127
127
|
export class MCPXmlBridge {
|
|
128
128
|
constructor(options = {}) {
|
|
129
129
|
this.debug = options.debug || false;
|
|
130
|
+
this.tracer = options.tracer || null;
|
|
130
131
|
this.mcpTools = {};
|
|
131
132
|
this.mcpManager = null;
|
|
132
133
|
this.xmlDefinitions = {};
|
|
@@ -179,8 +180,8 @@ export class MCPXmlBridge {
|
|
|
179
180
|
console.error('[MCP DEBUG] Initializing MCP client manager...');
|
|
180
181
|
}
|
|
181
182
|
|
|
182
|
-
// Initialize the MCP client manager
|
|
183
|
-
this.mcpManager = new MCPClientManager({ debug: this.debug });
|
|
183
|
+
// Initialize the MCP client manager with tracer support
|
|
184
|
+
this.mcpManager = new MCPClientManager({ debug: this.debug, tracer: this.tracer });
|
|
184
185
|
const result = await this.mcpManager.initialize(mcpConfigs);
|
|
185
186
|
|
|
186
187
|
// Get tools from the manager
|
|
@@ -211,11 +212,20 @@ export class MCPXmlBridge {
|
|
|
211
212
|
}
|
|
212
213
|
|
|
213
214
|
/**
|
|
214
|
-
* Get
|
|
215
|
+
* Get XML tool definitions for inclusion in system prompt
|
|
216
|
+
* @param {Array<string>|null} filterToolNames - Optional list of tool names to include (if null, include all)
|
|
215
217
|
* @returns {string} Combined XML tool definitions
|
|
216
218
|
*/
|
|
217
|
-
getXmlToolDefinitions() {
|
|
218
|
-
|
|
219
|
+
getXmlToolDefinitions(filterToolNames = null) {
|
|
220
|
+
if (filterToolNames === null) {
|
|
221
|
+
return Object.values(this.xmlDefinitions).join('\n\n');
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// Filter definitions based on provided tool names
|
|
225
|
+
return Object.entries(this.xmlDefinitions)
|
|
226
|
+
.filter(([name]) => filterToolNames.includes(name))
|
|
227
|
+
.map(([, def]) => def)
|
|
228
|
+
.join('\n\n');
|
|
219
229
|
}
|
|
220
230
|
|
|
221
231
|
/**
|
|
@@ -231,6 +231,32 @@ export class SimpleAppTracer {
|
|
|
231
231
|
});
|
|
232
232
|
}
|
|
233
233
|
|
|
234
|
+
/**
|
|
235
|
+
* Record MCP (Model Context Protocol) events
|
|
236
|
+
* Tracks server connections, tool discovery, method filtering, and tool execution
|
|
237
|
+
*/
|
|
238
|
+
recordMcpEvent(eventType, data = {}) {
|
|
239
|
+
if (!this.isEnabled()) return;
|
|
240
|
+
|
|
241
|
+
this.addEvent(`mcp.${eventType}`, {
|
|
242
|
+
'session.id': this.sessionId,
|
|
243
|
+
...data
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Record bash tool events
|
|
249
|
+
* Tracks command permission checks, allowed/denied commands, and execution
|
|
250
|
+
*/
|
|
251
|
+
recordBashEvent(eventType, data = {}) {
|
|
252
|
+
if (!this.isEnabled()) return;
|
|
253
|
+
|
|
254
|
+
this.addEvent(`bash.${eventType}`, {
|
|
255
|
+
'session.id': this.sessionId,
|
|
256
|
+
...data
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
|
|
234
260
|
setAttributes(attributes) {
|
|
235
261
|
// For simplicity, just log attributes when no active span
|
|
236
262
|
if (this.telemetry && this.telemetry.enableConsole) {
|
package/build/tools/bash.js
CHANGED
|
@@ -31,16 +31,18 @@ export const bashTool = (options = {}) => {
|
|
|
31
31
|
bashConfig = {},
|
|
32
32
|
debug = false,
|
|
33
33
|
cwd,
|
|
34
|
-
allowedFolders = []
|
|
34
|
+
allowedFolders = [],
|
|
35
|
+
tracer = null
|
|
35
36
|
} = options;
|
|
36
37
|
|
|
37
|
-
// Create permission checker
|
|
38
|
+
// Create permission checker with tracer for telemetry
|
|
38
39
|
const permissionChecker = new BashPermissionChecker({
|
|
39
40
|
allow: bashConfig.allow,
|
|
40
41
|
deny: bashConfig.deny,
|
|
41
42
|
disableDefaultAllow: bashConfig.disableDefaultAllow,
|
|
42
43
|
disableDefaultDeny: bashConfig.disableDefaultDeny,
|
|
43
|
-
debug
|
|
44
|
+
debug,
|
|
45
|
+
tracer
|
|
44
46
|
});
|
|
45
47
|
|
|
46
48
|
// Determine default working directory
|