@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.
- package/dist/{artifactExtractor-QIDCYO4J.js → artifactExtractor-Z6UQO2IR.js} +1 -1
- package/dist/{chunk-AOP6RYDY.js → chunk-5APQNY55.js} +2 -2
- package/dist/{chunk-LJG44UT3.js → chunk-AU7OYPU4.js} +2 -2
- package/dist/{chunk-FACWWYF7.js → chunk-F4TQZA4G.js} +6 -6
- package/dist/{chunk-H7RVLAQD.js → chunk-HJVFUXVX.js} +1 -1
- package/dist/{chunk-NJQYWIDJ.js → chunk-J3YSVA6W.js} +2 -2
- package/dist/{chunk-EY65E4W4.js → chunk-OCFOJXLA.js} +39 -11
- package/dist/{chunk-5YLGNW2B.js → chunk-QCDYXXSD.js} +163 -54
- package/dist/{chunk-XIB66EG7.js → chunk-XBAC2O32.js} +298 -94
- package/dist/commands/doctorCommand.js +1 -1
- package/dist/commands/headlessCommand.js +20 -18
- package/dist/commands/mcpCommand.js +2 -2
- package/dist/commands/updateCommand.js +1 -1
- package/dist/{create-IFIWNV4T.js → create-MVD65MYF.js} +3 -3
- package/dist/index.js +54 -35
- package/dist/{llmMarkdownGenerator-7D3C33BP.js → llmMarkdownGenerator-MEEKZGSS.js} +1 -1
- package/dist/{markdownGenerator-C2QW7FBH.js → markdownGenerator-NKNJTN6H.js} +1 -1
- package/dist/{mementoService-ABCCDW6T.js → mementoService-QPAN6H6A.js} +3 -3
- package/dist/{src-ZPD2SIIU.js → src-I44AUY66.js} +11 -1
- package/dist/{src-D6KSYF2W.js → src-UJ7SX2X5.js} +2 -2
- package/dist/{subtractCredits-QKF6HTX3.js → subtractCredits-K56NLITN.js} +3 -3
- package/package.json +6 -6
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
getOpenWeatherKey,
|
|
5
5
|
getSerperKey,
|
|
6
6
|
getWolframAlphaKey
|
|
7
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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
|
|
2154
|
-
const
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
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:
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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
|
|
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
|
|
15396
|
-
const
|
|
15397
|
-
|
|
15398
|
-
|
|
15399
|
-
|
|
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
|
-
|
|
15402
|
-
|
|
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
|
|
15567
|
+
* Connect eagerly (blocks initialize). Populates servers map and updates cache.
|
|
15407
15568
|
*/
|
|
15408
|
-
async
|
|
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
|
-
|
|
15412
|
-
|
|
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
|
-
*
|
|
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.
|