@probelabs/probe 0.6.0-rc206 → 0.6.0-rc208

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 (30) hide show
  1. package/bin/binaries/{probe-v0.6.0-rc206-aarch64-apple-darwin.tar.gz → probe-v0.6.0-rc208-aarch64-apple-darwin.tar.gz} +0 -0
  2. package/bin/binaries/probe-v0.6.0-rc208-aarch64-unknown-linux-musl.tar.gz +0 -0
  3. package/bin/binaries/probe-v0.6.0-rc208-x86_64-apple-darwin.tar.gz +0 -0
  4. package/bin/binaries/probe-v0.6.0-rc208-x86_64-pc-windows-msvc.zip +0 -0
  5. package/bin/binaries/probe-v0.6.0-rc208-x86_64-unknown-linux-musl.tar.gz +0 -0
  6. package/build/agent/ProbeAgent.js +144 -2
  7. package/build/agent/bashPermissions.js +88 -7
  8. package/build/agent/index.js +450 -18
  9. package/build/agent/mcp/client.js +234 -4
  10. package/build/agent/mcp/config.js +87 -0
  11. package/build/agent/mcp/xmlBridge.js +15 -5
  12. package/build/agent/simpleTelemetry.js +26 -0
  13. package/build/tools/bash.js +5 -3
  14. package/build/tools/common.js +31 -0
  15. package/cjs/agent/ProbeAgent.cjs +428 -18
  16. package/cjs/agent/simpleTelemetry.cjs +22 -0
  17. package/cjs/index.cjs +450 -18
  18. package/package.json +1 -1
  19. package/src/agent/ProbeAgent.js +144 -2
  20. package/src/agent/bashPermissions.js +88 -7
  21. package/src/agent/mcp/client.js +234 -4
  22. package/src/agent/mcp/config.js +87 -0
  23. package/src/agent/mcp/xmlBridge.js +15 -5
  24. package/src/agent/simpleTelemetry.js +26 -0
  25. package/src/tools/bash.js +5 -3
  26. package/src/tools/common.js +31 -0
  27. package/bin/binaries/probe-v0.6.0-rc206-aarch64-unknown-linux-musl.tar.gz +0 -0
  28. package/bin/binaries/probe-v0.6.0-rc206-x86_64-apple-darwin.tar.gz +0 -0
  29. package/bin/binaries/probe-v0.6.0-rc206-x86_64-pc-windows-msvc.zip +0 -0
  30. package/bin/binaries/probe-v0.6.0-rc206-x86_64-unknown-linux-musl.tar.gz +0 -0
@@ -36006,6 +36006,22 @@ function detectUnrecognizedToolCall(xmlString, validTools) {
36006
36006
  return toolName;
36007
36007
  }
36008
36008
  }
36009
+ const allToolNames = [.../* @__PURE__ */ new Set([...knownToolNames, ...validTools])];
36010
+ for (const toolName of allToolNames) {
36011
+ const escapedToolName = toolName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
36012
+ const wrapperPatterns = [
36013
+ new RegExp(`<tool_name>\\s*${escapedToolName}\\s*</tool_name>`, "i"),
36014
+ new RegExp(`<function>\\s*${escapedToolName}\\s*</function>`, "i"),
36015
+ new RegExp(`<name>\\s*${escapedToolName}\\s*</name>`, "i"),
36016
+ // Also check for tool name immediately after api_call or call opening tag
36017
+ new RegExp(`<(?:api_call|call)[^>]*>[\\s\\S]*?<tool_name>\\s*${escapedToolName}`, "i")
36018
+ ];
36019
+ for (const pattern of wrapperPatterns) {
36020
+ if (pattern.test(xmlString)) {
36021
+ return `wrapped_tool:${toolName}`;
36022
+ }
36023
+ }
36024
+ }
36009
36025
  return null;
36010
36026
  }
36011
36027
  function parseTargets(targets) {
@@ -37533,9 +37549,11 @@ var init_bashPermissions = __esm({
37533
37549
  * @param {boolean} [config.disableDefaultAllow] - Disable default allow list
37534
37550
  * @param {boolean} [config.disableDefaultDeny] - Disable default deny list
37535
37551
  * @param {boolean} [config.debug] - Enable debug logging
37552
+ * @param {Object} [config.tracer] - Optional tracer for telemetry
37536
37553
  */
37537
37554
  constructor(config = {}) {
37538
37555
  this.debug = config.debug || false;
37556
+ this.tracer = config.tracer || null;
37539
37557
  this.allowPatterns = [];
37540
37558
  if (!config.disableDefaultAllow) {
37541
37559
  this.allowPatterns.push(...DEFAULT_ALLOW_PATTERNS);
@@ -37565,6 +37583,24 @@ var init_bashPermissions = __esm({
37565
37583
  if (this.debug) {
37566
37584
  console.log(`[BashPermissions] Total patterns - Allow: ${this.allowPatterns.length}, Deny: ${this.denyPatterns.length}`);
37567
37585
  }
37586
+ this.recordBashEvent("permissions.initialized", {
37587
+ allowPatternCount: this.allowPatterns.length,
37588
+ denyPatternCount: this.denyPatterns.length,
37589
+ hasCustomAllowPatterns: !!(config.allow && config.allow.length > 0),
37590
+ hasCustomDenyPatterns: !!(config.deny && config.deny.length > 0),
37591
+ disableDefaultAllow: !!config.disableDefaultAllow,
37592
+ disableDefaultDeny: !!config.disableDefaultDeny
37593
+ });
37594
+ }
37595
+ /**
37596
+ * Record a bash telemetry event if tracer is available
37597
+ * @param {string} eventType - Event type (e.g., 'permission.checked', 'permission.denied')
37598
+ * @param {Object} data - Event data
37599
+ */
37600
+ recordBashEvent(eventType, data2 = {}) {
37601
+ if (this.tracer && typeof this.tracer.recordBashEvent === "function") {
37602
+ this.tracer.recordBashEvent(eventType, data2);
37603
+ }
37568
37604
  }
37569
37605
  /**
37570
37606
  * Check if a simple command is allowed (complex commands allowed if they match patterns)
@@ -37573,11 +37609,17 @@ var init_bashPermissions = __esm({
37573
37609
  */
37574
37610
  check(command) {
37575
37611
  if (!command || typeof command !== "string") {
37576
- return {
37612
+ const result2 = {
37577
37613
  allowed: false,
37578
37614
  reason: "Invalid or empty command",
37579
37615
  command
37580
37616
  };
37617
+ this.recordBashEvent("permission.denied", {
37618
+ command: String(command),
37619
+ reason: result2.reason,
37620
+ isComplex: false
37621
+ });
37622
+ return result2;
37581
37623
  }
37582
37624
  const commandIsComplex = isComplexCommand(command);
37583
37625
  if (commandIsComplex) {
@@ -37585,18 +37627,31 @@ var init_bashPermissions = __esm({
37585
37627
  }
37586
37628
  const parsed = parseCommand(command);
37587
37629
  if (parsed.error) {
37588
- return {
37630
+ const result2 = {
37589
37631
  allowed: false,
37590
37632
  reason: parsed.error,
37591
37633
  command
37592
37634
  };
37635
+ this.recordBashEvent("permission.denied", {
37636
+ command,
37637
+ reason: result2.reason,
37638
+ isComplex: false,
37639
+ parseError: true
37640
+ });
37641
+ return result2;
37593
37642
  }
37594
37643
  if (!parsed.command) {
37595
- return {
37644
+ const result2 = {
37596
37645
  allowed: false,
37597
37646
  reason: "No valid command found",
37598
37647
  command
37599
37648
  };
37649
+ this.recordBashEvent("permission.denied", {
37650
+ command,
37651
+ reason: result2.reason,
37652
+ isComplex: false
37653
+ });
37654
+ return result2;
37600
37655
  }
37601
37656
  if (this.debug) {
37602
37657
  console.log(`[BashPermissions] Checking simple command: "${command}"`);
@@ -37604,22 +37659,37 @@ var init_bashPermissions = __esm({
37604
37659
  }
37605
37660
  if (matchesAnyPattern(parsed, this.denyPatterns)) {
37606
37661
  const matchedPatterns = this.denyPatterns.filter((pattern) => matchesPattern(parsed, pattern));
37607
- return {
37662
+ const result2 = {
37608
37663
  allowed: false,
37609
37664
  reason: `Command matches deny pattern: ${matchedPatterns[0]}`,
37610
37665
  command,
37611
37666
  parsed,
37612
37667
  matchedPatterns
37613
37668
  };
37669
+ this.recordBashEvent("permission.denied", {
37670
+ command,
37671
+ parsedCommand: parsed.command,
37672
+ reason: "matches_deny_pattern",
37673
+ matchedPattern: matchedPatterns[0],
37674
+ isComplex: false
37675
+ });
37676
+ return result2;
37614
37677
  }
37615
37678
  if (this.allowPatterns.length > 0) {
37616
37679
  if (!matchesAnyPattern(parsed, this.allowPatterns)) {
37617
- return {
37680
+ const result2 = {
37618
37681
  allowed: false,
37619
37682
  reason: "Command not in allow list",
37620
37683
  command,
37621
37684
  parsed
37622
37685
  };
37686
+ this.recordBashEvent("permission.denied", {
37687
+ command,
37688
+ parsedCommand: parsed.command,
37689
+ reason: "not_in_allow_list",
37690
+ isComplex: false
37691
+ });
37692
+ return result2;
37623
37693
  }
37624
37694
  }
37625
37695
  const result = {
@@ -37631,6 +37701,11 @@ var init_bashPermissions = __esm({
37631
37701
  if (this.debug) {
37632
37702
  console.log(`[BashPermissions] ALLOWED - command passed all checks`);
37633
37703
  }
37704
+ this.recordBashEvent("permission.allowed", {
37705
+ command,
37706
+ parsedCommand: parsed.command,
37707
+ isComplex: false
37708
+ });
37634
37709
  return result;
37635
37710
  }
37636
37711
  /**
@@ -37654,13 +37729,20 @@ var init_bashPermissions = __esm({
37654
37729
  if (this.debug) {
37655
37730
  console.log(`[BashPermissions] DENIED - matches complex deny pattern: ${pattern}`);
37656
37731
  }
37657
- return {
37732
+ const result = {
37658
37733
  allowed: false,
37659
37734
  reason: `Command matches deny pattern: ${pattern}`,
37660
37735
  command,
37661
37736
  isComplex: true,
37662
37737
  matchedPatterns: [pattern]
37663
37738
  };
37739
+ this.recordBashEvent("permission.denied", {
37740
+ command,
37741
+ reason: "matches_deny_pattern",
37742
+ matchedPattern: pattern,
37743
+ isComplex: true
37744
+ });
37745
+ return result;
37664
37746
  }
37665
37747
  }
37666
37748
  for (const pattern of complexAllowPatterns) {
@@ -37668,17 +37750,28 @@ var init_bashPermissions = __esm({
37668
37750
  if (this.debug) {
37669
37751
  console.log(`[BashPermissions] ALLOWED - matches complex allow pattern: ${pattern}`);
37670
37752
  }
37671
- return {
37753
+ const result = {
37672
37754
  allowed: true,
37673
37755
  command,
37674
37756
  isComplex: true,
37675
37757
  matchedPattern: pattern
37676
37758
  };
37759
+ this.recordBashEvent("permission.allowed", {
37760
+ command,
37761
+ matchedPattern: pattern,
37762
+ isComplex: true
37763
+ });
37764
+ return result;
37677
37765
  }
37678
37766
  }
37679
37767
  if (this.debug) {
37680
37768
  console.log(`[BashPermissions] DENIED - no matching complex pattern found`);
37681
37769
  }
37770
+ this.recordBashEvent("permission.denied", {
37771
+ command,
37772
+ reason: "no_matching_complex_pattern",
37773
+ isComplex: true
37774
+ });
37682
37775
  return {
37683
37776
  allowed: false,
37684
37777
  reason: 'Complex shell commands require explicit allow patterns (e.g., "cd * && git *")',
@@ -37977,14 +38070,16 @@ var init_bash = __esm({
37977
38070
  bashConfig = {},
37978
38071
  debug = false,
37979
38072
  cwd,
37980
- allowedFolders = []
38073
+ allowedFolders = [],
38074
+ tracer = null
37981
38075
  } = options;
37982
38076
  const permissionChecker = new BashPermissionChecker({
37983
38077
  allow: bashConfig.allow,
37984
38078
  deny: bashConfig.deny,
37985
38079
  disableDefaultAllow: bashConfig.disableDefaultAllow,
37986
38080
  disableDefaultDeny: bashConfig.disableDefaultDeny,
37987
- debug
38081
+ debug,
38082
+ tracer
37988
38083
  });
37989
38084
  const getDefaultWorkingDirectory = () => {
37990
38085
  if (bashConfig.workingDirectory) {
@@ -84601,6 +84696,47 @@ function validateTimeout(value) {
84601
84696
  if (!Number.isFinite(num) || num < 0) return void 0;
84602
84697
  return Math.min(num, MAX_TIMEOUT);
84603
84698
  }
84699
+ function validateMethodFilter(serverConfig, serverName = "unknown") {
84700
+ const result = { allowedMethods: null, blockedMethods: null };
84701
+ const debug = process.env.DEBUG === "1" || process.env.DEBUG_MCP === "1";
84702
+ if (serverConfig.allowedMethods && serverConfig.blockedMethods) {
84703
+ console.error(`[MCP WARN] Server '${serverName}' has both allowedMethods and blockedMethods - using allowedMethods only`);
84704
+ }
84705
+ if (serverConfig.allowedMethods) {
84706
+ if (!Array.isArray(serverConfig.allowedMethods)) {
84707
+ console.error(`[MCP WARN] Server '${serverName}' allowedMethods must be an array, ignoring`);
84708
+ } else {
84709
+ const validMethods = serverConfig.allowedMethods.filter((m4) => typeof m4 === "string" && m4.length > 0);
84710
+ if (validMethods.length !== serverConfig.allowedMethods.length) {
84711
+ console.error(`[MCP WARN] Server '${serverName}' allowedMethods contains non-string values, skipping those`);
84712
+ }
84713
+ if (validMethods.length > 0) {
84714
+ result.allowedMethods = validMethods;
84715
+ if (debug) {
84716
+ console.error(`[MCP DEBUG] Server '${serverName}' allowedMethods: ${validMethods.join(", ")}`);
84717
+ }
84718
+ }
84719
+ }
84720
+ return result;
84721
+ }
84722
+ if (serverConfig.blockedMethods) {
84723
+ if (!Array.isArray(serverConfig.blockedMethods)) {
84724
+ console.error(`[MCP WARN] Server '${serverName}' blockedMethods must be an array, ignoring`);
84725
+ } else {
84726
+ const validMethods = serverConfig.blockedMethods.filter((m4) => typeof m4 === "string" && m4.length > 0);
84727
+ if (validMethods.length !== serverConfig.blockedMethods.length) {
84728
+ console.error(`[MCP WARN] Server '${serverName}' blockedMethods contains non-string values, skipping those`);
84729
+ }
84730
+ if (validMethods.length > 0) {
84731
+ result.blockedMethods = validMethods;
84732
+ if (debug) {
84733
+ console.error(`[MCP DEBUG] Server '${serverName}' blockedMethods: ${validMethods.join(", ")}`);
84734
+ }
84735
+ }
84736
+ }
84737
+ }
84738
+ return result;
84739
+ }
84604
84740
  function loadMCPConfigurationFromPath(configPath) {
84605
84741
  if (!configPath) {
84606
84742
  throw new Error("Config path is required");
@@ -84694,6 +84830,12 @@ function mergeWithEnvironment(config) {
84694
84830
  console.error(`[MCP WARN] Invalid timeout value for ${normalizedName}: ${value}`);
84695
84831
  }
84696
84832
  break;
84833
+ case "ALLOWLIST":
84834
+ config.mcpServers[normalizedName].allowedMethods = value.split(",").map((m4) => m4.trim()).filter(Boolean);
84835
+ break;
84836
+ case "BLOCKLIST":
84837
+ config.mcpServers[normalizedName].blockedMethods = value.split(",").map((m4) => m4.trim()).filter(Boolean);
84838
+ break;
84697
84839
  }
84698
84840
  }
84699
84841
  }
@@ -84744,6 +84886,9 @@ function parseEnabledServers(config) {
84744
84886
  }
84745
84887
  server.timeout = validatedTimeout;
84746
84888
  }
84889
+ const methodFilter = validateMethodFilter(serverConfig, name14);
84890
+ server.allowedMethods = methodFilter.allowedMethods;
84891
+ server.blockedMethods = methodFilter.blockedMethods;
84747
84892
  servers.push(server);
84748
84893
  }
84749
84894
  return servers;
@@ -84781,6 +84926,22 @@ var init_config = __esm({
84781
84926
  });
84782
84927
 
84783
84928
  // src/agent/mcp/client.js
84929
+ function isMethodAllowed(methodName, allowedMethods, blockedMethods) {
84930
+ const matchesPattern2 = (name14, pattern) => {
84931
+ if (!pattern.includes("*")) {
84932
+ return name14 === pattern;
84933
+ }
84934
+ const regexPattern = pattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*/g, ".*");
84935
+ return new RegExp(`^${regexPattern}$`).test(name14);
84936
+ };
84937
+ if (allowedMethods && allowedMethods.length > 0) {
84938
+ return allowedMethods.some((pattern) => matchesPattern2(methodName, pattern));
84939
+ }
84940
+ if (blockedMethods && blockedMethods.length > 0) {
84941
+ return !blockedMethods.some((pattern) => matchesPattern2(methodName, pattern));
84942
+ }
84943
+ return true;
84944
+ }
84784
84945
  function createTransport(serverConfig) {
84785
84946
  const { transport, command, args, url, env } = serverConfig;
84786
84947
  switch (transport) {
@@ -84865,6 +85026,17 @@ var init_client2 = __esm({
84865
85026
  this.tools = /* @__PURE__ */ new Map();
84866
85027
  this.debug = options.debug || process.env.DEBUG_MCP === "1";
84867
85028
  this.config = null;
85029
+ this.tracer = options.tracer || null;
85030
+ }
85031
+ /**
85032
+ * Record an MCP telemetry event if tracer is available
85033
+ * @param {string} eventType - Event type (e.g., 'server.connect', 'tool.discovered')
85034
+ * @param {Object} data - Event data
85035
+ */
85036
+ recordMcpEvent(eventType, data2 = {}) {
85037
+ if (this.tracer && typeof this.tracer.recordMcpEvent === "function") {
85038
+ this.tracer.recordMcpEvent(eventType, data2);
85039
+ }
84868
85040
  }
84869
85041
  /**
84870
85042
  * Initialize MCP clients from configuration
@@ -84873,10 +85045,20 @@ var init_client2 = __esm({
84873
85045
  async initialize(config = null) {
84874
85046
  this.config = config || loadMCPConfiguration();
84875
85047
  const servers = parseEnabledServers(this.config);
85048
+ this.recordMcpEvent("initialization.started", {
85049
+ serverCount: servers.length,
85050
+ serverNames: servers.map((s4) => s4.name)
85051
+ });
84876
85052
  console.error(`[MCP INFO] Found ${servers.length} enabled MCP server${servers.length !== 1 ? "s" : ""}`);
84877
85053
  if (servers.length === 0) {
84878
85054
  console.error("[MCP INFO] No MCP servers configured or enabled");
84879
85055
  console.error("[MCP INFO] 0 MCP tools available");
85056
+ this.recordMcpEvent("initialization.completed", {
85057
+ connected: 0,
85058
+ total: 0,
85059
+ toolCount: 0,
85060
+ tools: []
85061
+ });
84880
85062
  return {
84881
85063
  connected: 0,
84882
85064
  total: 0,
@@ -84913,10 +85095,17 @@ var init_client2 = __esm({
84913
85095
  console.error(`[MCP DEBUG] - ${toolName}`);
84914
85096
  });
84915
85097
  }
85098
+ const toolNames = Array.from(this.tools.keys());
85099
+ this.recordMcpEvent("initialization.completed", {
85100
+ connected: connectedCount,
85101
+ total: servers.length,
85102
+ toolCount: this.tools.size,
85103
+ tools: toolNames
85104
+ });
84916
85105
  return {
84917
85106
  connected: connectedCount,
84918
85107
  total: servers.length,
84919
- tools: Array.from(this.tools.keys())
85108
+ tools: toolNames
84920
85109
  };
84921
85110
  }
84922
85111
  /**
@@ -84925,6 +85114,12 @@ var init_client2 = __esm({
84925
85114
  */
84926
85115
  async connectToServer(serverConfig) {
84927
85116
  const { name: name14 } = serverConfig;
85117
+ this.recordMcpEvent("server.connecting", {
85118
+ serverName: name14,
85119
+ transport: serverConfig.transport,
85120
+ hasAllowedMethods: !!(serverConfig.allowedMethods && serverConfig.allowedMethods.length > 0),
85121
+ hasBlockedMethods: !!(serverConfig.blockedMethods && serverConfig.blockedMethods.length > 0)
85122
+ });
84928
85123
  try {
84929
85124
  if (this.debug) {
84930
85125
  console.error(`[MCP DEBUG] Connecting to ${name14} via ${serverConfig.transport}...`);
@@ -84946,27 +85141,92 @@ var init_client2 = __esm({
84946
85141
  config: serverConfig
84947
85142
  });
84948
85143
  const toolsResponse = await client.listTools();
84949
- const toolCount = toolsResponse?.tools?.length || 0;
85144
+ const totalToolCount = toolsResponse?.tools?.length || 0;
85145
+ let registeredCount = 0;
85146
+ let filteredCount = 0;
85147
+ const registeredTools = [];
85148
+ const filteredTools = [];
84950
85149
  if (toolsResponse && toolsResponse.tools) {
85150
+ const { allowedMethods, blockedMethods } = serverConfig;
85151
+ const allToolNames = toolsResponse.tools.map((t4) => t4.name);
85152
+ this.recordMcpEvent("tools.discovered", {
85153
+ serverName: name14,
85154
+ toolCount: totalToolCount,
85155
+ tools: allToolNames
85156
+ });
84951
85157
  for (const tool4 of toolsResponse.tools) {
85158
+ if (!isMethodAllowed(tool4.name, allowedMethods, blockedMethods)) {
85159
+ filteredCount++;
85160
+ filteredTools.push(tool4.name);
85161
+ if (this.debug) {
85162
+ console.error(`[MCP DEBUG] Filtered out tool: ${tool4.name} (not allowed by method filter)`);
85163
+ }
85164
+ continue;
85165
+ }
84952
85166
  const qualifiedName = `${name14}_${tool4.name}`;
84953
85167
  this.tools.set(qualifiedName, {
84954
85168
  ...tool4,
84955
85169
  serverName: name14,
84956
85170
  originalName: tool4.name
84957
85171
  });
85172
+ registeredCount++;
85173
+ registeredTools.push(qualifiedName);
84958
85174
  if (this.debug) {
84959
85175
  console.error(`[MCP DEBUG] Registered tool: ${qualifiedName}`);
84960
85176
  }
84961
85177
  }
85178
+ if (filteredCount > 0) {
85179
+ this.recordMcpEvent("tools.filtered", {
85180
+ serverName: name14,
85181
+ filteredCount,
85182
+ filteredTools,
85183
+ allowedMethods: allowedMethods || [],
85184
+ blockedMethods: blockedMethods || []
85185
+ });
85186
+ }
85187
+ if (allowedMethods && allowedMethods.length > 0) {
85188
+ const unmatchedPatterns = allowedMethods.filter((pattern) => {
85189
+ return !allToolNames.some((toolName) => isMethodAllowed(toolName, [pattern], null));
85190
+ });
85191
+ if (unmatchedPatterns.length > 0) {
85192
+ console.error(`[MCP WARN] Server '${name14}': The following allowedMethods patterns did not match any tools: ${unmatchedPatterns.join(", ")}`);
85193
+ console.error(`[MCP WARN] Available methods from '${name14}': ${allToolNames.join(", ")}`);
85194
+ }
85195
+ }
85196
+ if (blockedMethods && blockedMethods.length > 0) {
85197
+ const unmatchedPatterns = blockedMethods.filter((pattern) => {
85198
+ return !allToolNames.some((toolName) => !isMethodAllowed(toolName, null, [pattern]));
85199
+ });
85200
+ if (unmatchedPatterns.length > 0) {
85201
+ console.error(`[MCP WARN] Server '${name14}': The following blockedMethods patterns did not match any tools: ${unmatchedPatterns.join(", ")}`);
85202
+ console.error(`[MCP WARN] Available methods from '${name14}': ${allToolNames.join(", ")}`);
85203
+ }
85204
+ }
84962
85205
  }
84963
- console.error(`[MCP INFO] Connected to ${name14}: ${toolCount} tool${toolCount !== 1 ? "s" : ""} loaded`);
85206
+ if (filteredCount > 0) {
85207
+ console.error(`[MCP INFO] Connected to ${name14}: ${registeredCount} tool${registeredCount !== 1 ? "s" : ""} loaded (${filteredCount} filtered out)`);
85208
+ } else {
85209
+ console.error(`[MCP INFO] Connected to ${name14}: ${registeredCount} tool${registeredCount !== 1 ? "s" : ""} loaded`);
85210
+ }
85211
+ this.recordMcpEvent("server.connected", {
85212
+ serverName: name14,
85213
+ transport: serverConfig.transport,
85214
+ totalToolCount,
85215
+ registeredCount,
85216
+ filteredCount,
85217
+ registeredTools
85218
+ });
84964
85219
  return true;
84965
85220
  } catch (error2) {
84966
85221
  console.error(`[MCP ERROR] Error connecting to ${name14}:`, error2.message);
84967
85222
  if (this.debug) {
84968
85223
  console.error(`[MCP DEBUG] Full error details:`, error2);
84969
85224
  }
85225
+ this.recordMcpEvent("server.connection_failed", {
85226
+ serverName: name14,
85227
+ transport: serverConfig.transport,
85228
+ error: error2.message
85229
+ });
84970
85230
  return false;
84971
85231
  }
84972
85232
  }
@@ -84978,12 +85238,27 @@ var init_client2 = __esm({
84978
85238
  async callTool(toolName, args) {
84979
85239
  const tool4 = this.tools.get(toolName);
84980
85240
  if (!tool4) {
85241
+ this.recordMcpEvent("tool.call_failed", {
85242
+ toolName,
85243
+ error: "Unknown tool"
85244
+ });
84981
85245
  throw new Error(`Unknown tool: ${toolName}`);
84982
85246
  }
84983
85247
  const clientInfo = this.clients.get(tool4.serverName);
84984
85248
  if (!clientInfo) {
85249
+ this.recordMcpEvent("tool.call_failed", {
85250
+ toolName,
85251
+ serverName: tool4.serverName,
85252
+ error: "Server not connected"
85253
+ });
84985
85254
  throw new Error(`Server ${tool4.serverName} not connected`);
84986
85255
  }
85256
+ const startTime = Date.now();
85257
+ this.recordMcpEvent("tool.call_started", {
85258
+ toolName,
85259
+ serverName: tool4.serverName,
85260
+ originalToolName: tool4.originalName
85261
+ });
84987
85262
  try {
84988
85263
  if (this.debug) {
84989
85264
  console.error(`[MCP DEBUG] Calling ${toolName} with args:`, JSON.stringify(args, null, 2));
@@ -85003,15 +85278,31 @@ var init_client2 = __esm({
85003
85278
  }),
85004
85279
  timeoutPromise
85005
85280
  ]);
85281
+ const durationMs = Date.now() - startTime;
85006
85282
  if (this.debug) {
85007
85283
  console.error(`[MCP DEBUG] Tool ${toolName} executed successfully`);
85008
85284
  }
85285
+ this.recordMcpEvent("tool.call_completed", {
85286
+ toolName,
85287
+ serverName: tool4.serverName,
85288
+ originalToolName: tool4.originalName,
85289
+ durationMs
85290
+ });
85009
85291
  return result;
85010
85292
  } catch (error2) {
85293
+ const durationMs = Date.now() - startTime;
85011
85294
  console.error(`[MCP ERROR] Error calling tool ${toolName}:`, error2.message);
85012
85295
  if (this.debug) {
85013
85296
  console.error(`[MCP DEBUG] Full error details:`, error2);
85014
85297
  }
85298
+ this.recordMcpEvent("tool.call_failed", {
85299
+ toolName,
85300
+ serverName: tool4.serverName,
85301
+ originalToolName: tool4.originalName,
85302
+ error: error2.message,
85303
+ durationMs,
85304
+ isTimeout: error2.message.includes("timeout")
85305
+ });
85015
85306
  throw error2;
85016
85307
  }
85017
85308
  }
@@ -85056,12 +85347,17 @@ var init_client2 = __esm({
85056
85347
  */
85057
85348
  async disconnect() {
85058
85349
  const disconnectPromises = [];
85350
+ const serverNames = Array.from(this.clients.keys());
85059
85351
  if (this.clients.size === 0) {
85060
85352
  if (this.debug) {
85061
85353
  console.error("[MCP DEBUG] No MCP clients to disconnect");
85062
85354
  }
85063
85355
  return;
85064
85356
  }
85357
+ this.recordMcpEvent("disconnection.started", {
85358
+ serverCount: this.clients.size,
85359
+ serverNames
85360
+ });
85065
85361
  if (this.debug) {
85066
85362
  console.error(`[MCP DEBUG] Disconnecting from ${this.clients.size} MCP server${this.clients.size !== 1 ? "s" : ""}...`);
85067
85363
  }
@@ -85071,14 +85367,25 @@ var init_client2 = __esm({
85071
85367
  if (this.debug) {
85072
85368
  console.error(`[MCP DEBUG] Disconnected from ${name14}`);
85073
85369
  }
85370
+ this.recordMcpEvent("server.disconnected", {
85371
+ serverName: name14
85372
+ });
85074
85373
  }).catch((error2) => {
85075
85374
  console.error(`[MCP ERROR] Error disconnecting from ${name14}:`, error2.message);
85375
+ this.recordMcpEvent("server.disconnect_failed", {
85376
+ serverName: name14,
85377
+ error: error2.message
85378
+ });
85076
85379
  })
85077
85380
  );
85078
85381
  }
85079
85382
  await Promise.all(disconnectPromises);
85080
85383
  this.clients.clear();
85081
85384
  this.tools.clear();
85385
+ this.recordMcpEvent("disconnection.completed", {
85386
+ serverCount: serverNames.length,
85387
+ serverNames
85388
+ });
85082
85389
  if (this.debug) {
85083
85390
  console.error("[MCP DEBUG] All MCP connections closed");
85084
85391
  }
@@ -85222,6 +85529,7 @@ var init_xmlBridge = __esm({
85222
85529
  MCPXmlBridge = class {
85223
85530
  constructor(options = {}) {
85224
85531
  this.debug = options.debug || false;
85532
+ this.tracer = options.tracer || null;
85225
85533
  this.mcpTools = {};
85226
85534
  this.mcpManager = null;
85227
85535
  this.xmlDefinitions = {};
@@ -85264,7 +85572,7 @@ var init_xmlBridge = __esm({
85264
85572
  if (this.debug) {
85265
85573
  console.error("[MCP DEBUG] Initializing MCP client manager...");
85266
85574
  }
85267
- this.mcpManager = new MCPClientManager({ debug: this.debug });
85575
+ this.mcpManager = new MCPClientManager({ debug: this.debug, tracer: this.tracer });
85268
85576
  const result = await this.mcpManager.initialize(mcpConfigs);
85269
85577
  const vercelTools = this.mcpManager.getVercelTools();
85270
85578
  this.mcpTools = vercelTools;
@@ -85288,11 +85596,15 @@ var init_xmlBridge = __esm({
85288
85596
  }
85289
85597
  }
85290
85598
  /**
85291
- * Get all XML tool definitions for inclusion in system prompt
85599
+ * Get XML tool definitions for inclusion in system prompt
85600
+ * @param {Array<string>|null} filterToolNames - Optional list of tool names to include (if null, include all)
85292
85601
  * @returns {string} Combined XML tool definitions
85293
85602
  */
85294
- getXmlToolDefinitions() {
85295
- return Object.values(this.xmlDefinitions).join("\n\n");
85603
+ getXmlToolDefinitions(filterToolNames = null) {
85604
+ if (filterToolNames === null) {
85605
+ return Object.values(this.xmlDefinitions).join("\n\n");
85606
+ }
85607
+ return Object.entries(this.xmlDefinitions).filter(([name14]) => filterToolNames.includes(name14)).map(([, def]) => def).join("\n\n");
85296
85608
  }
85297
85609
  /**
85298
85610
  * Get list of MCP tool names
@@ -95379,6 +95691,27 @@ __export(ProbeAgent_exports, {
95379
95691
  ProbeAgent: () => ProbeAgent
95380
95692
  });
95381
95693
  module.exports = __toCommonJS(ProbeAgent_exports);
95694
+ function extractWrappedToolName(wrappedToolError) {
95695
+ if (!wrappedToolError || typeof wrappedToolError !== "string") {
95696
+ return "unknown";
95697
+ }
95698
+ const colonIndex = wrappedToolError.indexOf(":");
95699
+ return colonIndex !== -1 ? wrappedToolError.slice(colonIndex + 1) : "unknown";
95700
+ }
95701
+ function isWrappedToolError(error2) {
95702
+ return error2 && typeof error2 === "string" && error2.startsWith("wrapped_tool:");
95703
+ }
95704
+ function createWrappedToolErrorMessage(wrappedToolName) {
95705
+ return `Your response contained an incorrectly formatted tool call (${wrappedToolName} wrapped in XML tags). This cannot be used.
95706
+
95707
+ Please use the CORRECT format:
95708
+
95709
+ <${wrappedToolName}>
95710
+ Your content here
95711
+ </${wrappedToolName}>
95712
+
95713
+ Do NOT wrap in other tags like <api_call>, <tool_name>, <function>, etc.`;
95714
+ }
95382
95715
  var import_dotenv2, import_anthropic2, import_openai2, import_google2, import_ai5, import_crypto9, import_events4, import_fs14, import_promises6, import_path17, MAX_TOOL_ITERATIONS, MAX_HISTORY_MESSAGES, MAX_IMAGE_FILE_SIZE, ProbeAgent;
95383
95716
  var init_ProbeAgent = __esm({
95384
95717
  "src/agent/ProbeAgent.js"() {
@@ -97388,6 +97721,9 @@ You are working with a repository located at: ${searchDirectory}
97388
97721
  console.log(`[DEBUG] Schema provided, using extended iteration limit: ${maxIterations} (base: ${baseMaxIterations})`);
97389
97722
  }
97390
97723
  }
97724
+ let lastFormatErrorType = null;
97725
+ let sameFormatErrorCount = 0;
97726
+ const MAX_REPEATED_FORMAT_ERRORS = 3;
97391
97727
  while (currentIteration < maxIterations && !completionAttempted) {
97392
97728
  currentIteration++;
97393
97729
  if (this.cancelled) throw new Error("Request was cancelled by the user");
@@ -97586,7 +97922,22 @@ You are working with a repository located at: ${searchDirectory}
97586
97922
  (msg) => msg.role === "assistant" && msg.content && !(this.mcpBridge ? parseHybridXmlToolCall(msg.content, validTools, this.mcpBridge) : parseXmlToolCallWithThinking(msg.content, validTools))
97587
97923
  );
97588
97924
  if (lastAssistantMessage) {
97589
- finalResult = lastAssistantMessage.content;
97925
+ const prevContent = lastAssistantMessage.content;
97926
+ const wrappedToolError = detectUnrecognizedToolCall(prevContent, validTools);
97927
+ if (isWrappedToolError(wrappedToolError)) {
97928
+ const wrappedToolName = extractWrappedToolName(wrappedToolError);
97929
+ if (this.debug) {
97930
+ console.log(`[DEBUG] Previous response contains wrapped tool '${wrappedToolName}' - rejecting for __PREVIOUS_RESPONSE__`);
97931
+ }
97932
+ currentMessages.push({ role: "assistant", content: assistantResponseContent });
97933
+ currentMessages.push({
97934
+ role: "user",
97935
+ content: createWrappedToolErrorMessage(wrappedToolName)
97936
+ });
97937
+ completionAttempted = false;
97938
+ continue;
97939
+ }
97940
+ finalResult = prevContent;
97590
97941
  if (this.debug) console.log(`[DEBUG] Using previous response as completion: ${finalResult.substring(0, 100)}...`);
97591
97942
  } else {
97592
97943
  finalResult = "Error: No previous response found to use as completion.";
@@ -97857,7 +98208,33 @@ ${errorXml}
97857
98208
  currentMessages.push({ role: "assistant", content: assistantResponseContent });
97858
98209
  const unrecognizedTool = detectUnrecognizedToolCall(assistantResponseContent, validTools);
97859
98210
  let reminderContent;
97860
- if (unrecognizedTool) {
98211
+ if (isWrappedToolError(unrecognizedTool)) {
98212
+ const wrappedToolName = extractWrappedToolName(unrecognizedTool);
98213
+ if (this.debug) {
98214
+ console.log(`[DEBUG] Detected wrapped tool '${wrappedToolName}' in assistant response - wrong XML format.`);
98215
+ }
98216
+ const toolError = new ParameterError(
98217
+ `Tool '${wrappedToolName}' found but in WRONG FORMAT - do not wrap tools in other XML tags.`,
98218
+ {
98219
+ suggestion: `Use the tool tag DIRECTLY without any wrapper:
98220
+
98221
+ CORRECT FORMAT:
98222
+ <${wrappedToolName}>
98223
+ <param>value</param>
98224
+ </${wrappedToolName}>
98225
+
98226
+ WRONG (what you did - do not wrap in other tags):
98227
+ <api_call><tool_name>${wrappedToolName}</tool_name>...</api_call>
98228
+ <function>${wrappedToolName}</function>
98229
+ <call name="${wrappedToolName}">...</call>
98230
+
98231
+ Remove ALL wrapper tags and use <${wrappedToolName}> directly as the outermost tag.`
98232
+ }
98233
+ );
98234
+ reminderContent = `<tool_result>
98235
+ ${formatErrorForAI(toolError)}
98236
+ </tool_result>`;
98237
+ } else if (unrecognizedTool) {
97861
98238
  if (this.debug) {
97862
98239
  console.log(`[DEBUG] Detected unrecognized tool '${unrecognizedTool}' in assistant response.`);
97863
98240
  }
@@ -97868,6 +98245,20 @@ ${errorXml}
97868
98245
  ${formatErrorForAI(toolError)}
97869
98246
  </tool_result>`;
97870
98247
  } else {
98248
+ if (currentIteration >= maxIterations) {
98249
+ let cleanedResponse = assistantResponseContent;
98250
+ cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*?<\/thinking>/gi, "").trim();
98251
+ cleanedResponse = cleanedResponse.replace(/<thinking>[\s\S]*$/gi, "").trim();
98252
+ const hasSubstantialContent = cleanedResponse.length > 50 && !cleanedResponse.includes("<api_call>") && !cleanedResponse.includes("<tool_name>") && !cleanedResponse.includes("<function>");
98253
+ if (hasSubstantialContent) {
98254
+ if (this.debug) {
98255
+ console.log(`[DEBUG] Max iterations reached - accepting AI response as final answer (${cleanedResponse.length} chars)`);
98256
+ }
98257
+ finalResult = cleanedResponse;
98258
+ completionAttempted = true;
98259
+ break;
98260
+ }
98261
+ }
97871
98262
  reminderContent = `Please use one of the available tools to help answer the question, or use attempt_completion if you have enough information to provide a final answer.
97872
98263
 
97873
98264
  Remember: Use proper XML format with BOTH opening and closing tags:
@@ -97897,6 +98288,25 @@ Note: <attempt_complete></attempt_complete> reuses your PREVIOUS assistant messa
97897
98288
  console.log(`[DEBUG] No tool call detected in assistant response. Prompting for tool use.`);
97898
98289
  }
97899
98290
  }
98291
+ if (unrecognizedTool) {
98292
+ const isWrapped = isWrappedToolError(unrecognizedTool);
98293
+ const errorCategory = isWrapped ? "wrapped_tool" : unrecognizedTool;
98294
+ if (errorCategory === lastFormatErrorType) {
98295
+ sameFormatErrorCount++;
98296
+ if (sameFormatErrorCount >= MAX_REPEATED_FORMAT_ERRORS) {
98297
+ const errorDesc = isWrapped ? "wrapped tool format" : unrecognizedTool;
98298
+ console.error(`[ERROR] Format error category '${errorCategory}' repeated ${sameFormatErrorCount} times. Breaking loop early to prevent infinite iteration.`);
98299
+ finalResult = `Error: Unable to complete request. The AI model repeatedly used incorrect tool call format (${errorDesc}). Please try rephrasing your question or using a different model.`;
98300
+ break;
98301
+ }
98302
+ } else {
98303
+ lastFormatErrorType = errorCategory;
98304
+ sameFormatErrorCount = 1;
98305
+ }
98306
+ } else {
98307
+ lastFormatErrorType = null;
98308
+ sameFormatErrorCount = 0;
98309
+ }
97900
98310
  }
97901
98311
  if (currentMessages.length > MAX_HISTORY_MESSAGES) {
97902
98312
  const messagesBefore = currentMessages.length;