@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.
Files changed (33) hide show
  1. package/bin/binaries/probe-v0.6.0-rc209-aarch64-apple-darwin.tar.gz +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc209-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc209-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc209-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc209-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.d.ts +4 -2
  7. package/build/agent/ProbeAgent.js +2 -1
  8. package/build/agent/bashPermissions.js +88 -7
  9. package/build/agent/index.js +342 -24
  10. package/build/agent/mcp/client.js +236 -5
  11. package/build/agent/mcp/config.js +87 -0
  12. package/build/agent/mcp/xmlBridge.js +15 -5
  13. package/build/agent/simpleTelemetry.js +26 -0
  14. package/build/tools/bash.js +5 -3
  15. package/cjs/agent/ProbeAgent.cjs +314 -18
  16. package/cjs/agent/simpleTelemetry.cjs +22 -0
  17. package/cjs/index.cjs +336 -18
  18. package/index.d.ts +4 -2
  19. package/package.json +1 -1
  20. package/src/agent/ProbeAgent.d.ts +4 -2
  21. package/src/agent/ProbeAgent.js +2 -1
  22. package/src/agent/bashPermissions.js +88 -7
  23. package/src/agent/index.js +5 -5
  24. package/src/agent/mcp/client.js +236 -5
  25. package/src/agent/mcp/config.js +87 -0
  26. package/src/agent/mcp/xmlBridge.js +15 -5
  27. package/src/agent/simpleTelemetry.js +26 -0
  28. package/src/tools/bash.js +5 -3
  29. package/bin/binaries/probe-v0.6.0-rc207-aarch64-apple-darwin.tar.gz +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc207-aarch64-unknown-linux-musl.tar.gz +0 -0
  31. package/bin/binaries/probe-v0.6.0-rc207-x86_64-apple-darwin.tar.gz +0 -0
  32. package/bin/binaries/probe-v0.6.0-rc207-x86_64-pc-windows-msvc.zip +0 -0
  33. 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: Array.from(this.tools.keys())
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 toolCount = toolsResponse?.tools?.length || 0;
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
- console.error(`[MCP INFO] Connected to ${name}: ${toolCount} tool${toolCount !== 1 ? 's' : ''} loaded`);
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 all XML tool definitions for inclusion in system prompt
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
- return Object.values(this.xmlDefinitions).join('\n\n');
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) {
@@ -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