@corenel/sidecar 0.8.2 → 0.8.3
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.js +281 -47
- package/package.json +5 -5
package/dist/cli.js
CHANGED
|
@@ -14239,10 +14239,10 @@ var require_filters = __commonJS({
|
|
|
14239
14239
|
_exports.striptags = striptags;
|
|
14240
14240
|
function title(str10) {
|
|
14241
14241
|
str10 = normalize3(str10, "");
|
|
14242
|
-
var
|
|
14242
|
+
var words2 = str10.split(" ").map(function(word) {
|
|
14243
14243
|
return capitalize(word);
|
|
14244
14244
|
});
|
|
14245
|
-
return r2.copySafeness(str10,
|
|
14245
|
+
return r2.copySafeness(str10, words2.join(" "));
|
|
14246
14246
|
}
|
|
14247
14247
|
_exports.title = title;
|
|
14248
14248
|
function trim(str10) {
|
|
@@ -14297,7 +14297,7 @@ var require_filters = __commonJS({
|
|
|
14297
14297
|
length = Infinity;
|
|
14298
14298
|
}
|
|
14299
14299
|
var noFollowAttr = nofollow === true ? ' rel="nofollow"' : "";
|
|
14300
|
-
var
|
|
14300
|
+
var words2 = str10.split(/(\s+)/).filter(function(word) {
|
|
14301
14301
|
return word && word.length;
|
|
14302
14302
|
}).map(function(word) {
|
|
14303
14303
|
var matches = word.match(puncRe);
|
|
@@ -14317,13 +14317,13 @@ var require_filters = __commonJS({
|
|
|
14317
14317
|
}
|
|
14318
14318
|
return word;
|
|
14319
14319
|
});
|
|
14320
|
-
return
|
|
14320
|
+
return words2.join("");
|
|
14321
14321
|
}
|
|
14322
14322
|
_exports.urlize = urlize;
|
|
14323
14323
|
function wordcount(str10) {
|
|
14324
14324
|
str10 = normalize3(str10, "");
|
|
14325
|
-
var
|
|
14326
|
-
return
|
|
14325
|
+
var words2 = str10 ? str10.match(/\w+/g) : null;
|
|
14326
|
+
return words2 ? words2.length : null;
|
|
14327
14327
|
}
|
|
14328
14328
|
_exports.wordcount = wordcount;
|
|
14329
14329
|
function float(val, def) {
|
|
@@ -17408,10 +17408,10 @@ ${yamlContent}` : yamlContent;
|
|
|
17408
17408
|
if (!str10 || typeof str10 !== "string") {
|
|
17409
17409
|
return "";
|
|
17410
17410
|
}
|
|
17411
|
-
const
|
|
17411
|
+
const words2 = str10.split(/\s+/);
|
|
17412
17412
|
const lines = [];
|
|
17413
17413
|
let currentLine = "";
|
|
17414
|
-
for (const word of
|
|
17414
|
+
for (const word of words2) {
|
|
17415
17415
|
if (currentLine.length + word.length + 1 <= width) {
|
|
17416
17416
|
currentLine += (currentLine ? " " : "") + word;
|
|
17417
17417
|
} else {
|
|
@@ -18083,10 +18083,10 @@ ${content}` : parentData.content;
|
|
|
18083
18083
|
if (!str10 || typeof str10 !== "string") {
|
|
18084
18084
|
return "";
|
|
18085
18085
|
}
|
|
18086
|
-
const
|
|
18086
|
+
const words2 = str10.split(/\s+/);
|
|
18087
18087
|
const lines = [];
|
|
18088
18088
|
let currentLine = "";
|
|
18089
|
-
for (const word of
|
|
18089
|
+
for (const word of words2) {
|
|
18090
18090
|
if (currentLine.length + word.length + 1 <= width) {
|
|
18091
18091
|
currentLine += (currentLine ? " " : "") + word;
|
|
18092
18092
|
} else {
|
|
@@ -24888,7 +24888,7 @@ var require_abort_controller = __commonJS({
|
|
|
24888
24888
|
"use strict";
|
|
24889
24889
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24890
24890
|
var eventTargetShim = require_event_target_shim();
|
|
24891
|
-
var
|
|
24891
|
+
var AbortSignal2 = class extends eventTargetShim.EventTarget {
|
|
24892
24892
|
/**
|
|
24893
24893
|
* AbortSignal cannot be constructed directly.
|
|
24894
24894
|
*/
|
|
@@ -24907,9 +24907,9 @@ var require_abort_controller = __commonJS({
|
|
|
24907
24907
|
return aborted;
|
|
24908
24908
|
}
|
|
24909
24909
|
};
|
|
24910
|
-
eventTargetShim.defineEventAttribute(
|
|
24910
|
+
eventTargetShim.defineEventAttribute(AbortSignal2.prototype, "abort");
|
|
24911
24911
|
function createAbortSignal() {
|
|
24912
|
-
const signal = Object.create(
|
|
24912
|
+
const signal = Object.create(AbortSignal2.prototype);
|
|
24913
24913
|
eventTargetShim.EventTarget.call(signal);
|
|
24914
24914
|
abortedFlags.set(signal, false);
|
|
24915
24915
|
return signal;
|
|
@@ -24922,11 +24922,11 @@ var require_abort_controller = __commonJS({
|
|
|
24922
24922
|
signal.dispatchEvent({ type: "abort" });
|
|
24923
24923
|
}
|
|
24924
24924
|
var abortedFlags = /* @__PURE__ */ new WeakMap();
|
|
24925
|
-
Object.defineProperties(
|
|
24925
|
+
Object.defineProperties(AbortSignal2.prototype, {
|
|
24926
24926
|
aborted: { enumerable: true }
|
|
24927
24927
|
});
|
|
24928
24928
|
if (typeof Symbol === "function" && typeof Symbol.toStringTag === "symbol") {
|
|
24929
|
-
Object.defineProperty(
|
|
24929
|
+
Object.defineProperty(AbortSignal2.prototype, Symbol.toStringTag, {
|
|
24930
24930
|
configurable: true,
|
|
24931
24931
|
value: "AbortSignal"
|
|
24932
24932
|
});
|
|
@@ -24970,11 +24970,11 @@ var require_abort_controller = __commonJS({
|
|
|
24970
24970
|
});
|
|
24971
24971
|
}
|
|
24972
24972
|
exports.AbortController = AbortController2;
|
|
24973
|
-
exports.AbortSignal =
|
|
24973
|
+
exports.AbortSignal = AbortSignal2;
|
|
24974
24974
|
exports.default = AbortController2;
|
|
24975
24975
|
module.exports = AbortController2;
|
|
24976
24976
|
module.exports.AbortController = module.exports["default"] = AbortController2;
|
|
24977
|
-
module.exports.AbortSignal =
|
|
24977
|
+
module.exports.AbortSignal = AbortSignal2;
|
|
24978
24978
|
}
|
|
24979
24979
|
});
|
|
24980
24980
|
|
|
@@ -41720,6 +41720,9 @@ var BUILTIN_NAMESPACES = {
|
|
|
41720
41720
|
spawn_agents: "agent",
|
|
41721
41721
|
call_agent: "agent",
|
|
41722
41722
|
todo_write: "agent",
|
|
41723
|
+
// Finds deferred MCP tools (toolSearch.ts). Deliberately not `system`, which
|
|
41724
|
+
// the standard policy denies: search is a read and must always be reachable.
|
|
41725
|
+
tool_search: "agent",
|
|
41723
41726
|
// Named here as a backstop; the sidecar's own decls also carry `namespace`,
|
|
41724
41727
|
// and an explicit field always wins (resolveNamespace). Both say the same
|
|
41725
41728
|
// thing so a sidecar that predates this still lands in the right namespace.
|
|
@@ -41807,14 +41810,115 @@ ${ref.preview}${ref.bytes > ref.preview.length ? "\n\u2026(truncated)" : ""}` :
|
|
|
41807
41810
|
// ../harness/prompts/toolsMarker.ts
|
|
41808
41811
|
var TOOLS_MARKER = "\uE000PROMPD_TOOLS\uE000";
|
|
41809
41812
|
function formatToolLines(tools) {
|
|
41810
|
-
return tools.map((t2) =>
|
|
41813
|
+
return tools.map((t2) => `\`${t2.name}\``).join(", ");
|
|
41811
41814
|
}
|
|
41812
41815
|
function fillToolsMarker(system, tools) {
|
|
41813
41816
|
if (!system.includes(TOOLS_MARKER)) return system;
|
|
41814
41817
|
return system.replaceAll(TOOLS_MARKER, tools.length ? formatToolLines(tools) : "(no tools available this run)");
|
|
41815
41818
|
}
|
|
41816
41819
|
|
|
41820
|
+
// ../harness/tools/toolSearch.ts
|
|
41821
|
+
init_protocol();
|
|
41822
|
+
var TOOL_SEARCH_NAME = "tool_search";
|
|
41823
|
+
var LOADED_TOOLS_CAP = 24;
|
|
41824
|
+
var DEFAULT_LIMIT = 8;
|
|
41825
|
+
var MAX_LIMIT = 20;
|
|
41826
|
+
function serverOf(tool) {
|
|
41827
|
+
return parseMcpWireName(tool.name)?.server ?? "mcp";
|
|
41828
|
+
}
|
|
41829
|
+
function words(s2) {
|
|
41830
|
+
return s2.toLowerCase().split(/[^a-z0-9]+/).filter((w2) => w2.length > 1);
|
|
41831
|
+
}
|
|
41832
|
+
function rankTools(query, tools, opts = {}) {
|
|
41833
|
+
const limit2 = Math.min(MAX_LIMIT, Math.max(1, Math.floor(opts.limit ?? DEFAULT_LIMIT)));
|
|
41834
|
+
const pool = opts.server ? tools.filter((t2) => serverOf(t2) === opts.server) : tools;
|
|
41835
|
+
const q2 = words(query);
|
|
41836
|
+
const scored = pool.map((tool, i2) => {
|
|
41837
|
+
const parsed = parseMcpWireName(tool.name);
|
|
41838
|
+
const nameWords = words(parsed?.tool ?? tool.name);
|
|
41839
|
+
const serverWords = words(parsed?.server ?? "");
|
|
41840
|
+
const descWords = words(tool.description ?? "");
|
|
41841
|
+
let score = 0;
|
|
41842
|
+
for (const w2 of q2) {
|
|
41843
|
+
if (nameWords.includes(w2)) score += 3;
|
|
41844
|
+
else if (nameWords.some((n2) => n2.startsWith(w2) || w2.startsWith(n2))) score += 2;
|
|
41845
|
+
if (serverWords.includes(w2)) score += 2;
|
|
41846
|
+
if (descWords.includes(w2)) score += 1;
|
|
41847
|
+
else if (descWords.some((d2) => d2.startsWith(w2))) score += 0.5;
|
|
41848
|
+
}
|
|
41849
|
+
return { tool, score, i: i2 };
|
|
41850
|
+
});
|
|
41851
|
+
return scored.filter((s2) => s2.score > 0 || opts.server).sort((a2, b2) => b2.score - a2.score || a2.i - b2.i).slice(0, limit2).map((s2) => s2.tool);
|
|
41852
|
+
}
|
|
41853
|
+
function serverIndex(deferred) {
|
|
41854
|
+
const counts = /* @__PURE__ */ new Map();
|
|
41855
|
+
for (const t2 of deferred) counts.set(serverOf(t2), (counts.get(serverOf(t2)) ?? 0) + 1);
|
|
41856
|
+
const lines = [...counts].map(([s2, n2]) => `- ${s2} \xB7 ${n2} tool${n2 === 1 ? "" : "s"}`);
|
|
41857
|
+
return [
|
|
41858
|
+
`Connected services. Their tools are not loaded until you find them: call \`${TOOL_SEARCH_NAME}\` with what you want to do (and optionally the server) before concluding something is unavailable. Tools it returns can be called directly from then on.`,
|
|
41859
|
+
...lines
|
|
41860
|
+
].join("\n");
|
|
41861
|
+
}
|
|
41862
|
+
function createToolLoader(opts) {
|
|
41863
|
+
const cap = opts.cap ?? LOADED_TOOLS_CAP;
|
|
41864
|
+
const byName = new Map(opts.deferred.map((t2) => [t2.name, t2]));
|
|
41865
|
+
const incoming = opts.loaded ?? [];
|
|
41866
|
+
let order = incoming.filter((n2, i2) => byName.has(n2) && incoming.indexOf(n2) === i2).slice(-cap);
|
|
41867
|
+
if (order.length !== incoming.length) opts.onChange?.([...order]);
|
|
41868
|
+
const add = (names) => {
|
|
41869
|
+
const before = new Set(order);
|
|
41870
|
+
for (const n2 of names) {
|
|
41871
|
+
order = order.filter((x2) => x2 !== n2);
|
|
41872
|
+
order.push(n2);
|
|
41873
|
+
}
|
|
41874
|
+
if (order.length > cap) order = order.slice(order.length - cap);
|
|
41875
|
+
const changed = order.length !== before.size || order.some((n2) => !before.has(n2));
|
|
41876
|
+
if (changed) opts.onChange?.([...order]);
|
|
41877
|
+
};
|
|
41878
|
+
const searchTool = {
|
|
41879
|
+
name: TOOL_SEARCH_NAME,
|
|
41880
|
+
namespace: "agent",
|
|
41881
|
+
description: "Find tools on connected services (MCP servers) by what you want to do. Returns matching tools with their input schemas and makes them callable from your next step. Search before concluding a service cannot do something.",
|
|
41882
|
+
parameters: {
|
|
41883
|
+
type: "object",
|
|
41884
|
+
properties: {
|
|
41885
|
+
query: { type: "string", description: 'What you want to do, in plain words, e.g. "send an email" or "list calendar events".' },
|
|
41886
|
+
server: { type: "string", description: "Only search this server (a name from the connected services list)." },
|
|
41887
|
+
limit: { type: "number", description: `Most results to return (default ${DEFAULT_LIMIT}, max ${MAX_LIMIT}).` }
|
|
41888
|
+
},
|
|
41889
|
+
required: ["query"]
|
|
41890
|
+
},
|
|
41891
|
+
mutates: false,
|
|
41892
|
+
// The schemas ARE the answer; offloading them behind a pointer would hide
|
|
41893
|
+
// exactly what the model searched for.
|
|
41894
|
+
noOffload: true,
|
|
41895
|
+
async run(args) {
|
|
41896
|
+
const query = typeof args.query === "string" ? args.query.trim() : "";
|
|
41897
|
+
const server = typeof args.server === "string" && args.server ? args.server : void 0;
|
|
41898
|
+
if (!query && !server) return "Error: pass a query describing what you want to do.";
|
|
41899
|
+
const limit2 = typeof args.limit === "number" ? args.limit : void 0;
|
|
41900
|
+
const found = rankTools(query, opts.deferred, { server, limit: limit2 });
|
|
41901
|
+
add(found.map((t2) => t2.name));
|
|
41902
|
+
const servers = [...new Set(opts.deferred.map(serverOf))];
|
|
41903
|
+
return JSON.stringify({
|
|
41904
|
+
tools: found.map((t2) => ({ name: t2.name, server: serverOf(t2), description: t2.description, inputSchema: t2.parameters })),
|
|
41905
|
+
note: found.length ? "These tools are now loaded; call them directly." : `No tools matched. Connected servers: ${servers.join(", ") || "(none)"}. Try other words or name a server.`
|
|
41906
|
+
});
|
|
41907
|
+
}
|
|
41908
|
+
};
|
|
41909
|
+
return {
|
|
41910
|
+
searchTool,
|
|
41911
|
+
loaded: () => order.map((n2) => byName.get(n2)).filter((t2) => !!t2),
|
|
41912
|
+
loadedNames: () => [...order],
|
|
41913
|
+
touch: (name) => {
|
|
41914
|
+
if (!order.includes(name)) return;
|
|
41915
|
+
order = [...order.filter((n2) => n2 !== name), name];
|
|
41916
|
+
}
|
|
41917
|
+
};
|
|
41918
|
+
}
|
|
41919
|
+
|
|
41817
41920
|
// ../harness/core/loop.ts
|
|
41921
|
+
var MAX_PROVIDER_TOOLS = 128;
|
|
41818
41922
|
async function runAgent(args) {
|
|
41819
41923
|
const { client, system, tools, toolCtx } = args;
|
|
41820
41924
|
const policy = args.policy ?? STANDARD_POLICY;
|
|
@@ -41840,17 +41944,28 @@ async function runAgent(args) {
|
|
|
41840
41944
|
turnAc?.abort();
|
|
41841
41945
|
});
|
|
41842
41946
|
const messages = [...args.messages];
|
|
41843
|
-
const
|
|
41947
|
+
const deferred = args.deferredTools ?? [];
|
|
41948
|
+
const loader = deferred.length ? createToolLoader({ deferred, loaded: args.loadedTools, onChange: (names) => emit({ type: "tools-loaded", names }) }) : null;
|
|
41949
|
+
const eager = loader ? [...tools, loader.searchTool] : tools;
|
|
41950
|
+
const capped = (list) => list.length > MAX_PROVIDER_TOOLS ? list.slice(0, MAX_PROVIDER_TOOLS) : list;
|
|
41951
|
+
const activeTools = () => capped(loader ? [...eager, ...loader.loaded()] : eager);
|
|
41952
|
+
let sent = activeTools();
|
|
41953
|
+
const indexedSystem = loader ? system ? `${system}
|
|
41954
|
+
|
|
41955
|
+
${serverIndex(deferred)}` : serverIndex(deferred) : system;
|
|
41956
|
+
const filledSystem = indexedSystem ? fillToolsMarker(indexedSystem, sent) : indexedSystem;
|
|
41844
41957
|
if (filledSystem && !messages.some((m2) => m2.role === "system")) {
|
|
41845
41958
|
messages.unshift({ role: "system", content: filledSystem });
|
|
41846
41959
|
}
|
|
41847
|
-
const
|
|
41960
|
+
const defsFor = (list) => list.map((t2) => ({
|
|
41848
41961
|
type: "function",
|
|
41849
41962
|
function: { name: t2.name, description: t2.description, parameters: t2.parameters }
|
|
41850
41963
|
}));
|
|
41851
|
-
|
|
41964
|
+
let toolDefs = defsFor(sent);
|
|
41965
|
+
let toolByName = new Map(sent.map((t2) => [t2.name, t2]));
|
|
41852
41966
|
if (compressionStrategy === "deferred" && compression && compression.mode !== "off" && toolCtx.recall) {
|
|
41853
|
-
|
|
41967
|
+
const known = new Map([...eager, ...deferred].map((t2) => [t2.name, t2]));
|
|
41968
|
+
await compactIncomingOffloads(messages, known, compression, toolCtx.recall, emit);
|
|
41854
41969
|
}
|
|
41855
41970
|
const usage = { promptTokens: 0, completionTokens: 0 };
|
|
41856
41971
|
let text = "";
|
|
@@ -41879,6 +41994,11 @@ async function runAgent(args) {
|
|
|
41879
41994
|
break;
|
|
41880
41995
|
}
|
|
41881
41996
|
}
|
|
41997
|
+
if (loader) {
|
|
41998
|
+
sent = activeTools();
|
|
41999
|
+
toolDefs = defsFor(sent);
|
|
42000
|
+
toolByName = new Map(sent.map((t2) => [t2.name, t2]));
|
|
42001
|
+
}
|
|
41882
42002
|
let streamed;
|
|
41883
42003
|
let streamFail = null;
|
|
41884
42004
|
const model = args.getModel?.() ?? args.model;
|
|
@@ -42005,6 +42125,7 @@ async function runAgent(args) {
|
|
|
42005
42125
|
emit({ type: "tool-result", id: call.id, name: call.name, result: "unknown tool", isError: true });
|
|
42006
42126
|
continue;
|
|
42007
42127
|
}
|
|
42128
|
+
loader?.touch(tool.name);
|
|
42008
42129
|
const address = toolAddress(tool);
|
|
42009
42130
|
const gate = evaluateToolCall(policy, gstate, { name: call.name, exists: true, address });
|
|
42010
42131
|
if (!gate.ok) {
|
|
@@ -46193,7 +46314,7 @@ var FLAG_TO_KEY = {
|
|
|
46193
46314
|
"--default-model": "defaultModel",
|
|
46194
46315
|
"--name": "name"
|
|
46195
46316
|
};
|
|
46196
|
-
var DEFAULT_GUEST_IMAGE2 = "prompd/corenel-sandbox:0.8.
|
|
46317
|
+
var DEFAULT_GUEST_IMAGE2 = "prompd/corenel-sandbox:0.8.3";
|
|
46197
46318
|
function parseArgsWithProvenance(argv) {
|
|
46198
46319
|
const explicit = /* @__PURE__ */ new Set();
|
|
46199
46320
|
for (const a2 of argv) {
|
|
@@ -48914,7 +49035,8 @@ var McpHttpClient = class {
|
|
|
48914
49035
|
* loop -- a credential that fails twice is refused, not flaky. */
|
|
48915
49036
|
async send(method, params, isRetry = false) {
|
|
48916
49037
|
const headers = await this.buildHeaders();
|
|
48917
|
-
const
|
|
49038
|
+
const id = this.nextRequestId++;
|
|
49039
|
+
const res = await this.request(headers, id, method, params);
|
|
48918
49040
|
if (res.status === 401) {
|
|
48919
49041
|
if (!isRetry && await this.auth.onUnauthorized()) {
|
|
48920
49042
|
return this.send(method, params, true);
|
|
@@ -48925,26 +49047,77 @@ var McpHttpClient = class {
|
|
|
48925
49047
|
}
|
|
48926
49048
|
const sessionId = res.headers.get(SESSION_HEADER);
|
|
48927
49049
|
if (sessionId) this.sessionId = sessionId;
|
|
49050
|
+
if (!res.ok) {
|
|
49051
|
+
throw new Error(`MCP server "${this.server.name}" returned HTTP ${res.status}`);
|
|
49052
|
+
}
|
|
48928
49053
|
const contentType = res.headers.get("content-type") ?? "";
|
|
49054
|
+
let body2;
|
|
48929
49055
|
if (contentType.includes("text/event-stream")) {
|
|
48930
|
-
|
|
48931
|
-
|
|
48932
|
-
);
|
|
48933
|
-
}
|
|
48934
|
-
if (!contentType.includes("application/json")) {
|
|
49056
|
+
body2 = await this.readSseResponse(res, id);
|
|
49057
|
+
} else if (contentType.includes("application/json")) {
|
|
49058
|
+
body2 = await res.json();
|
|
49059
|
+
} else {
|
|
48935
49060
|
throw new Error(
|
|
48936
49061
|
`MCP server "${this.server.name}" answered with an unexpected content-type: "${contentType || "(none)"}"`
|
|
48937
49062
|
);
|
|
48938
49063
|
}
|
|
48939
|
-
if (!res.ok) {
|
|
48940
|
-
throw new Error(`MCP server "${this.server.name}" returned HTTP ${res.status}`);
|
|
48941
|
-
}
|
|
48942
|
-
const body2 = await res.json();
|
|
48943
49064
|
if (body2.error) {
|
|
48944
49065
|
throw new Error(`MCP server "${this.server.name}" returned error ${body2.error.code}: ${body2.error.message}`);
|
|
48945
49066
|
}
|
|
48946
49067
|
return body2.result;
|
|
48947
49068
|
}
|
|
49069
|
+
/** Read an SSE body until the JSON-RPC response for `id` arrives.
|
|
49070
|
+
*
|
|
49071
|
+
* Events are separated by a blank line; an event's `data:` lines are joined
|
|
49072
|
+
* with newlines, per the SSE spec, and parsed as one JSON-RPC message.
|
|
49073
|
+
* Notifications (no `id`) and responses to other ids are skipped. The reader
|
|
49074
|
+
* is cancelled once the answer is in, so a server that keeps the stream open
|
|
49075
|
+
* after replying cannot hold this call open with it. */
|
|
49076
|
+
async readSseResponse(res, id) {
|
|
49077
|
+
if (!res.body) {
|
|
49078
|
+
throw new Error(`MCP server "${this.server.name}" closed the stream without a response`);
|
|
49079
|
+
}
|
|
49080
|
+
const reader = res.body.getReader();
|
|
49081
|
+
const decoder = new TextDecoder();
|
|
49082
|
+
let buffer = "";
|
|
49083
|
+
try {
|
|
49084
|
+
for (; ; ) {
|
|
49085
|
+
const { done, value } = await reader.read();
|
|
49086
|
+
if (value) buffer += decoder.decode(value, { stream: true });
|
|
49087
|
+
buffer = buffer.replace(/\r\n/g, "\n");
|
|
49088
|
+
let boundary = buffer.indexOf("\n\n");
|
|
49089
|
+
while (boundary !== -1) {
|
|
49090
|
+
const event = buffer.slice(0, boundary);
|
|
49091
|
+
buffer = buffer.slice(boundary + 2);
|
|
49092
|
+
const match = this.matchSseEvent(event, id);
|
|
49093
|
+
if (match) return match;
|
|
49094
|
+
boundary = buffer.indexOf("\n\n");
|
|
49095
|
+
}
|
|
49096
|
+
if (done) {
|
|
49097
|
+
const tail3 = buffer.trim() ? this.matchSseEvent(buffer, id) : null;
|
|
49098
|
+
if (tail3) return tail3;
|
|
49099
|
+
throw new Error(`MCP server "${this.server.name}" closed the stream without a response`);
|
|
49100
|
+
}
|
|
49101
|
+
}
|
|
49102
|
+
} finally {
|
|
49103
|
+
reader.cancel().catch(() => {
|
|
49104
|
+
});
|
|
49105
|
+
}
|
|
49106
|
+
}
|
|
49107
|
+
/** The JSON-RPC response in one SSE event, if it answers `id`; else null. */
|
|
49108
|
+
matchSseEvent(event, id) {
|
|
49109
|
+
const data = event.split("\n").filter((line) => line.startsWith("data:")).map((line) => line.slice(5).replace(/^ /, "")).join("\n");
|
|
49110
|
+
if (!data) return null;
|
|
49111
|
+
let parsed;
|
|
49112
|
+
try {
|
|
49113
|
+
parsed = JSON.parse(data);
|
|
49114
|
+
} catch {
|
|
49115
|
+
return null;
|
|
49116
|
+
}
|
|
49117
|
+
if (!isRecord3(parsed) || parsed.id !== id) return null;
|
|
49118
|
+
if (!("result" in parsed) && !("error" in parsed)) return null;
|
|
49119
|
+
return parsed;
|
|
49120
|
+
}
|
|
48948
49121
|
/** The bare `fetch()` call, wrapped -- every OTHER throw in `send` already
|
|
48949
49122
|
* names the server; this is the one call whose own failures (DNS miss,
|
|
48950
49123
|
* connection refused, TLS failure) never reach `send`'s response-handling
|
|
@@ -48954,12 +49127,12 @@ var McpHttpClient = class {
|
|
|
48954
49127
|
* whose entire job is diagnosis. `err.cause` carries the real reason
|
|
48955
49128
|
* (ECONNREFUSED, ENOTFOUND, a TLS alert); undici puts it there rather than
|
|
48956
49129
|
* in `err.message`. */
|
|
48957
|
-
async request(headers, method, params) {
|
|
49130
|
+
async request(headers, id, method, params) {
|
|
48958
49131
|
try {
|
|
48959
49132
|
return await this.fetchImpl(this.server.url, {
|
|
48960
49133
|
method: "POST",
|
|
48961
49134
|
headers,
|
|
48962
|
-
body: JSON.stringify({ jsonrpc: "2.0", id
|
|
49135
|
+
body: JSON.stringify({ jsonrpc: "2.0", id, method, params })
|
|
48963
49136
|
});
|
|
48964
49137
|
} catch (err) {
|
|
48965
49138
|
const cause = err instanceof Error && err.cause instanceof Error ? err.cause.message : err instanceof Error ? err.message : String(err);
|
|
@@ -49020,6 +49193,25 @@ function authProviderFor(stateDir, server, fetchImpl = fetch, accountToken = asy
|
|
|
49020
49193
|
|
|
49021
49194
|
// src/mcp/registry.ts
|
|
49022
49195
|
init_store();
|
|
49196
|
+
|
|
49197
|
+
// src/mcp/timedFetch.ts
|
|
49198
|
+
var AUTH_REQUEST_TIMEOUT_MS = 15e3;
|
|
49199
|
+
function timedFetch(ms, inner = fetch) {
|
|
49200
|
+
return async (input, init2 = {}) => {
|
|
49201
|
+
const deadline = AbortSignal.timeout(ms);
|
|
49202
|
+
const signal = init2.signal ? AbortSignal.any([init2.signal, deadline]) : deadline;
|
|
49203
|
+
try {
|
|
49204
|
+
return await inner(input, { ...init2, signal });
|
|
49205
|
+
} catch (err) {
|
|
49206
|
+
if (deadline.aborted && !init2.signal?.aborted) {
|
|
49207
|
+
throw new Error(`no answer within ${ms / 1e3}s`, { cause: err });
|
|
49208
|
+
}
|
|
49209
|
+
throw err;
|
|
49210
|
+
}
|
|
49211
|
+
};
|
|
49212
|
+
}
|
|
49213
|
+
|
|
49214
|
+
// src/mcp/registry.ts
|
|
49023
49215
|
async function accountTokenFor(stateDir) {
|
|
49024
49216
|
try {
|
|
49025
49217
|
const cred = await resolveCredential(process.env, stateDir);
|
|
@@ -49029,8 +49221,12 @@ async function accountTokenFor(stateDir) {
|
|
|
49029
49221
|
}
|
|
49030
49222
|
}
|
|
49031
49223
|
function defaultClientFactory(stateDir, server) {
|
|
49032
|
-
return new McpHttpClient(
|
|
49224
|
+
return new McpHttpClient(
|
|
49225
|
+
server,
|
|
49226
|
+
authProviderFor(stateDir, server, timedFetch(AUTH_REQUEST_TIMEOUT_MS), () => accountTokenFor(stateDir))
|
|
49227
|
+
);
|
|
49033
49228
|
}
|
|
49229
|
+
var SERVER_CONNECT_TIMEOUT_MS = 3e4;
|
|
49034
49230
|
var McpRegistry = class {
|
|
49035
49231
|
constructor(stateDir, clientFactory = defaultClientFactory) {
|
|
49036
49232
|
this.stateDir = stateDir;
|
|
@@ -49054,27 +49250,60 @@ var McpRegistry = class {
|
|
|
49054
49250
|
* a server that fails to initialize, or fails listing tools, is recorded
|
|
49055
49251
|
* in `status()` with zero tools and the daemon keeps starting.
|
|
49056
49252
|
*/
|
|
49057
|
-
async connectAll() {
|
|
49253
|
+
async connectAll(opts = {}) {
|
|
49058
49254
|
this.connected.clear();
|
|
49059
49255
|
this.statuses.clear();
|
|
49060
49256
|
const servers = await readMcpServers(this.stateDir);
|
|
49061
49257
|
this.serverOrder = servers.map((s2) => s2.name);
|
|
49062
|
-
|
|
49258
|
+
const timeoutMs = opts.timeoutMs ?? SERVER_CONNECT_TIMEOUT_MS;
|
|
49259
|
+
await Promise.all(servers.map((server) => this.connectOne(server, timeoutMs, opts.onServer)));
|
|
49063
49260
|
}
|
|
49064
|
-
|
|
49065
|
-
|
|
49261
|
+
/* BOUNDED, AND SETTLED EXACTLY ONCE.
|
|
49262
|
+
*
|
|
49263
|
+
* Every server connects under one Promise.all, and daemon startup awaits it.
|
|
49264
|
+
* With no bound, one stalled server -- measured: an expired Microsoft 365
|
|
49265
|
+
* token refreshing against a cold service, still waiting at 60 seconds --
|
|
49266
|
+
* left the terminal silent after `mirrors sessions`, indistinguishable from a
|
|
49267
|
+
* crash.
|
|
49268
|
+
*
|
|
49269
|
+
* The deadline covers the whole connect (any credential refresh, initialize,
|
|
49270
|
+
* tools/list), not each request, because several individually bounded steps
|
|
49271
|
+
* in a row can still add up to a hang. The attempt that loses the race keeps
|
|
49272
|
+
* running; its late result is deliberately ignored, so a server reported as
|
|
49273
|
+
* timed out cannot quietly become connected afterwards -- status would then
|
|
49274
|
+
* tell doctor one thing and the registered tools another. */
|
|
49275
|
+
async connectOne(server, timeoutMs, onServer) {
|
|
49276
|
+
let timer;
|
|
49277
|
+
const deadline = new Promise((resolve13) => {
|
|
49278
|
+
timer = setTimeout(() => resolve13("timeout"), timeoutMs);
|
|
49279
|
+
});
|
|
49280
|
+
const attempt = (async () => {
|
|
49066
49281
|
const client = this.clientFactory(this.stateDir, server);
|
|
49067
49282
|
await client.initialize();
|
|
49068
49283
|
const tools = await client.listTools();
|
|
49069
|
-
|
|
49070
|
-
|
|
49284
|
+
return { client, tools };
|
|
49285
|
+
})();
|
|
49286
|
+
attempt.catch(() => {
|
|
49287
|
+
});
|
|
49288
|
+
try {
|
|
49289
|
+
const outcome = await Promise.race([attempt, deadline]);
|
|
49290
|
+
if (outcome === "timeout") {
|
|
49291
|
+
this.statuses.set(server.name, { connected: false, toolCount: 0, error: `timed out after ${timeoutMs / 1e3}s` });
|
|
49292
|
+
} else {
|
|
49293
|
+
this.connected.set(server.name, outcome);
|
|
49294
|
+
this.statuses.set(server.name, { connected: true, toolCount: outcome.tools.length });
|
|
49295
|
+
}
|
|
49071
49296
|
} catch (err) {
|
|
49072
49297
|
this.statuses.set(server.name, {
|
|
49073
49298
|
connected: false,
|
|
49074
49299
|
toolCount: 0,
|
|
49075
49300
|
error: err instanceof Error ? err.message : String(err)
|
|
49076
49301
|
});
|
|
49302
|
+
} finally {
|
|
49303
|
+
clearTimeout(timer);
|
|
49077
49304
|
}
|
|
49305
|
+
const settled = this.statuses.get(server.name);
|
|
49306
|
+
if (settled) onServer?.({ server: server.name, ...settled });
|
|
49078
49307
|
}
|
|
49079
49308
|
/** Every tool from every connected server, paired with the server it came
|
|
49080
49309
|
* from, in CONFIGURED order (`serverOrder`) -- not the order servers
|
|
@@ -49110,7 +49339,12 @@ var McpRegistry = class {
|
|
|
49110
49339
|
* that records a failure without a reason, because "no tools" alone tells
|
|
49111
49340
|
* the reader nothing about what to fix. */
|
|
49112
49341
|
status() {
|
|
49113
|
-
|
|
49342
|
+
const out = [];
|
|
49343
|
+
for (const server of this.serverOrder) {
|
|
49344
|
+
const s2 = this.statuses.get(server);
|
|
49345
|
+
if (s2) out.push({ server, ...s2 });
|
|
49346
|
+
}
|
|
49347
|
+
return out;
|
|
49114
49348
|
}
|
|
49115
49349
|
};
|
|
49116
49350
|
|
|
@@ -49755,11 +49989,11 @@ async function main() {
|
|
|
49755
49989
|
const procs = args.allowShell ? createNodeProcs({ roots: args.roots, log: slog }) : void 0;
|
|
49756
49990
|
const mcpPolicy = buildMcpPolicy(fileConfig);
|
|
49757
49991
|
const mcpRegistry = new McpRegistry(args.stateDir);
|
|
49758
|
-
|
|
49992
|
+
slog("mcp: connecting configured servers\u2026");
|
|
49993
|
+
await mcpRegistry.connectAll({
|
|
49994
|
+
onServer: (s2) => slog(s2.connected ? `mcp: "${s2.server}" connected -- ${s2.toolCount} tool(s)` : `mcp: "${s2.server}" not connected -- ${s2.error} (run: corenel mcp doctor)`)
|
|
49995
|
+
});
|
|
49759
49996
|
const unregisterMcpTools = registerMcpTools(mcpRegistry, mcpPolicy);
|
|
49760
|
-
for (const s2 of mcpRegistry.status()) {
|
|
49761
|
-
slog(s2.connected ? `mcp: "${s2.server}" connected -- ${s2.toolCount} tool(s)` : `mcp: "${s2.server}" not connected -- ${s2.error} (run: corenel mcp doctor)`);
|
|
49762
|
-
}
|
|
49763
49997
|
const mcpPort = buildMcpPort(mcpRegistry, mcpPolicy);
|
|
49764
49998
|
const mcpAdminPort = buildMcpAdmin(args.stateDir);
|
|
49765
49999
|
const sessions = runner ? await wireSessions({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@corenel/sidecar",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Elastic-2.0",
|
|
6
6
|
"description": "Prompd sidecar — a local capability server. Run it next to Prompd (prompd.app) so the browser app can reach your machine: local LLMs (Ollama/LM Studio/vLLM) without CORS, your filesystem, and (opt-in) shell.",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
"esbuild": "^0.21.5",
|
|
37
37
|
"tsx": "^4.19.2",
|
|
38
38
|
"typescript": "^5.9.3",
|
|
39
|
-
"@corenel/
|
|
40
|
-
"@corenel/
|
|
41
|
-
"@corenel/
|
|
42
|
-
"@corenel/
|
|
39
|
+
"@corenel/protocol": "0.7.1",
|
|
40
|
+
"@corenel/harness": "0.7.1",
|
|
41
|
+
"@corenel/tools-node": "0.3.3",
|
|
42
|
+
"@corenel/crew": "0.0.0"
|
|
43
43
|
},
|
|
44
44
|
"repository": {
|
|
45
45
|
"type": "git",
|