reasonix 0.21.0 → 0.22.0
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/cli/index.js +344 -91
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +31 -1
- package/dist/index.js +32 -26
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -1322,6 +1322,26 @@ function computeP95(samples) {
|
|
|
1322
1322
|
// src/mcp/registry.ts
|
|
1323
1323
|
var DEFAULT_MAX_RESULT_CHARS = 32e3;
|
|
1324
1324
|
var DEFAULT_MAX_RESULT_TOKENS = 8e3;
|
|
1325
|
+
function registerSingleMcpTool(mcpTool, env) {
|
|
1326
|
+
if (!mcpTool.name) return "";
|
|
1327
|
+
const registeredName = `${env.prefix}${mcpTool.name}`;
|
|
1328
|
+
env.registry.register({
|
|
1329
|
+
name: registeredName,
|
|
1330
|
+
description: mcpTool.description ?? "",
|
|
1331
|
+
parameters: mcpTool.inputSchema,
|
|
1332
|
+
fn: async (args, ctx) => {
|
|
1333
|
+
const t0 = env.tracker ? Date.now() : 0;
|
|
1334
|
+
const live = env.host.client;
|
|
1335
|
+
const toolResult = await live.callTool(mcpTool.name, args, {
|
|
1336
|
+
onProgress: env.onProgress ? (info) => env.onProgress({ toolName: registeredName, ...info }) : void 0,
|
|
1337
|
+
signal: ctx?.signal
|
|
1338
|
+
});
|
|
1339
|
+
if (env.tracker) env.tracker.record(Date.now() - t0);
|
|
1340
|
+
return flattenMcpResult(toolResult, { maxChars: env.maxResultChars });
|
|
1341
|
+
}
|
|
1342
|
+
});
|
|
1343
|
+
return registeredName;
|
|
1344
|
+
}
|
|
1325
1345
|
async function bridgeMcpTools(client, opts = {}) {
|
|
1326
1346
|
const registry = opts.registry ?? new ToolRegistry({ autoFlatten: opts.autoFlatten });
|
|
1327
1347
|
const prefix = opts.namePrefix ?? "";
|
|
@@ -1329,39 +1349,25 @@ async function bridgeMcpTools(client, opts = {}) {
|
|
|
1329
1349
|
const result = { registry, registeredNames: [], skipped: [] };
|
|
1330
1350
|
const serverName = opts.serverName ?? prefix.replace(/_$/, "") ?? "anon";
|
|
1331
1351
|
const tracker = opts.onSlow ? new LatencyTracker(serverName, { thresholdMs: opts.slowThresholdMs, onSlow: opts.onSlow }) : null;
|
|
1352
|
+
const host = opts.host ?? { client };
|
|
1353
|
+
const env = {
|
|
1354
|
+
registry,
|
|
1355
|
+
host,
|
|
1356
|
+
prefix,
|
|
1357
|
+
maxResultChars,
|
|
1358
|
+
tracker,
|
|
1359
|
+
onProgress: opts.onProgress
|
|
1360
|
+
};
|
|
1332
1361
|
const listed = await client.listTools();
|
|
1333
1362
|
for (const mcpTool of listed.tools) {
|
|
1334
1363
|
if (!mcpTool.name) {
|
|
1335
1364
|
result.skipped.push({ name: "?", reason: "empty tool name" });
|
|
1336
1365
|
continue;
|
|
1337
1366
|
}
|
|
1338
|
-
const registeredName =
|
|
1339
|
-
|
|
1340
|
-
name: registeredName,
|
|
1341
|
-
description: mcpTool.description ?? "",
|
|
1342
|
-
parameters: mcpTool.inputSchema,
|
|
1343
|
-
fn: async (args, ctx) => {
|
|
1344
|
-
const t0 = tracker ? Date.now() : 0;
|
|
1345
|
-
const toolResult = await client.callTool(mcpTool.name, args, {
|
|
1346
|
-
// Forward server-side progress frames to the bridge caller,
|
|
1347
|
-
// tagged with the registered name so multi-server UIs can
|
|
1348
|
-
// disambiguate. No-op when `onProgress` isn't configured —
|
|
1349
|
-
// the client then also omits the _meta.progressToken and
|
|
1350
|
-
// the server won't emit progress.
|
|
1351
|
-
onProgress: opts.onProgress ? (info) => opts.onProgress({ toolName: registeredName, ...info }) : void 0,
|
|
1352
|
-
// Thread the tool-dispatch AbortSignal all the way down to
|
|
1353
|
-
// the MCP request so Esc truly cancels in flight — the
|
|
1354
|
-
// client will emit notifications/cancelled AND reject the
|
|
1355
|
-
// pending promise immediately, no "wait for subprocess".
|
|
1356
|
-
signal: ctx?.signal
|
|
1357
|
-
});
|
|
1358
|
-
if (tracker) tracker.record(Date.now() - t0);
|
|
1359
|
-
return flattenMcpResult(toolResult, { maxChars: maxResultChars });
|
|
1360
|
-
}
|
|
1361
|
-
});
|
|
1362
|
-
result.registeredNames.push(registeredName);
|
|
1367
|
+
const registeredName = registerSingleMcpTool(mcpTool, env);
|
|
1368
|
+
if (registeredName) result.registeredNames.push(registeredName);
|
|
1363
1369
|
}
|
|
1364
|
-
return result;
|
|
1370
|
+
return { ...result, env };
|
|
1365
1371
|
}
|
|
1366
1372
|
function flattenMcpResult(result, opts = {}) {
|
|
1367
1373
|
const parts = result.content.map(blockToString);
|
|
@@ -12978,7 +12984,244 @@ function EditConfirm({ block: block2, onChoose }) {
|
|
|
12978
12984
|
// src/cli/ui/McpBrowser.tsx
|
|
12979
12985
|
import { Box as Box7, Text as Text7 } from "ink";
|
|
12980
12986
|
import React9, { useState as useState4 } from "react";
|
|
12981
|
-
|
|
12987
|
+
|
|
12988
|
+
// src/cli/ui/mcp-disable.ts
|
|
12989
|
+
function toggleMcpDisabled(action, name) {
|
|
12990
|
+
const trimmed = name.trim();
|
|
12991
|
+
if (!trimmed) {
|
|
12992
|
+
return `usage: /mcp ${action} <name> \xB7 pick a name shown in /mcp (anonymous servers can't be named-toggled).`;
|
|
12993
|
+
}
|
|
12994
|
+
const cfg = readConfig();
|
|
12995
|
+
const current = new Set(cfg.mcpDisabled ?? []);
|
|
12996
|
+
if (action === "disable") {
|
|
12997
|
+
if (current.has(trimmed)) {
|
|
12998
|
+
return `\u25B8 ${trimmed} is already disabled \u2014 restart to apply, or /mcp enable ${trimmed}.`;
|
|
12999
|
+
}
|
|
13000
|
+
current.add(trimmed);
|
|
13001
|
+
writeConfig({ ...cfg, mcpDisabled: [...current].sort() });
|
|
13002
|
+
return `\u25B8 ${trimmed} disabled \u2014 takes effect on next launch. /mcp enable ${trimmed} to revert.`;
|
|
13003
|
+
}
|
|
13004
|
+
if (!current.has(trimmed)) {
|
|
13005
|
+
return `\u25B8 ${trimmed} is not disabled.`;
|
|
13006
|
+
}
|
|
13007
|
+
current.delete(trimmed);
|
|
13008
|
+
writeConfig({ ...cfg, mcpDisabled: current.size > 0 ? [...current].sort() : void 0 });
|
|
13009
|
+
return `\u25B8 ${trimmed} re-enabled \u2014 takes effect on next launch.`;
|
|
13010
|
+
}
|
|
13011
|
+
|
|
13012
|
+
// src/mcp/drift.ts
|
|
13013
|
+
function classifyToolListDrift(before, after) {
|
|
13014
|
+
const beforeNames = before.map(nameOf);
|
|
13015
|
+
const afterNames = after.map(nameOf);
|
|
13016
|
+
const beforeSet = new Set(beforeNames);
|
|
13017
|
+
const afterSet = new Set(afterNames);
|
|
13018
|
+
const added = afterNames.filter((n) => !beforeSet.has(n));
|
|
13019
|
+
const removed = beforeNames.filter((n) => !afterSet.has(n));
|
|
13020
|
+
const edited = [];
|
|
13021
|
+
const sharedLen = Math.min(before.length, after.length);
|
|
13022
|
+
for (let i = 0; i < sharedLen; i++) {
|
|
13023
|
+
if (beforeNames[i] === afterNames[i] && hash(before[i]) !== hash(after[i])) {
|
|
13024
|
+
edited.push(beforeNames[i]);
|
|
13025
|
+
}
|
|
13026
|
+
}
|
|
13027
|
+
if (before.length === after.length && edited.length === 0 && beforeNames.every((n, i) => n === afterNames[i])) {
|
|
13028
|
+
return { kind: "identity", added: [], removed: [], edited: [] };
|
|
13029
|
+
}
|
|
13030
|
+
if (removed.length > 0) {
|
|
13031
|
+
return { kind: "remove", added, removed, edited };
|
|
13032
|
+
}
|
|
13033
|
+
if (after.length > before.length && beforeNames.every((n, i) => n === afterNames[i] && hash(before[i]) === hash(after[i]))) {
|
|
13034
|
+
return { kind: "append", added, removed: [], edited: [] };
|
|
13035
|
+
}
|
|
13036
|
+
const sameNameSet = beforeSet.size === afterSet.size && [...beforeSet].every((n) => afterSet.has(n));
|
|
13037
|
+
if (sameNameSet) {
|
|
13038
|
+
const positionsMatch = beforeNames.every((n, i) => n === afterNames[i]);
|
|
13039
|
+
if (positionsMatch) {
|
|
13040
|
+
return { kind: "edit", added: [], removed: [], edited };
|
|
13041
|
+
}
|
|
13042
|
+
return { kind: "reorder", added: [], removed: [], edited };
|
|
13043
|
+
}
|
|
13044
|
+
return { kind: "reorder", added, removed: [], edited };
|
|
13045
|
+
}
|
|
13046
|
+
function nameOf(spec) {
|
|
13047
|
+
return spec.function?.name ?? "";
|
|
13048
|
+
}
|
|
13049
|
+
function hash(spec) {
|
|
13050
|
+
return JSON.stringify(spec);
|
|
13051
|
+
}
|
|
13052
|
+
|
|
13053
|
+
// src/mcp/reconnect.ts
|
|
13054
|
+
async function reconnectMcpServer(args) {
|
|
13055
|
+
const t0 = Date.now();
|
|
13056
|
+
const accept = args.accept ?? ["identity"];
|
|
13057
|
+
let parsed;
|
|
13058
|
+
try {
|
|
13059
|
+
parsed = parseMcpSpec(args.spec);
|
|
13060
|
+
} catch (err) {
|
|
13061
|
+
return {
|
|
13062
|
+
ok: false,
|
|
13063
|
+
reason: "spec_parse",
|
|
13064
|
+
message: err.message,
|
|
13065
|
+
ms: Date.now() - t0
|
|
13066
|
+
};
|
|
13067
|
+
}
|
|
13068
|
+
const transport = parsed.transport === "sse" ? new SseTransport({ url: parsed.url }) : parsed.transport === "streamable-http" ? new StreamableHttpTransport({ url: parsed.url }) : new StdioTransport({ command: parsed.command, args: parsed.args });
|
|
13069
|
+
const next = new McpClient({ transport });
|
|
13070
|
+
try {
|
|
13071
|
+
await next.initialize();
|
|
13072
|
+
const listed = await next.listTools();
|
|
13073
|
+
const drift = classifyToolListDrift(toolsToSpecs(args.beforeTools), toolsToSpecs(listed.tools));
|
|
13074
|
+
const acceptedKind = drift.kind === "identity" ? "identity" : drift.kind === "append" && accept.includes("append") ? "append" : null;
|
|
13075
|
+
if (acceptedKind === null) {
|
|
13076
|
+
await next.close().catch(() => {
|
|
13077
|
+
});
|
|
13078
|
+
const refused = drift.kind;
|
|
13079
|
+
return {
|
|
13080
|
+
ok: false,
|
|
13081
|
+
reason: driftReason(refused),
|
|
13082
|
+
message: driftMessage(drift),
|
|
13083
|
+
ms: Date.now() - t0
|
|
13084
|
+
};
|
|
13085
|
+
}
|
|
13086
|
+
const addedTools = acceptedKind === "append" ? listed.tools.filter((t2) => drift.added.includes(t2.name)) : [];
|
|
13087
|
+
const old = args.host.client;
|
|
13088
|
+
args.host.client = next;
|
|
13089
|
+
await old.close().catch(() => {
|
|
13090
|
+
});
|
|
13091
|
+
return {
|
|
13092
|
+
ok: true,
|
|
13093
|
+
kind: acceptedKind,
|
|
13094
|
+
afterTools: listed.tools,
|
|
13095
|
+
addedTools,
|
|
13096
|
+
ms: Date.now() - t0
|
|
13097
|
+
};
|
|
13098
|
+
} catch (err) {
|
|
13099
|
+
await next.close().catch(() => {
|
|
13100
|
+
});
|
|
13101
|
+
return {
|
|
13102
|
+
ok: false,
|
|
13103
|
+
reason: "handshake",
|
|
13104
|
+
message: err.message,
|
|
13105
|
+
ms: Date.now() - t0
|
|
13106
|
+
};
|
|
13107
|
+
}
|
|
13108
|
+
}
|
|
13109
|
+
function driftReason(kind) {
|
|
13110
|
+
if (kind === "append") return "drift_added";
|
|
13111
|
+
if (kind === "edit") return "drift_edited";
|
|
13112
|
+
if (kind === "reorder") return "drift_reordered";
|
|
13113
|
+
return "drift_removed";
|
|
13114
|
+
}
|
|
13115
|
+
function driftMessage(drift) {
|
|
13116
|
+
if (drift.kind === "append") {
|
|
13117
|
+
return `tool list grew (${drift.added.length} added: ${drift.added.join(", ")}). Restart Reasonix to bridge the new tool(s).`;
|
|
13118
|
+
}
|
|
13119
|
+
if (drift.kind === "edit") {
|
|
13120
|
+
return `tool description/schema changed for ${drift.edited.join(", ")}. Restart Reasonix to apply.`;
|
|
13121
|
+
}
|
|
13122
|
+
if (drift.kind === "remove") {
|
|
13123
|
+
return `tool(s) removed: ${drift.removed.join(", ")}. Restart Reasonix to drop them from the registry.`;
|
|
13124
|
+
}
|
|
13125
|
+
return "tool list reordered or restructured \u2014 cache prefix would be invalidated. Restart Reasonix.";
|
|
13126
|
+
}
|
|
13127
|
+
function toolsToSpecs(tools) {
|
|
13128
|
+
return tools.map((t2) => ({
|
|
13129
|
+
type: "function",
|
|
13130
|
+
function: {
|
|
13131
|
+
name: t2.name,
|
|
13132
|
+
description: t2.description ?? "",
|
|
13133
|
+
parameters: t2.inputSchema
|
|
13134
|
+
}
|
|
13135
|
+
}));
|
|
13136
|
+
}
|
|
13137
|
+
|
|
13138
|
+
// src/cli/ui/mcp-lifecycle.ts
|
|
13139
|
+
var STATE = {
|
|
13140
|
+
handshake: { glyph: "\u21BB", label: "handshake\u2026" },
|
|
13141
|
+
connected: { glyph: "\u2713", label: "connected" },
|
|
13142
|
+
failed: { glyph: "\u2716", label: "failed" },
|
|
13143
|
+
disabled: { glyph: "\u25CB", label: "disabled" },
|
|
13144
|
+
reconnect: { glyph: "\u21BB", label: "reconnect\u2026" }
|
|
13145
|
+
};
|
|
13146
|
+
var NAME_COL = 22;
|
|
13147
|
+
var STATE_COL = 15;
|
|
13148
|
+
function formatMcpLifecycleEvent(ev) {
|
|
13149
|
+
const { glyph, label } = STATE[ev.state];
|
|
13150
|
+
const namePart = `MCP \xB7 ${ev.name}`;
|
|
13151
|
+
const namePad = " ".repeat(Math.max(1, NAME_COL - namePart.length));
|
|
13152
|
+
const stateField = `${glyph} ${label}`.padEnd(STATE_COL);
|
|
13153
|
+
return `\u2318 ${namePart}${namePad}${stateField}${describeDetail(ev)}`;
|
|
13154
|
+
}
|
|
13155
|
+
function describeDetail(ev) {
|
|
13156
|
+
if (ev.state === "handshake") return "initialise \u2192 tools/list \u2192 resources/list";
|
|
13157
|
+
if (ev.state === "failed") return ev.reason;
|
|
13158
|
+
if (ev.state === "disabled") return `via /mcp disable ${ev.name}`;
|
|
13159
|
+
if (ev.state === "reconnect") return "tearing down \xB7 re-handshake \xB7 listing tools";
|
|
13160
|
+
const parts = [`${ev.tools} tools`];
|
|
13161
|
+
if (ev.resources && ev.resources > 0) parts.push(`${ev.resources} resources`);
|
|
13162
|
+
if (ev.prompts && ev.prompts > 0) parts.push(`${ev.prompts} prompts`);
|
|
13163
|
+
parts.push(`${ev.ms}ms`);
|
|
13164
|
+
return parts.join(" \xB7 ");
|
|
13165
|
+
}
|
|
13166
|
+
|
|
13167
|
+
// src/cli/ui/mcp-reconnect-kickoff.ts
|
|
13168
|
+
function kickOffMcpReconnect(target, postInfo, applyAppend) {
|
|
13169
|
+
const beforeTools = target.report.tools.supported ? target.report.tools.items : [];
|
|
13170
|
+
const accept = applyAppend ? ["identity", "append"] : ["identity"];
|
|
13171
|
+
void (async () => {
|
|
13172
|
+
try {
|
|
13173
|
+
const result = await reconnectMcpServer({
|
|
13174
|
+
host: target.host,
|
|
13175
|
+
spec: target.spec,
|
|
13176
|
+
beforeTools,
|
|
13177
|
+
accept
|
|
13178
|
+
});
|
|
13179
|
+
if (result.ok) {
|
|
13180
|
+
if (result.kind === "append" && applyAppend) {
|
|
13181
|
+
applyAppend(target, result.addedTools);
|
|
13182
|
+
}
|
|
13183
|
+
postInfo(
|
|
13184
|
+
formatMcpLifecycleEvent({
|
|
13185
|
+
state: "connected",
|
|
13186
|
+
name: target.label,
|
|
13187
|
+
tools: result.afterTools.length,
|
|
13188
|
+
ms: result.ms
|
|
13189
|
+
})
|
|
13190
|
+
);
|
|
13191
|
+
if (result.kind === "append") {
|
|
13192
|
+
const names = result.addedTools.map((t2) => t2.name).join(", ");
|
|
13193
|
+
postInfo(`\u25B8 ${target.label}: added ${result.addedTools.length} tool(s) \u2014 ${names}`);
|
|
13194
|
+
}
|
|
13195
|
+
} else {
|
|
13196
|
+
postInfo(
|
|
13197
|
+
formatMcpLifecycleEvent({
|
|
13198
|
+
state: "failed",
|
|
13199
|
+
name: target.label,
|
|
13200
|
+
reason: `${result.reason} \xB7 ${result.message}`
|
|
13201
|
+
})
|
|
13202
|
+
);
|
|
13203
|
+
}
|
|
13204
|
+
} catch (err) {
|
|
13205
|
+
postInfo(
|
|
13206
|
+
formatMcpLifecycleEvent({
|
|
13207
|
+
state: "failed",
|
|
13208
|
+
name: target.label,
|
|
13209
|
+
reason: err.message
|
|
13210
|
+
})
|
|
13211
|
+
);
|
|
13212
|
+
}
|
|
13213
|
+
})();
|
|
13214
|
+
return formatMcpLifecycleEvent({ state: "reconnect", name: target.label });
|
|
13215
|
+
}
|
|
13216
|
+
|
|
13217
|
+
// src/cli/ui/McpBrowser.tsx
|
|
13218
|
+
function McpBrowser({
|
|
13219
|
+
servers,
|
|
13220
|
+
configPath,
|
|
13221
|
+
onClose,
|
|
13222
|
+
postInfo,
|
|
13223
|
+
applyAppend
|
|
13224
|
+
}) {
|
|
12982
13225
|
const [index, setIndex] = useState4(0);
|
|
12983
13226
|
const max = Math.max(0, servers.length - 1);
|
|
12984
13227
|
useKeystroke((ev) => {
|
|
@@ -12986,6 +13229,17 @@ function McpBrowser({ servers, configPath, onClose }) {
|
|
|
12986
13229
|
if (ev.upArrow) setIndex((i) => Math.max(0, i - 1));
|
|
12987
13230
|
else if (ev.downArrow) setIndex((i) => Math.min(max, i + 1));
|
|
12988
13231
|
else if (ev.escape) onClose();
|
|
13232
|
+
else if (ev.input === "r") {
|
|
13233
|
+
const target = servers[index];
|
|
13234
|
+
if (!target) return;
|
|
13235
|
+
postInfo(kickOffMcpReconnect(target, postInfo, applyAppend));
|
|
13236
|
+
onClose();
|
|
13237
|
+
} else if (ev.input === "d") {
|
|
13238
|
+
const target = servers[index];
|
|
13239
|
+
if (!target) return;
|
|
13240
|
+
postInfo(toggleMcpDisabled("disable", target.label));
|
|
13241
|
+
onClose();
|
|
13242
|
+
}
|
|
12989
13243
|
});
|
|
12990
13244
|
return /* @__PURE__ */ React9.createElement(Box7, { flexDirection: "column", paddingX: 1 }, /* @__PURE__ */ React9.createElement(Box7, null, /* @__PURE__ */ React9.createElement(Text7, { bold: true, color: COLOR.brand }, "\u25C8 MCP browser"), /* @__PURE__ */ React9.createElement(
|
|
12991
13245
|
Text7,
|
|
@@ -12993,7 +13247,7 @@ function McpBrowser({ servers, configPath, onClose }) {
|
|
|
12993
13247
|
dimColor: true
|
|
12994
13248
|
},
|
|
12995
13249
|
` \xB7 ${configPath} \xB7 ${servers.length} server${servers.length === 1 ? "" : "s"}`
|
|
12996
|
-
)), /* @__PURE__ */ React9.createElement(Box7, { marginTop: 1, flexDirection: "column" }, servers.length === 0 ? /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, "No MCP servers attached. Run `reasonix setup` to pick some, or launch with --mcp.") : servers.map((s, i) => /* @__PURE__ */ React9.createElement(ServerRow, { key: s.label + s.spec, server: s, active: i === index }))), /* @__PURE__ */ React9.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, "\u2191\u2193 pick \xB7 [r] reconnect
|
|
13250
|
+
)), /* @__PURE__ */ React9.createElement(Box7, { marginTop: 1, flexDirection: "column" }, servers.length === 0 ? /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, "No MCP servers attached. Run `reasonix setup` to pick some, or launch with --mcp.") : servers.map((s, i) => /* @__PURE__ */ React9.createElement(ServerRow, { key: s.label + s.spec, server: s, active: i === index }))), /* @__PURE__ */ React9.createElement(Box7, { marginTop: 1 }, /* @__PURE__ */ React9.createElement(Text7, { dimColor: true }, "\u2191\u2193 pick \xB7 [r] reconnect \xB7 [d] disable \xB7 esc quit")));
|
|
12997
13251
|
}
|
|
12998
13252
|
function ServerRow({ server, active }) {
|
|
12999
13253
|
const { label, toolCount, report } = server;
|
|
@@ -18979,6 +19233,32 @@ function formatDuration(ms) {
|
|
|
18979
19233
|
return mm === 0 ? `${h}h` : `${h}h${mm}m`;
|
|
18980
19234
|
}
|
|
18981
19235
|
|
|
19236
|
+
// src/cli/ui/mcp-append.ts
|
|
19237
|
+
function applyMcpAppend(loop2, target, addedTools) {
|
|
19238
|
+
const accepted = [];
|
|
19239
|
+
for (const mcpTool of addedTools) {
|
|
19240
|
+
if (!mcpTool.name) continue;
|
|
19241
|
+
const registeredName = registerSingleMcpTool(mcpTool, target.bridgeEnv);
|
|
19242
|
+
if (!registeredName) continue;
|
|
19243
|
+
const spec = {
|
|
19244
|
+
type: "function",
|
|
19245
|
+
function: {
|
|
19246
|
+
name: registeredName,
|
|
19247
|
+
description: mcpTool.description ?? "",
|
|
19248
|
+
parameters: mcpTool.inputSchema
|
|
19249
|
+
}
|
|
19250
|
+
};
|
|
19251
|
+
loop2.prefix.addTool(spec);
|
|
19252
|
+
accepted.push(mcpTool);
|
|
19253
|
+
}
|
|
19254
|
+
if (accepted.length === 0) return;
|
|
19255
|
+
if (target.report.tools.supported) {
|
|
19256
|
+
const merged = [...target.report.tools.items, ...accepted];
|
|
19257
|
+
target.report.tools.items = merged;
|
|
19258
|
+
target.toolCount = merged.length;
|
|
19259
|
+
}
|
|
19260
|
+
}
|
|
19261
|
+
|
|
18982
19262
|
// src/cli/ui/mcp-browse.ts
|
|
18983
19263
|
function formatResourceList(servers) {
|
|
18984
19264
|
const lines = [];
|
|
@@ -19107,14 +19387,7 @@ async function handleMcpBrowseSlash(kind, arg, servers, log) {
|
|
|
19107
19387
|
);
|
|
19108
19388
|
return;
|
|
19109
19389
|
}
|
|
19110
|
-
const client2 = server2.client;
|
|
19111
|
-
if (!client2) {
|
|
19112
|
-
log.pushWarning(
|
|
19113
|
-
`server [${server2.label}] is not connected (display-only)`,
|
|
19114
|
-
"Resource read requires a live MCP client."
|
|
19115
|
-
);
|
|
19116
|
-
return;
|
|
19117
|
-
}
|
|
19390
|
+
const client2 = server2.host.client;
|
|
19118
19391
|
try {
|
|
19119
19392
|
const result = await client2.readResource(arg);
|
|
19120
19393
|
log.pushInfo(formatResourceContents(arg, result));
|
|
@@ -19131,14 +19404,7 @@ async function handleMcpBrowseSlash(kind, arg, servers, log) {
|
|
|
19131
19404
|
);
|
|
19132
19405
|
return;
|
|
19133
19406
|
}
|
|
19134
|
-
const client = server.client;
|
|
19135
|
-
if (!client) {
|
|
19136
|
-
log.pushWarning(
|
|
19137
|
-
`server [${server.label}] is not connected (display-only)`,
|
|
19138
|
-
"Prompt fetch requires a live MCP client."
|
|
19139
|
-
);
|
|
19140
|
-
return;
|
|
19141
|
-
}
|
|
19407
|
+
const client = server.host.client;
|
|
19142
19408
|
try {
|
|
19143
19409
|
const result = await client.getPrompt(arg);
|
|
19144
19410
|
log.pushInfo(formatPromptMessages(arg, result));
|
|
@@ -20606,6 +20872,9 @@ var mcp = (args, loop2, ctx) => {
|
|
|
20606
20872
|
if (sub === "disable" || sub === "enable") {
|
|
20607
20873
|
return toggleDisabled(sub, args[1], { servers, specs });
|
|
20608
20874
|
}
|
|
20875
|
+
if (sub === "reconnect") {
|
|
20876
|
+
return triggerReconnect(args[1], servers, ctx.postInfo, loop2);
|
|
20877
|
+
}
|
|
20609
20878
|
const wantsTextDump = sub === "text";
|
|
20610
20879
|
if (servers.length === 0 && specs.length === 0 && toolSpecs.length === 0) {
|
|
20611
20880
|
return {
|
|
@@ -20681,29 +20950,35 @@ function toggleDisabled(action, rawName, ctx) {
|
|
|
20681
20950
|
const list2 = [...known].sort().join(", ") || "(none)";
|
|
20682
20951
|
return { info: `unknown MCP server "${name}". Known: ${list2}.` };
|
|
20683
20952
|
}
|
|
20684
|
-
|
|
20685
|
-
const current = new Set(cfg.mcpDisabled ?? []);
|
|
20686
|
-
if (action === "disable") {
|
|
20687
|
-
if (current.has(name)) {
|
|
20688
|
-
return { info: `\u25B8 ${name} is already disabled \u2014 restart to apply, or /mcp enable ${name}.` };
|
|
20689
|
-
}
|
|
20690
|
-
current.add(name);
|
|
20691
|
-
writeConfig({ ...cfg, mcpDisabled: [...current].sort() });
|
|
20692
|
-
return {
|
|
20693
|
-
info: `\u25B8 ${name} disabled \u2014 takes effect on next launch. /mcp enable ${name} to revert.`
|
|
20694
|
-
};
|
|
20695
|
-
}
|
|
20696
|
-
if (!current.has(name)) {
|
|
20697
|
-
return { info: `\u25B8 ${name} is not disabled.` };
|
|
20698
|
-
}
|
|
20699
|
-
current.delete(name);
|
|
20700
|
-
writeConfig({ ...cfg, mcpDisabled: current.size > 0 ? [...current].sort() : void 0 });
|
|
20701
|
-
return { info: `\u25B8 ${name} re-enabled \u2014 takes effect on next launch.` };
|
|
20953
|
+
return { info: toggleMcpDisabled(action, name) };
|
|
20702
20954
|
}
|
|
20703
20955
|
function parseLabelFromSpec(spec) {
|
|
20704
20956
|
const match = spec.match(/^([a-zA-Z_][a-zA-Z0-9_-]*)=/);
|
|
20705
20957
|
return match ? match[1] ?? null : null;
|
|
20706
20958
|
}
|
|
20959
|
+
function triggerReconnect(rawName, servers, postInfo, loop2) {
|
|
20960
|
+
const name = rawName?.trim();
|
|
20961
|
+
if (!name) {
|
|
20962
|
+
return {
|
|
20963
|
+
info: "usage: /mcp reconnect <name> \xB7 pick a name shown in /mcp."
|
|
20964
|
+
};
|
|
20965
|
+
}
|
|
20966
|
+
const target = servers.find((s) => s.label === name);
|
|
20967
|
+
if (!target) {
|
|
20968
|
+
const list2 = servers.map((s) => s.label).sort().join(", ");
|
|
20969
|
+
return { info: `unknown MCP server "${name}". Known: ${list2 || "(none)"}.` };
|
|
20970
|
+
}
|
|
20971
|
+
if (!postInfo) {
|
|
20972
|
+
return { info: "/mcp reconnect requires the interactive TUI (postInfo not wired)." };
|
|
20973
|
+
}
|
|
20974
|
+
return {
|
|
20975
|
+
info: kickOffMcpReconnect(
|
|
20976
|
+
target,
|
|
20977
|
+
postInfo,
|
|
20978
|
+
(t2, addedTools) => applyMcpAppend(loop2, t2, addedTools)
|
|
20979
|
+
)
|
|
20980
|
+
};
|
|
20981
|
+
}
|
|
20707
20982
|
var handlers7 = { mcp };
|
|
20708
20983
|
|
|
20709
20984
|
// src/cli/ui/slash/handlers/memory.ts
|
|
@@ -24660,7 +24935,9 @@ Continue executing from the next pending step. Call mark_step_complete after eac
|
|
|
24660
24935
|
{
|
|
24661
24936
|
servers: mcpServers ?? [],
|
|
24662
24937
|
configPath: defaultConfigPath(),
|
|
24663
|
-
onClose: () => setPendingMcpBrowser(false)
|
|
24938
|
+
onClose: () => setPendingMcpBrowser(false),
|
|
24939
|
+
postInfo: (text) => log.pushInfo(text),
|
|
24940
|
+
applyAppend: (target, addedTools) => applyMcpAppend(loop2, target, addedTools)
|
|
24664
24941
|
}
|
|
24665
24942
|
) : pendingPlan ? /* @__PURE__ */ React54.createElement(
|
|
24666
24943
|
PlanConfirm,
|
|
@@ -24796,33 +25073,6 @@ function Setup({ onReady }) {
|
|
|
24796
25073
|
)), error ? /* @__PURE__ */ React55.createElement(Box47, { marginTop: 1 }, /* @__PURE__ */ React55.createElement(Text49, { color: COLOR.err, bold: true }, GLYPH.err), /* @__PURE__ */ React55.createElement(Text49, { color: COLOR.err }, ` ${error}`)) : value ? /* @__PURE__ */ React55.createElement(Box47, { marginTop: 1 }, /* @__PURE__ */ React55.createElement(Text49, { dimColor: true }, ` preview \xB7 ${redactKey(value)}`)) : null, /* @__PURE__ */ React55.createElement(Box47, { marginTop: 1 }, /* @__PURE__ */ React55.createElement(Text49, { dimColor: true }, " /exit to abort")));
|
|
24797
25074
|
}
|
|
24798
25075
|
|
|
24799
|
-
// src/cli/ui/mcp-lifecycle.ts
|
|
24800
|
-
var STATE = {
|
|
24801
|
-
handshake: { glyph: "\u21BB", label: "handshake\u2026" },
|
|
24802
|
-
connected: { glyph: "\u2713", label: "connected" },
|
|
24803
|
-
failed: { glyph: "\u2716", label: "failed" },
|
|
24804
|
-
disabled: { glyph: "\u25CB", label: "disabled" }
|
|
24805
|
-
};
|
|
24806
|
-
var NAME_COL = 22;
|
|
24807
|
-
var STATE_COL = 15;
|
|
24808
|
-
function formatMcpLifecycleEvent(ev) {
|
|
24809
|
-
const { glyph, label } = STATE[ev.state];
|
|
24810
|
-
const namePart = `MCP \xB7 ${ev.name}`;
|
|
24811
|
-
const namePad = " ".repeat(Math.max(1, NAME_COL - namePart.length));
|
|
24812
|
-
const stateField = `${glyph} ${label}`.padEnd(STATE_COL);
|
|
24813
|
-
return `\u2318 ${namePart}${namePad}${stateField}${describeDetail(ev)}`;
|
|
24814
|
-
}
|
|
24815
|
-
function describeDetail(ev) {
|
|
24816
|
-
if (ev.state === "handshake") return "initialise \u2192 tools/list \u2192 resources/list";
|
|
24817
|
-
if (ev.state === "failed") return ev.reason;
|
|
24818
|
-
if (ev.state === "disabled") return `via /mcp disable ${ev.name}`;
|
|
24819
|
-
const parts = [`${ev.tools} tools`];
|
|
24820
|
-
if (ev.resources && ev.resources > 0) parts.push(`${ev.resources} resources`);
|
|
24821
|
-
if (ev.prompts && ev.prompts > 0) parts.push(`${ev.prompts} prompts`);
|
|
24822
|
-
parts.push(`${ev.ms}ms`);
|
|
24823
|
-
return parts.join(" \xB7 ");
|
|
24824
|
-
}
|
|
24825
|
-
|
|
24826
25076
|
// src/cli/ui/mcp-toast.ts
|
|
24827
25077
|
function formatMcpSlowToast(t2) {
|
|
24828
25078
|
const seconds = (t2.p95Ms / 1e3).toFixed(1);
|
|
@@ -24941,10 +25191,12 @@ async function chatCommand(opts) {
|
|
|
24941
25191
|
const transport = spec.transport === "sse" ? new SseTransport({ url: spec.url }) : spec.transport === "streamable-http" ? new StreamableHttpTransport({ url: spec.url }) : new StdioTransport({ command: spec.command, args: spec.args });
|
|
24942
25192
|
const mcp3 = new McpClient({ transport });
|
|
24943
25193
|
await mcp3.initialize();
|
|
25194
|
+
const host = { client: mcp3 };
|
|
24944
25195
|
const bridge = await bridgeMcpTools(mcp3, {
|
|
24945
25196
|
registry: tools,
|
|
24946
25197
|
namePrefix: prefix,
|
|
24947
25198
|
serverName: label,
|
|
25199
|
+
host,
|
|
24948
25200
|
onProgress: (info) => progressSink.current?.(info),
|
|
24949
25201
|
onSlow: (info) => process.stderr.write(
|
|
24950
25202
|
`${formatMcpSlowToast({ name: info.serverName, p95Ms: info.p95Ms, sampleSize: info.sampleSize })}
|
|
@@ -24986,7 +25238,8 @@ async function chatCommand(opts) {
|
|
|
24986
25238
|
spec: raw,
|
|
24987
25239
|
toolCount: bridge.registeredNames.length,
|
|
24988
25240
|
report,
|
|
24989
|
-
|
|
25241
|
+
host,
|
|
25242
|
+
bridgeEnv: bridge.env
|
|
24990
25243
|
});
|
|
24991
25244
|
} catch (err) {
|
|
24992
25245
|
const reason = err.message;
|