@bike4mind/cli 0.2.37 → 0.2.38-add-notebook-project-fixes.20160

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.
@@ -4,7 +4,7 @@ import {
4
4
  getOpenWeatherKey,
5
5
  getSerperKey,
6
6
  getWolframAlphaKey
7
- } from "./chunk-NJQYWIDJ.js";
7
+ } from "./chunk-J3YSVA6W.js";
8
8
  import {
9
9
  BFLImageService,
10
10
  BaseStorage,
@@ -16,14 +16,14 @@ import {
16
16
  OpenAIBackend,
17
17
  OpenAIImageService,
18
18
  XAIImageService
19
- } from "./chunk-5YLGNW2B.js";
19
+ } from "./chunk-QCDYXXSD.js";
20
20
  import {
21
21
  Logger
22
22
  } from "./chunk-PFBYGCOW.js";
23
23
  import {
24
24
  ConfigStore,
25
25
  logger
26
- } from "./chunk-H7RVLAQD.js";
26
+ } from "./chunk-HJVFUXVX.js";
27
27
  import {
28
28
  AiEvents,
29
29
  ApiKeyEvents,
@@ -80,7 +80,7 @@ import {
80
80
  getMcpProviderMetadata,
81
81
  getViewById,
82
82
  resolveNavigationIntents
83
- } from "./chunk-EY65E4W4.js";
83
+ } from "./chunk-OCFOJXLA.js";
84
84
 
85
85
  // src/utils/fileSearch.ts
86
86
  import * as fs from "fs";
@@ -1846,7 +1846,8 @@ ${options.context}` : this.getSystemPrompt()
1846
1846
  temperature,
1847
1847
  abortSignal: options.signal,
1848
1848
  tool_choice: this.context.toolChoice,
1849
- executeTools: false
1849
+ executeTools: false,
1850
+ thinking: this.context.thinking
1850
1851
  },
1851
1852
  async (texts, completionInfo) => {
1852
1853
  for (const text of texts) {
@@ -2146,34 +2147,20 @@ Remember: You are an autonomous AGENT. Act independently and solve problems proa
2146
2147
  }
2147
2148
  }
2148
2149
  /**
2149
- * Build and append tool call/result messages for the conversation history
2150
+ * Build and append tool call/result messages for the conversation history.
2151
+ * Delegates to the backend's pushToolMessages so each provider formats
2152
+ * messages according to its own API requirements.
2150
2153
  */
2151
2154
  appendToolMessages(messages, toolUse, observation, thinkingBlocks) {
2152
2155
  const params = this.parseToolArguments(toolUse.arguments);
2153
- const msgToolCallId = `${toolUse.name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2154
- const assistantContent = [
2155
- ...thinkingBlocks,
2156
- {
2157
- type: "tool_use",
2158
- id: msgToolCallId,
2159
- name: toolUse.name,
2160
- input: params
2161
- }
2162
- ];
2163
- messages.push({
2164
- role: "assistant",
2165
- content: assistantContent
2166
- });
2167
- messages.push({
2168
- role: "user",
2169
- content: [
2170
- {
2171
- type: "tool_result",
2172
- tool_use_id: msgToolCallId,
2173
- content: observation
2174
- }
2175
- ]
2176
- });
2156
+ const toolId = toolUse.id || `${toolUse.name}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
2157
+ const parameters = typeof toolUse.arguments === "string" ? toolUse.arguments : JSON.stringify(params);
2158
+ this.context.llm.pushToolMessages(
2159
+ messages,
2160
+ { id: toolId, name: toolUse.name, parameters },
2161
+ observation,
2162
+ thinkingBlocks
2163
+ );
2177
2164
  }
2178
2165
  };
2179
2166
 
@@ -8745,7 +8732,7 @@ var knowledgeBaseSearchTool = {
8745
8732
  toolFn: async (value) => {
8746
8733
  const params = value;
8747
8734
  const { query, tags, file_type, max_results = 5 } = params;
8748
- context.logger.log("\u{1F4DA} Knowledge Base Search: Starting search for query:", query);
8735
+ context.logger.log("\u{1F4DA} Knowledge Base Search: userId:", context.userId, "query:", query, "tags:", tags);
8749
8736
  if (!context.db.fabfiles) {
8750
8737
  context.logger.error("\u274C Knowledge Base Search: fabfiles repository not available");
8751
8738
  return "Knowledge base search is not available at this time.";
@@ -8771,7 +8758,7 @@ var knowledgeBaseSearchTool = {
8771
8758
  userGroups: context.user.groups || []
8772
8759
  // Pass user's groups for org-level sharing
8773
8760
  });
8774
- context.logger.log("\u{1F4DA} Knowledge Base Search: Found", searchResults.data.length, "results");
8761
+ context.logger.log("\u{1F4DA} Knowledge Base Search: Found", searchResults.data.length, "of", searchResults.total, "results. Files:", searchResults.data.map((f) => f.fileName));
8775
8762
  return formatSearchResults(searchResults.data);
8776
8763
  } catch (error) {
8777
8764
  context.logger.error("\u274C Knowledge Base Search: Error during search:", error);
@@ -8791,7 +8778,7 @@ var knowledgeBaseSearchTool = {
8791
8778
  tags: {
8792
8779
  type: "array",
8793
8780
  items: { type: "string" },
8794
- description: 'Optional: filter results by tag names. Supports partial matching. For optimization docs, use tags like "opti:family:scheduling", "opti:QUBO", "opti:solver:highs", etc. Any matching tag qualifies the file.'
8781
+ description: 'Optional: filter results by tag names. Supports partial matching. For optimization docs, use tags like "opti:family:scheduling", "opti:QUBO", "opti:solver:highs". For IonQ sales intelligence, use tags like "ionq:vertical:pharma", "ionq:competitor:ibm", "ionq:type:product-specs", "ionq:stage:discovery", "ionq:offering:forte". Any matching tag qualifies the file.'
8795
8782
  },
8796
8783
  file_type: {
8797
8784
  type: "string",
@@ -8904,7 +8891,7 @@ Chunks: ${chunks.length} | Characters: ${charLabel}
8904
8891
  tags: {
8905
8892
  type: "array",
8906
8893
  items: { type: "string" },
8907
- description: 'Filter documents by tags. For optimization docs, use tags like "opti:family:scheduling", "opti:solver:highs", etc.'
8894
+ description: 'Filter documents by tags. For optimization docs, use tags like "opti:family:scheduling", "opti:solver:highs". For IonQ sales intelligence, use tags like "ionq:vertical:pharma", "ionq:competitor:ibm", "ionq:content:product-specs", "ionq:offering:forte".'
8908
8895
  },
8909
8896
  query: {
8910
8897
  type: "string",
@@ -13280,6 +13267,61 @@ var generateMcpTools = async (mcpData) => {
13280
13267
  Logger.debug(`\u{1F527} generateMcpTools: Generated ${result.length} tool implementations for ${mcpData.serverName}`);
13281
13268
  return result;
13282
13269
  };
13270
+ var generateMcpToolsFromCache = (serverName, cachedTools, callTool) => {
13271
+ const normalizedServerName = serverName.toLowerCase();
13272
+ const result = cachedTools.map((item) => {
13273
+ const { name: originalToolName, ...rest } = item;
13274
+ const namespacedToolName = `${normalizedServerName}__${originalToolName}`;
13275
+ const providerMetadata = getMcpProviderMetadata(normalizedServerName);
13276
+ const fallbackDescription = providerMetadata?.defaultToolDescriptions?.[originalToolName] ?? "";
13277
+ const parameters = normalizeToolParameters(rest);
13278
+ const optionTools = {
13279
+ toolFn: async (args) => {
13280
+ Logger.debug(`Calling ${originalToolName} tool via ${serverName}`, args);
13281
+ try {
13282
+ const toolResult = await callTool(originalToolName, args);
13283
+ const contentBlocks = toolResult?.content;
13284
+ if (Array.isArray(contentBlocks) && contentBlocks.length > 0) {
13285
+ const normalized = contentBlocks.map((entry) => {
13286
+ if (entry && typeof entry === "object" && "text" in entry) {
13287
+ return entry.text;
13288
+ }
13289
+ return JSON.stringify(entry);
13290
+ }).join("\n");
13291
+ Logger.debug(`[Tool Result] ${originalToolName}:`, normalized);
13292
+ return normalized;
13293
+ }
13294
+ const serialized = typeof toolResult === "string" ? toolResult : JSON.stringify(toolResult);
13295
+ Logger.debug(`[Tool Result] Unexpected format for ${originalToolName}, returning serialized output`);
13296
+ return serialized;
13297
+ } catch (error) {
13298
+ if (normalizedServerName === "atlassian") {
13299
+ const errorName = error instanceof Error ? error.name : "";
13300
+ const errorMessage = error instanceof Error ? error.message : String(error);
13301
+ const isTokenError = errorName === "AtlassianReconnectRequiredError" || errorMessage.includes("401") || errorMessage.includes("403") || errorMessage.includes("unauthorized") || errorMessage.includes("expired");
13302
+ if (isTokenError) {
13303
+ console.warn(`Atlassian token may be expired for tool ${originalToolName}, error:`, errorMessage);
13304
+ return ATLASSIAN_RECONNECT_MESSAGE;
13305
+ }
13306
+ }
13307
+ throw error;
13308
+ }
13309
+ },
13310
+ toolSchema: {
13311
+ name: namespacedToolName,
13312
+ description: rest.description || fallbackDescription,
13313
+ parameters
13314
+ }
13315
+ };
13316
+ return {
13317
+ name: namespacedToolName,
13318
+ ...optionTools,
13319
+ _isMcpTool: true
13320
+ };
13321
+ });
13322
+ Logger.debug(`\u{1F527} generateMcpToolsFromCache: Generated ${result.length} tool implementations for ${serverName} (from cache)`);
13323
+ return result;
13324
+ };
13283
13325
 
13284
13326
  // ../../b4m-core/packages/services/dist/src/llm/agents/ServerSubagentOrchestrator.js
13285
13327
  var SUBAGENT_TIMEOUT_MS = 5 * 60 * 1e3;
@@ -13382,7 +13424,6 @@ var GithubManagerAgent = (config) => ({
13382
13424
  name: "github_manager",
13383
13425
  description: "GitHub operations (issues, pull requests, code search, branches, workflows, reviews). ALWAYS delegate GitHub requests to this agent \u2014 you do not have direct access to these tools",
13384
13426
  model: config?.model ?? ChatModels.CLAUDE_4_6_SONNET_BEDROCK,
13385
- fallbackModels: [ChatModels.GPT4_1, ChatModels.GPT4_1_MINI],
13386
13427
  defaultThoroughness: config?.defaultThoroughness ?? "medium",
13387
13428
  maxIterations: { quick: 3, medium: 8, very_thorough: 15 },
13388
13429
  allowedTools: [...config?.extraAllowedTools ?? []],
@@ -15238,6 +15279,12 @@ function substituteArguments(template, args) {
15238
15279
  return result;
15239
15280
  }
15240
15281
 
15282
+ // src/utils/mcpAdapter.ts
15283
+ import { createHash } from "crypto";
15284
+ import { readFile as readFile2, writeFile, mkdir } from "fs/promises";
15285
+ import { homedir as homedir4 } from "os";
15286
+ import { join as join3, dirname as dirname2 } from "path";
15287
+
15241
15288
  // ../../b4m-core/packages/mcp/dist/src/client.js
15242
15289
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
15243
15290
  import { Client as Client2 } from "@modelcontextprotocol/sdk/client/index.js";
@@ -15251,15 +15298,17 @@ var MCPClient = class {
15251
15298
  customCommand;
15252
15299
  customArgs;
15253
15300
  suppressStderr;
15301
+ onStderrLine;
15254
15302
  tools = [];
15255
15303
  serverName;
15256
- constructor({ envVariables, name, selectedRepositories, command, args, suppressStderr = false }) {
15304
+ constructor({ envVariables, name, selectedRepositories, command, args, suppressStderr = false, onStderrLine }) {
15257
15305
  this.mcp = new Client2({ name: "mcp-client-cli", version: "1.0.0" });
15258
15306
  this.envVariables = [...envVariables];
15259
15307
  this.serverName = name;
15260
15308
  this.customCommand = command;
15261
15309
  this.customArgs = args;
15262
15310
  this.suppressStderr = suppressStderr;
15311
+ this.onStderrLine = onStderrLine;
15263
15312
  if (name === "github" && selectedRepositories) {
15264
15313
  const reposJson = JSON.stringify(selectedRepositories);
15265
15314
  this.envVariables.push({
@@ -15305,6 +15354,7 @@ var MCPClient = class {
15305
15354
  args = [serverScriptPath];
15306
15355
  console.log(`[MCP] Using server: ${this.serverName} at ${serverScriptPath}`);
15307
15356
  }
15357
+ const stderrMode = this.suppressStderr ? "ignore" : this.onStderrLine ? "pipe" : void 0;
15308
15358
  const transportConfig = {
15309
15359
  command,
15310
15360
  args,
@@ -15312,14 +15362,16 @@ var MCPClient = class {
15312
15362
  ...Object.fromEntries(Object.entries(process.env).filter((entry) => entry[1] !== void 0)),
15313
15363
  ...envVarsObject
15314
15364
  },
15315
- // Suppress stderr in CLI context to prevent MCP server startup logs from breaking layout
15316
- ...this.suppressStderr && { stderr: "ignore" }
15365
+ ...stderrMode && { stderr: stderrMode }
15317
15366
  };
15318
15367
  this.transport = new StdioClientTransport(transportConfig);
15319
15368
  this.transport.onerror = (error) => {
15320
15369
  console.error(`[MCP] Transport error for ${this.serverName}:`, error);
15321
15370
  };
15322
15371
  await this.mcp.connect(this.transport);
15372
+ if (this.onStderrLine && this.transport.stderr) {
15373
+ this.readStderr(this.transport.stderr);
15374
+ }
15323
15375
  await new Promise((resolve3) => setTimeout(resolve3, 100));
15324
15376
  const toolsResult = await this.mcp.listTools();
15325
15377
  this.tools = toolsResult.tools.map((tool) => {
@@ -15341,6 +15393,30 @@ var MCPClient = class {
15341
15393
  throw e;
15342
15394
  }
15343
15395
  }
15396
+ /**
15397
+ * Read lines from the child process stderr stream and dispatch to the callback.
15398
+ * Uses Node.js stream events; errors are silently caught (stream closes on disconnect).
15399
+ */
15400
+ readStderr(stream) {
15401
+ let buffer = "";
15402
+ stream.setEncoding("utf8");
15403
+ stream.on("data", (chunk) => {
15404
+ buffer += chunk;
15405
+ const lines = buffer.split("\n");
15406
+ buffer = lines.pop() || "";
15407
+ for (const line of lines) {
15408
+ const trimmed = line.trim();
15409
+ if (trimmed)
15410
+ this.onStderrLine?.(trimmed);
15411
+ }
15412
+ });
15413
+ stream.on("end", () => {
15414
+ if (buffer.trim())
15415
+ this.onStderrLine?.(buffer.trim());
15416
+ });
15417
+ stream.on("error", () => {
15418
+ });
15419
+ }
15344
15420
  async callTool(toolName, toolArgs) {
15345
15421
  try {
15346
15422
  const args = toolArgs && typeof toolArgs === "object" ? toolArgs : void 0;
@@ -15376,14 +15452,30 @@ var MCPClient = class {
15376
15452
  };
15377
15453
 
15378
15454
  // src/utils/mcpAdapter.ts
15455
+ var CACHE_VERSION = 1;
15456
+ var CACHE_FILE = join3(homedir4(), ".bike4mind", "mcp-schema-cache.json");
15379
15457
  var McpManager = class {
15380
15458
  constructor(config) {
15381
15459
  this.servers = /* @__PURE__ */ new Map();
15382
15460
  this.connectionStates = /* @__PURE__ */ new Map();
15461
+ /** Per-server deferred promise resolved once the background connection is ready */
15462
+ this.connectionReady = /* @__PURE__ */ new Map();
15463
+ /**
15464
+ * Serializes background cache saves so concurrent writes don't race.
15465
+ * JSON.stringify is evaluated at run-time, always capturing the latest cache state.
15466
+ */
15467
+ this.backgroundSaveQueue = Promise.resolve();
15383
15468
  this.config = config;
15384
15469
  }
15470
+ /** Subscribe to background connection state changes for live UI updates. */
15471
+ setOnStateChange(callback) {
15472
+ this.onStateChange = callback;
15473
+ }
15385
15474
  /**
15386
- * Initialize and connect to all enabled MCP servers
15475
+ * Initialize MCP servers with schema caching.
15476
+ *
15477
+ * - Cache hit → tools registered immediately from cache; server connected in background
15478
+ * - Cache miss → server connected eagerly (blocks); result cached for next run
15387
15479
  */
15388
15480
  async initialize() {
15389
15481
  const enabledServers = this.config.mcpServers.filter((s) => s.enabled);
@@ -15392,47 +15484,96 @@ var McpManager = class {
15392
15484
  return;
15393
15485
  }
15394
15486
  logger.debug(`\u{1F4E1} Initializing ${enabledServers.length} MCP server(s)...`);
15395
- const results = await Promise.allSettled(enabledServers.map((serverConfig) => this.connectServer(serverConfig)));
15396
- const successful = results.filter((r) => r.status === "fulfilled").length;
15397
- const failed = results.filter((r) => r.status === "rejected").length;
15398
- if (successful > 0) {
15399
- logger.debug(`\u2705 Connected to ${successful} MCP server(s)`);
15487
+ const cache = await this.loadCache();
15488
+ const configuredNames = new Set(this.config.mcpServers.map((s) => s.name));
15489
+ let pruned = false;
15490
+ for (const name of Object.keys(cache.servers)) {
15491
+ if (!configuredNames.has(name)) {
15492
+ delete cache.servers[name];
15493
+ pruned = true;
15494
+ }
15495
+ }
15496
+ const eagerConnections = [];
15497
+ for (const serverConfig of enabledServers) {
15498
+ const configHash = this.hashServerConfig(serverConfig);
15499
+ const cachedEntry = cache.servers[serverConfig.name];
15500
+ if (cachedEntry && cachedEntry.configHash === configHash) {
15501
+ this.registerFromCache(serverConfig, cachedEntry);
15502
+ this.connectBackground(serverConfig, cache);
15503
+ } else {
15504
+ eagerConnections.push(this.connectEager(serverConfig, cache));
15505
+ }
15506
+ }
15507
+ if (eagerConnections.length > 0) {
15508
+ await Promise.allSettled(eagerConnections);
15509
+ await this.saveCache(cache);
15510
+ } else if (pruned) {
15511
+ await this.saveCache(cache);
15400
15512
  }
15401
- if (failed > 0) {
15402
- logger.debug(`\u26A0\uFE0F Failed to connect to ${failed} MCP server(s)`);
15513
+ const connected = [...this.connectionStates.values()].filter((s) => s === "connected").length;
15514
+ const pending = [...this.connectionStates.values()].filter((s) => s === "connecting").length;
15515
+ if (connected > 0 || pending > 0) {
15516
+ logger.debug(`\u2705 ${connected} MCP server(s) ready${pending > 0 ? `, ${pending} connecting in background` : ""}`);
15403
15517
  }
15404
15518
  }
15519
+ // ─── Private: connection strategies ────────────────────────────────────────
15520
+ /**
15521
+ * Register tool stubs from cache immediately. callTool lazily awaits the
15522
+ * background connection before forwarding to the real client.
15523
+ */
15524
+ registerFromCache(serverConfig, entry) {
15525
+ this.connectionStates.set(serverConfig.name, "connecting");
15526
+ let resolveReady;
15527
+ let rejectReady;
15528
+ const promise = new Promise((res, rej) => {
15529
+ resolveReady = res;
15530
+ rejectReady = rej;
15531
+ });
15532
+ promise.catch(() => {
15533
+ });
15534
+ this.connectionReady.set(serverConfig.name, { resolve: resolveReady, reject: rejectReady, promise });
15535
+ const callTool = async (name, args) => {
15536
+ await promise;
15537
+ return this.servers.get(serverConfig.name).client.callTool(name, args);
15538
+ };
15539
+ const tools = generateMcpToolsFromCache(serverConfig.name, entry.tools, callTool);
15540
+ this.servers.set(serverConfig.name, { name: serverConfig.name, client: null, tools });
15541
+ logger.debug(`\u{1F4CB} ${entry.tools.length} tools for ${serverConfig.name} loaded from cache`);
15542
+ }
15543
+ /**
15544
+ * Spawn the server process in the background. Resolves the per-server
15545
+ * deferred promise when ready so pending callTool invocations can proceed.
15546
+ */
15547
+ connectBackground(serverConfig, cache) {
15548
+ this.doConnect(serverConfig).then(({ client }) => {
15549
+ const instance = this.servers.get(serverConfig.name);
15550
+ if (instance) {
15551
+ instance.client = client;
15552
+ }
15553
+ this.connectionStates.set(serverConfig.name, "connected");
15554
+ this.connectionReady.get(serverConfig.name)?.resolve();
15555
+ this.onStateChange?.();
15556
+ logger.debug(`\u2705 Background connection to ${serverConfig.name} established`);
15557
+ this.writeCacheEntry(cache, serverConfig, client.tools);
15558
+ this.scheduleBackgroundSave(cache);
15559
+ }).catch((err) => {
15560
+ this.connectionStates.set(serverConfig.name, "failed");
15561
+ this.connectionReady.get(serverConfig.name)?.reject(err);
15562
+ this.onStateChange?.();
15563
+ logger.debug(`\u274C Background connection to ${serverConfig.name} failed: ${err}`);
15564
+ });
15565
+ }
15405
15566
  /**
15406
- * Connect to a single MCP server
15567
+ * Connect eagerly (blocks initialize). Populates servers map and updates cache.
15407
15568
  */
15408
- async connectServer(serverConfig) {
15569
+ async connectEager(serverConfig, cache) {
15409
15570
  this.connectionStates.set(serverConfig.name, "connecting");
15571
+ logger.debug(`\u{1F504} Connecting to ${serverConfig.name}...`);
15410
15572
  try {
15411
- logger.debug(`\u{1F504} Connecting to ${serverConfig.name}...`);
15412
- const envVariables = Object.entries(serverConfig.env).map(([key, value]) => ({
15413
- key,
15414
- value
15415
- }));
15416
- const client = new MCPClient({
15417
- envVariables,
15418
- name: serverConfig.name,
15419
- command: serverConfig.command,
15420
- args: serverConfig.args,
15421
- suppressStderr: true
15422
- });
15423
- await client.connectToServer();
15424
- const mcpData = {
15425
- serverName: serverConfig.name,
15426
- getTools: async () => client.tools,
15427
- callTool: async (name, args) => client.callTool(name, args)
15428
- };
15429
- const tools = await generateMcpTools(mcpData);
15430
- this.servers.set(serverConfig.name, {
15431
- name: serverConfig.name,
15432
- client,
15433
- tools
15434
- });
15573
+ const { client, tools } = await this.doConnect(serverConfig);
15574
+ this.servers.set(serverConfig.name, { name: serverConfig.name, client, tools });
15435
15575
  this.connectionStates.set(serverConfig.name, "connected");
15576
+ this.writeCacheEntry(cache, serverConfig, client.tools);
15436
15577
  logger.debug(`\u2705 Connected to ${serverConfig.name} (${tools.length} tools)`);
15437
15578
  } catch (error) {
15438
15579
  this.connectionStates.set(serverConfig.name, "failed");
@@ -15440,8 +15581,82 @@ var McpManager = class {
15440
15581
  }
15441
15582
  }
15442
15583
  /**
15443
- * Get all tools from all connected MCP servers
15584
+ * Shared: spawn and handshake with an MCP server process.
15444
15585
  */
15586
+ async doConnect(serverConfig) {
15587
+ const envVariables = Object.entries(serverConfig.env).map(([key, value]) => ({ key, value }));
15588
+ const client = new MCPClient({
15589
+ envVariables,
15590
+ name: serverConfig.name,
15591
+ command: serverConfig.command,
15592
+ args: serverConfig.args,
15593
+ suppressStderr: true
15594
+ });
15595
+ await client.connectToServer();
15596
+ const mcpData = {
15597
+ serverName: serverConfig.name,
15598
+ getTools: async () => client.tools,
15599
+ // any: generateMcpTools accepts a loose duck-type; MCPClient.callTool return type
15600
+ // doesn't match the internal interface exactly, but runtime behaviour is correct.
15601
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15602
+ callTool: async (name, args) => client.callTool(name, args)
15603
+ };
15604
+ const tools = await generateMcpTools(mcpData);
15605
+ return { client, tools };
15606
+ }
15607
+ // ─── Private: cache helpers ──────────────────────────────────────────────────
15608
+ async loadCache() {
15609
+ try {
15610
+ const raw = await readFile2(CACHE_FILE, "utf-8");
15611
+ const parsed = JSON.parse(raw);
15612
+ if (parsed.version === CACHE_VERSION) {
15613
+ return parsed;
15614
+ }
15615
+ } catch {
15616
+ }
15617
+ return { version: CACHE_VERSION, servers: {} };
15618
+ }
15619
+ async saveCache(cache) {
15620
+ try {
15621
+ await mkdir(dirname2(CACHE_FILE), { recursive: true });
15622
+ await writeFile(CACHE_FILE, JSON.stringify(cache, null, 2), "utf-8");
15623
+ } catch (error) {
15624
+ logger.debug(`\u26A0\uFE0F Failed to save MCP schema cache: ${error}`);
15625
+ }
15626
+ }
15627
+ /**
15628
+ * Enqueue a background cache save. Saves are serialized so concurrent background
15629
+ * connections can't interleave writes and lose each other's entries. JSON.stringify
15630
+ * is evaluated when the queued write runs, so it always captures the latest state.
15631
+ */
15632
+ scheduleBackgroundSave(cache) {
15633
+ this.backgroundSaveQueue = this.backgroundSaveQueue.then(() => this.saveCache(cache)).catch(() => {
15634
+ });
15635
+ }
15636
+ writeCacheEntry(cache, serverConfig, rawTools) {
15637
+ cache.servers[serverConfig.name] = {
15638
+ configHash: this.hashServerConfig(serverConfig),
15639
+ tools: rawTools.map((t) => {
15640
+ const tool = t;
15641
+ return {
15642
+ name: t.name,
15643
+ description: tool.description,
15644
+ // Normalize both MCP SDK casing variants to input_schema for storage
15645
+ input_schema: tool.inputSchema ?? tool.input_schema
15646
+ };
15647
+ }),
15648
+ cachedAt: (/* @__PURE__ */ new Date()).toISOString()
15649
+ };
15650
+ }
15651
+ hashServerConfig(serverConfig) {
15652
+ const key = JSON.stringify({
15653
+ command: serverConfig.command,
15654
+ args: serverConfig.args,
15655
+ env: serverConfig.env
15656
+ });
15657
+ return createHash("sha256").update(key).digest("hex").slice(0, 16);
15658
+ }
15659
+ // ─── Public API ───────────────────────────────────────────────────────────────
15445
15660
  getTools() {
15446
15661
  const allTools = [];
15447
15662
  for (const server of this.servers.values()) {
@@ -15449,24 +15664,17 @@ var McpManager = class {
15449
15664
  }
15450
15665
  return allTools;
15451
15666
  }
15452
- /**
15453
- * Get tool count by server
15454
- */
15455
15667
  getToolCount() {
15456
15668
  return Array.from(this.servers.values()).map((server) => ({
15457
15669
  serverName: server.name,
15458
15670
  count: server.tools.length
15459
15671
  }));
15460
15672
  }
15461
- /**
15462
- * Disconnect from all MCP servers
15463
- */
15464
15673
  async disconnect() {
15465
- if (this.servers.size === 0) {
15466
- return;
15467
- }
15674
+ if (this.servers.size === 0) return;
15468
15675
  logger.debug(`\u{1F50C} Disconnecting from ${this.servers.size} MCP server(s)...`);
15469
15676
  const disconnectPromises = Array.from(this.servers.values()).map(async (server) => {
15677
+ if (!server.client) return;
15470
15678
  try {
15471
15679
  await server.client.disconnect();
15472
15680
  logger.debug(`\u2705 Disconnected from ${server.name}`);
@@ -15476,27 +15684,17 @@ var McpManager = class {
15476
15684
  });
15477
15685
  await Promise.allSettled(disconnectPromises);
15478
15686
  this.servers.clear();
15687
+ this.connectionReady.clear();
15479
15688
  }
15480
- /**
15481
- * Check if any MCP servers are connected
15482
- */
15483
15689
  hasServers() {
15484
15690
  return this.servers.size > 0;
15485
15691
  }
15486
- /**
15487
- * Get list of connected server names
15488
- */
15489
15692
  getServerNames() {
15490
15693
  return Array.from(this.servers.keys());
15491
15694
  }
15492
- /**
15493
- * Get connection state for a server
15494
- */
15495
15695
  getConnectionState(serverName) {
15496
15696
  const serverConfig = this.config.mcpServers.find((s) => s.name === serverName);
15497
- if (!serverConfig?.enabled) {
15498
- return "disabled";
15499
- }
15697
+ if (!serverConfig?.enabled) return "disabled";
15500
15698
  return this.connectionStates.get(serverName) || "connecting";
15501
15699
  }
15502
15700
  };
@@ -15904,6 +16102,9 @@ var ServerLlmBackend = class {
15904
16102
  });
15905
16103
  });
15906
16104
  }
16105
+ pushToolMessages(_messages, _tool, _result) {
16106
+ throw new Error("ServerLlmBackend does not support pushToolMessages \u2014 tools are executed server-side");
16107
+ }
15907
16108
  /**
15908
16109
  * Get available models from server
15909
16110
  * Fetches from /api/models and filters for CLI-compatible models
@@ -16123,6 +16324,9 @@ var WebSocketLlmBackend = class {
16123
16324
  });
16124
16325
  });
16125
16326
  }
16327
+ pushToolMessages(_messages, _tool, _result) {
16328
+ throw new Error("WebSocketLlmBackend does not support pushToolMessages \u2014 tools are executed server-side");
16329
+ }
16126
16330
  /**
16127
16331
  * Get available models from server (REST call, not streaming).
16128
16332
  * Delegates to ApiClient -- same as ServerLlmBackend.
@@ -3,7 +3,7 @@ import {
3
3
  fetchLatestVersion,
4
4
  forceCheckForUpdate,
5
5
  package_default
6
- } from "../chunk-FACWWYF7.js";
6
+ } from "../chunk-F4TQZA4G.js";
7
7
 
8
8
  // src/commands/doctorCommand.ts
9
9
  import { execSync } from "child_process";