frizz-server 0.15.10 → 0.15.11
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/claude-agent-broker.js +434 -13
- package/dist/dev-child.js +218 -72
- package/package.json +1 -1
- package/runtime/cc-worker/bin/frizz-mcp.mjs +22 -11
- package/web-dist/assets/{abnfDiagram-VRR7QNED-CPgDRCPL.js → abnfDiagram-VRR7QNED-NqOfYLl_.js} +1 -1
- package/web-dist/assets/{arc-BHLpLJBo.js → arc-D9UsqPOW.js} +1 -1
- package/web-dist/assets/architecture-TIHT7OUA-CYx5mu8e.js +1 -0
- package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-D0qwvPWx.js → architectureDiagram-ZJ3FMSHR-CRU2_T_H.js} +1 -1
- package/web-dist/assets/{blockDiagram-677ZJIJ3-C3NPa6bJ.js → blockDiagram-677ZJIJ3-DZT8OjJK.js} +1 -1
- package/web-dist/assets/{c4Diagram-LMCZKHZV-BW9GHi19.js → c4Diagram-LMCZKHZV-k-Ft1ThH.js} +1 -1
- package/web-dist/assets/channel-CD-hjjIL.js +1 -0
- package/web-dist/assets/{chunk-32BRIVSS-DHkNUXJc.js → chunk-32BRIVSS-DDMMpuZb.js} +1 -1
- package/web-dist/assets/{chunk-52WLFC77-CD7yIcMj.js → chunk-52WLFC77-BW9KF5RJ.js} +1 -1
- package/web-dist/assets/{chunk-C7G6YPKG-DCNeb-B6.js → chunk-C7G6YPKG-B4caDIAT.js} +1 -1
- package/web-dist/assets/{chunk-EX3LRPZG-YD5FxMO3.js → chunk-EX3LRPZG-Dyz0PnIY.js} +1 -1
- package/web-dist/assets/{chunk-FWX5IMBZ-XPo-YcnN.js → chunk-FWX5IMBZ-Dxhqduv9.js} +2 -2
- package/web-dist/assets/{chunk-HOUHSVGY-D6MFIjXX.js → chunk-HOUHSVGY-Du7Ln4sO.js} +1 -1
- package/web-dist/assets/{chunk-ICXQ74PX-B4YNEdT-.js → chunk-ICXQ74PX-sNRNHZp6.js} +1 -1
- package/web-dist/assets/{chunk-MOJQB5TN-DmeXNe3e.js → chunk-MOJQB5TN-BsgPysrE.js} +1 -1
- package/web-dist/assets/{chunk-OGEWGWER-DOiD4wAh.js → chunk-OGEWGWER-DZ288hvO.js} +1 -1
- package/web-dist/assets/{chunk-PUDLZKDR-1SEr54_e.js → chunk-PUDLZKDR-DuU7e-Sm.js} +1 -1
- package/web-dist/assets/{chunk-Q4XR5HBZ-CyWVwYh2.js → chunk-Q4XR5HBZ-BO2aVRAy.js} +1 -1
- package/web-dist/assets/{chunk-RYQCIY6F-ClBJ_iGr.js → chunk-RYQCIY6F-pQoqlm5b.js} +1 -1
- package/web-dist/assets/{chunk-V7JOEXUC-Bz8VXwYP.js → chunk-V7JOEXUC-DkkQI0OQ.js} +1 -1
- package/web-dist/assets/{chunk-VAUOI2AC-DawR4suA.js → chunk-VAUOI2AC-D3_hzRfs.js} +1 -1
- package/web-dist/assets/{chunk-VR4S4FIN-B-_noBva.js → chunk-VR4S4FIN-BBnj_eoh.js} +1 -1
- package/web-dist/assets/{chunk-WYO6CB5R-DL1c5T7_.js → chunk-WYO6CB5R-BqxQ9C2U.js} +1 -1
- package/web-dist/assets/{chunk-XXDRQBXY-BtfDQurg.js → chunk-XXDRQBXY-i1ZlWoHe.js} +1 -1
- package/web-dist/assets/{chunk-ZGVPDNZ5-Du753kJ-.js → chunk-ZGVPDNZ5-CsZazrsC.js} +1 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-B-BSDhSx.js +1 -0
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-B-BSDhSx.js +1 -0
- package/web-dist/assets/{cose-bilkent-JH36ORCC-B92GSF8h.js → cose-bilkent-JH36ORCC-BmEzvocR.js} +1 -1
- package/web-dist/assets/{cynefin-VYW2F7L2-K0fFe9n6.js → cynefin-VYW2F7L2-DZw7i9wG.js} +1 -1
- package/web-dist/assets/{cynefinDiagram-TSTJHNR4-DPizF8tw.js → cynefinDiagram-TSTJHNR4-vngpN5Da.js} +1 -1
- package/web-dist/assets/{dagre-VKFMJZFB-CUyDVdiG.js → dagre-VKFMJZFB-DkIdWq34.js} +1 -1
- package/web-dist/assets/{diagram-FQU43EPY-B6T_JCaJ.js → diagram-FQU43EPY-BB6mK3b6.js} +1 -1
- package/web-dist/assets/{diagram-G47NLZAW-CoDvaB51.js → diagram-G47NLZAW-DNs0n26T.js} +1 -1
- package/web-dist/assets/{diagram-NH7WQ7WH-o5OSs1Ze.js → diagram-NH7WQ7WH-DOg3HmEF.js} +1 -1
- package/web-dist/assets/{diagram-OA4YK3LP-CZLlB0ak.js → diagram-OA4YK3LP-BVKuqpaY.js} +1 -1
- package/web-dist/assets/{diagram-WEI45ONY-2VF6u7pk.js → diagram-WEI45ONY-BVnqmqct.js} +1 -1
- package/web-dist/assets/{dist-C2Rccr_W.js → dist-DuviCA4e.js} +1 -1
- package/web-dist/assets/{ebnfDiagram-CCIWWBDH-D9GSCcoU.js → ebnfDiagram-CCIWWBDH-BrqvjM1n.js} +1 -1
- package/web-dist/assets/{erDiagram-Q63AITRT-BLwWP_S8.js → erDiagram-Q63AITRT-Du1wclw9.js} +1 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-BPCrKw7m.js +1 -0
- package/web-dist/assets/flowDiagram-23GEKE2U-AA0QqWn2.js +1 -0
- package/web-dist/assets/{ganttDiagram-NO4QXBWP-BDe4wbPG.js → ganttDiagram-NO4QXBWP-CSiHPJrQ.js} +1 -1
- package/web-dist/assets/{gitGraph-TEB2WS4Q-DtGgkTGs.js → gitGraph-TEB2WS4Q-7-fFcwxj.js} +1 -1
- package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-Cz2LctIP.js → gitGraphDiagram-IHSO6WYX-CElQAu0f.js} +1 -1
- package/web-dist/assets/index-BWqGEWRg.js +330 -0
- package/web-dist/assets/index-W88ZnKOr.css +2 -0
- package/web-dist/assets/{info-DKCQHKI2-Crwk5sJH.js → info-DKCQHKI2-CXWr1yIe.js} +1 -1
- package/web-dist/assets/{infoDiagram-FWYZ7A6U-DkHHoxhz.js → infoDiagram-FWYZ7A6U-gN_v0GbL.js} +1 -1
- package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-CXMQf65c.js → ishikawaDiagram-FXEZZL3T-je5d5eKU.js} +1 -1
- package/web-dist/assets/{journeyDiagram-5HDEW3XC-BMdDsuH7.js → journeyDiagram-5HDEW3XC-CybyC_OE.js} +1 -1
- package/web-dist/assets/{kanban-definition-HUTT4EX6-BLp4VuCG.js → kanban-definition-HUTT4EX6-DthSkUsp.js} +1 -1
- package/web-dist/assets/{line-DOl515UP.js → line-C6dQfGfT.js} +1 -1
- package/web-dist/assets/{linear-DXvWeSoC.js → linear-zrRCwmn0.js} +1 -1
- package/web-dist/assets/{mermaid-parser.core-CqGam-b6.js → mermaid-parser.core-DGsIiQR5.js} +3 -3
- package/web-dist/assets/{mermaid.core-DawC2x_0.js → mermaid.core-Dk0TeuEE.js} +3 -3
- package/web-dist/assets/{mindmap-definition-LN4V7U3C-DlsS0Y-V.js → mindmap-definition-LN4V7U3C-Wo-S6GUr.js} +1 -1
- package/web-dist/assets/{packet-7NZHBO7P-CmlVYVem.js → packet-7NZHBO7P-BjJcy3q1.js} +1 -1
- package/web-dist/assets/{pegDiagram-2B236MQR-Cx0g5nsx.js → pegDiagram-2B236MQR-BBD9ezK8.js} +1 -1
- package/web-dist/assets/{pie-RZYD4A2V-C0PsjAlT.js → pie-RZYD4A2V-B9UJsCc6.js} +1 -1
- package/web-dist/assets/{pieDiagram-ENE6RG2P-CSuFf6VT.js → pieDiagram-ENE6RG2P-DvLH3PU6.js} +1 -1
- package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-CuQtvBia.js → quadrantDiagram-ABIIQ3AL-BwC1BKho.js} +1 -1
- package/web-dist/assets/{radar-I7S5WNFK-gk0WSCd9.js → radar-I7S5WNFK-BziFtIY8.js} +1 -1
- package/web-dist/assets/{railroad-3IZDKUUU-CovQLWaQ.js → railroad-3IZDKUUU-DPD8gyCw.js} +1 -1
- package/web-dist/assets/railroad-abnf-AHOZXSZD-C6wFrQk2.js +1 -0
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-yv1OIO4r.js +1 -0
- package/web-dist/assets/railroad-peg-LSFZ7HO6-BNocqLQb.js +1 -0
- package/web-dist/assets/{railroadDiagram-RFXS5EU6-RU1nu6mz.js → railroadDiagram-RFXS5EU6-DkS_57bW.js} +1 -1
- package/web-dist/assets/{requirementDiagram-TGXJPOKE-VoYjpOAN.js → requirementDiagram-TGXJPOKE-DT4YnV0v.js} +1 -1
- package/web-dist/assets/{sankeyDiagram-HTMAVEWB-DOVqKs1B.js → sankeyDiagram-HTMAVEWB-DMhUiLXu.js} +1 -1
- package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-DWMCuKM1.js → sequenceDiagram-DBY2YBRQ-BzyFG5mU.js} +1 -1
- package/web-dist/assets/{src-T1UUKJjT.js → src-Bpw4w4N9.js} +1 -1
- package/web-dist/assets/{stateDiagram-2N3HPSRC-mvFnj-9X.js → stateDiagram-2N3HPSRC-DI7bXIvV.js} +1 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BnPQRodM.js +1 -0
- package/web-dist/assets/{swimlanes-5IMT3BWC-kRCjOJ75.js → swimlanes-5IMT3BWC-ocEwjrkf.js} +1 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-Cmi6u5Ox.js +8 -0
- package/web-dist/assets/{timeline-definition-FHXFAJF6-Z8oX_ic6.js → timeline-definition-FHXFAJF6-Cx606xig.js} +1 -1
- package/web-dist/assets/{treeView-QDETBFTQ-BNFv3tbG.js → treeView-QDETBFTQ-BBjaqvh7.js} +1 -1
- package/web-dist/assets/{treemap-6X3UGDF4-DFxnrL9n.js → treemap-6X3UGDF4-Bn8fQ4vt.js} +1 -1
- package/web-dist/assets/{vennDiagram-L72KCM5P-rBeP_pn_.js → vennDiagram-L72KCM5P-DaKXvRdc.js} +1 -1
- package/web-dist/assets/{wardley-OPB4EBWU-BovK6GPr.js → wardley-OPB4EBWU-CK6Oy13G.js} +1 -1
- package/web-dist/assets/{wardleyDiagram-EHGQE667-B1yRRFsz.js → wardleyDiagram-EHGQE667-esMnbbXF.js} +1 -1
- package/web-dist/assets/{xychartDiagram-FW5EYKEG-IShFUxtM.js → xychartDiagram-FW5EYKEG-D3DoqcNr.js} +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/assets/architecture-TIHT7OUA-BxHw4J0J.js +0 -1
- package/web-dist/assets/channel-CgUG0O7S.js +0 -1
- package/web-dist/assets/classDiagram-OUVF2IWQ-CpxiJqgs.js +0 -1
- package/web-dist/assets/classDiagram-v2-EOCWNBFH-CpxiJqgs.js +0 -1
- package/web-dist/assets/eventmodeling-45OFAUF4-C-XvAPEG.js +0 -1
- package/web-dist/assets/flowDiagram-23GEKE2U-DYBwekLd.js +0 -1
- package/web-dist/assets/index-B9jaLEKn.js +0 -330
- package/web-dist/assets/index-Dubos_kO.css +0 -2
- package/web-dist/assets/railroad-abnf-AHOZXSZD-DSq7zK8A.js +0 -1
- package/web-dist/assets/railroad-ebnf-EBAXGLYW-icFIsnio.js +0 -1
- package/web-dist/assets/railroad-peg-LSFZ7HO6-Csep5xwu.js +0 -1
- package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BkzW7JgJ.js +0 -1
- package/web-dist/assets/swimlanesDiagram-G3AALYLV-mE9The3K.js +0 -8
|
@@ -2,12 +2,13 @@ import { createRequire as __frizzCreateRequire } from "node:module"; const requi
|
|
|
2
2
|
|
|
3
3
|
// packages/server/src/backend/claude-agent-broker.ts
|
|
4
4
|
import net from "node:net";
|
|
5
|
-
import { readFileSync as
|
|
5
|
+
import { readFileSync as readFileSync6, realpathSync as realpathSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
6
|
+
import { join as join8 } from "node:path";
|
|
6
7
|
import { randomUUID } from "node:crypto";
|
|
7
8
|
import { fileURLToPath } from "node:url";
|
|
8
9
|
|
|
9
10
|
// packages/server/src/backend/claude-agent-sdk.ts
|
|
10
|
-
import { accessSync, constants as fsConstants } from "node:fs";
|
|
11
|
+
import { accessSync, constants as fsConstants, rmSync as rmSync3 } from "node:fs";
|
|
11
12
|
import { delimiter, isAbsolute as isAbsolute3 } from "node:path";
|
|
12
13
|
|
|
13
14
|
// node_modules/.pnpm/@anthropic-ai+claude-agent-sdk@0.3.289_@anthropic-ai+sdk@0.111.0_zod@4.4.3__@modelconte_76f969b8ddf7678546f3feaaa7329174/node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs
|
|
@@ -26533,6 +26534,7 @@ function startClaudeQuery(executablePath, options) {
|
|
|
26533
26534
|
} : void 0;
|
|
26534
26535
|
const claudeEffort = resolveClaudeEffort(options.effort);
|
|
26535
26536
|
const launchModel = resolveClaudeLaunchModel(options.model);
|
|
26537
|
+
const mcpConfigFile = options.mcpServers && Object.keys(options.mcpServers).length > 0 ? writeMcpConfigFile(sessionId, options.mcpServers) : void 0;
|
|
26536
26538
|
const raw = nKe({
|
|
26537
26539
|
prompt: input,
|
|
26538
26540
|
options: {
|
|
@@ -26567,7 +26569,7 @@ function startClaudeQuery(executablePath, options) {
|
|
|
26567
26569
|
// SDK renders that one as inline `--mcp-config <json>` on the CLI's argv, and the config carries the
|
|
26568
26570
|
// operator's credentials — so every worker published its bearer tokens to `ps` (writeMcpConfigFile).
|
|
26569
26571
|
...options.pluginDir ? { plugins: [{ type: "local", path: options.pluginDir }] } : {},
|
|
26570
|
-
...
|
|
26572
|
+
...mcpConfigFile ? { extraArgs: { "mcp-config": mcpConfigFile } } : {},
|
|
26571
26573
|
...options.strictMcpConfig ? { strictMcpConfig: true } : {},
|
|
26572
26574
|
...options.allowedTools?.length ? { allowedTools: options.allowedTools } : {},
|
|
26573
26575
|
...options.disallowedTools?.length ? { disallowedTools: [...options.disallowedTools] } : {},
|
|
@@ -26603,7 +26605,17 @@ function startClaudeQuery(executablePath, options) {
|
|
|
26603
26605
|
}
|
|
26604
26606
|
}
|
|
26605
26607
|
});
|
|
26606
|
-
|
|
26608
|
+
const handle = new RealClaudeQueryHandle(raw, input, sessionId, lifecycleAbort, redact, diagnostic);
|
|
26609
|
+
if (mcpConfigFile) {
|
|
26610
|
+
const drop = () => {
|
|
26611
|
+
try {
|
|
26612
|
+
rmSync3(mcpConfigFile, { force: true });
|
|
26613
|
+
} catch {
|
|
26614
|
+
}
|
|
26615
|
+
};
|
|
26616
|
+
void handle.ready().then(drop, drop);
|
|
26617
|
+
}
|
|
26618
|
+
return handle;
|
|
26607
26619
|
}
|
|
26608
26620
|
function mapPermissionRequest(toolName, rawInput, context) {
|
|
26609
26621
|
return {
|
|
@@ -27521,6 +27533,391 @@ function workerMcpServers(project, frizz) {
|
|
|
27521
27533
|
return { ...project ?? {}, ...frizz ?? {} };
|
|
27522
27534
|
}
|
|
27523
27535
|
|
|
27536
|
+
// packages/server/src/backend/lazy-mcp-host.ts
|
|
27537
|
+
import { spawn } from "node:child_process";
|
|
27538
|
+
import { createHash as createHash2, randomBytes } from "node:crypto";
|
|
27539
|
+
import { mkdirSync as mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
27540
|
+
import http from "node:http";
|
|
27541
|
+
import { join as join6 } from "node:path";
|
|
27542
|
+
var CACHED_METHODS = /* @__PURE__ */ new Set(["tools/list", "prompts/list", "resources/list", "resources/templates/list", "server/discover"]);
|
|
27543
|
+
var METHOD_NOT_FOUND = -32601;
|
|
27544
|
+
var CACHE_VERSION = 2;
|
|
27545
|
+
var LIST_CHANGED = {
|
|
27546
|
+
"tools/list": "notifications/tools/list_changed",
|
|
27547
|
+
"prompts/list": "notifications/prompts/list_changed",
|
|
27548
|
+
"resources/list": "notifications/resources/list_changed",
|
|
27549
|
+
"resources/templates/list": "notifications/resources/list_changed"
|
|
27550
|
+
};
|
|
27551
|
+
var FALLBACK_PROTOCOL_VERSION = "2025-06-18";
|
|
27552
|
+
var STDERR_TAIL_BYTES = 2048;
|
|
27553
|
+
var MAX_BODY_BYTES = 64 * 1024 * 1024;
|
|
27554
|
+
function cacheable(response) {
|
|
27555
|
+
if (response.result !== void 0) return { result: response.result };
|
|
27556
|
+
if (response.error?.code === METHOD_NOT_FOUND) return { error: response.error };
|
|
27557
|
+
return void 0;
|
|
27558
|
+
}
|
|
27559
|
+
function lazyMcpEnabled(env = process.env) {
|
|
27560
|
+
return env.FRIZZ_LAZY_MCP_OFF !== "1";
|
|
27561
|
+
}
|
|
27562
|
+
function lazyMcpCacheKey(server, cwd) {
|
|
27563
|
+
const identity = JSON.stringify({ command: server.command, args: server.args ?? [], env: server.env ?? {}, cwd });
|
|
27564
|
+
return createHash2("sha256").update(identity).digest("hex").slice(0, 32);
|
|
27565
|
+
}
|
|
27566
|
+
function listKey(method, params) {
|
|
27567
|
+
if (params === null || typeof params !== "object" || Array.isArray(params)) return method;
|
|
27568
|
+
const { _meta: _ignored, ...rest } = params;
|
|
27569
|
+
return Object.keys(rest).length === 0 ? method : `${method} ${JSON.stringify(rest)}`;
|
|
27570
|
+
}
|
|
27571
|
+
function isRequest(message) {
|
|
27572
|
+
return typeof message.method === "string" && message.id !== void 0 && message.id !== null;
|
|
27573
|
+
}
|
|
27574
|
+
function isResponse(message) {
|
|
27575
|
+
return message.method === void 0 && message.id !== void 0 && ("result" in message || "error" in message);
|
|
27576
|
+
}
|
|
27577
|
+
var LazyServer = class {
|
|
27578
|
+
constructor(name, server, context, cachePath, log) {
|
|
27579
|
+
this.name = name;
|
|
27580
|
+
this.server = server;
|
|
27581
|
+
this.context = context;
|
|
27582
|
+
this.cachePath = cachePath;
|
|
27583
|
+
this.log = log;
|
|
27584
|
+
this.cache = this.readCache();
|
|
27585
|
+
}
|
|
27586
|
+
child;
|
|
27587
|
+
ready;
|
|
27588
|
+
initResult;
|
|
27589
|
+
clientInitParams;
|
|
27590
|
+
nextInternalId = 0;
|
|
27591
|
+
/** Requests the HOST sent the child (its own handshake and refreshes), by id. */
|
|
27592
|
+
internal = /* @__PURE__ */ new Map();
|
|
27593
|
+
/** Client requests forwarded to the child, by id, waiting for the child's answer. */
|
|
27594
|
+
forwarded = /* @__PURE__ */ new Map();
|
|
27595
|
+
stream;
|
|
27596
|
+
outbox = [];
|
|
27597
|
+
stderrTail = "";
|
|
27598
|
+
cache;
|
|
27599
|
+
get isRunning() {
|
|
27600
|
+
return this.child !== void 0;
|
|
27601
|
+
}
|
|
27602
|
+
readCache() {
|
|
27603
|
+
try {
|
|
27604
|
+
const parsed = JSON.parse(readFileSync4(this.cachePath, "utf8"));
|
|
27605
|
+
if (parsed?.version === CACHE_VERSION && parsed.answers && typeof parsed.answers === "object") return parsed;
|
|
27606
|
+
} catch {
|
|
27607
|
+
}
|
|
27608
|
+
return { version: CACHE_VERSION, answers: {} };
|
|
27609
|
+
}
|
|
27610
|
+
writeCache() {
|
|
27611
|
+
try {
|
|
27612
|
+
const temp = `${this.cachePath}.${process.pid}.tmp`;
|
|
27613
|
+
writeFileSync3(temp, JSON.stringify(this.cache), { mode: 384 });
|
|
27614
|
+
renameSync2(temp, this.cachePath);
|
|
27615
|
+
} catch (error) {
|
|
27616
|
+
this.log(`${this.name}: cache write failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
27617
|
+
}
|
|
27618
|
+
}
|
|
27619
|
+
// ---- the client side -------------------------------------------------------------------------
|
|
27620
|
+
async handleRequest(message) {
|
|
27621
|
+
const id2 = message.id;
|
|
27622
|
+
try {
|
|
27623
|
+
const method = message.method;
|
|
27624
|
+
if (method === "initialize") {
|
|
27625
|
+
this.clientInitParams = message.params ?? {};
|
|
27626
|
+
if (this.initResult !== void 0) return { jsonrpc: "2.0", id: id2, result: this.initResult };
|
|
27627
|
+
if (this.cache.initialize !== void 0) return { jsonrpc: "2.0", id: id2, result: this.cache.initialize };
|
|
27628
|
+
await this.start(method);
|
|
27629
|
+
return { jsonrpc: "2.0", id: id2, result: this.initResult };
|
|
27630
|
+
}
|
|
27631
|
+
if (method === "ping" && !this.child) return { jsonrpc: "2.0", id: id2, result: {} };
|
|
27632
|
+
if (CACHED_METHODS.has(method) && !this.child) {
|
|
27633
|
+
const cached2 = this.cache.answers[listKey(method, message.params)];
|
|
27634
|
+
if (cached2 !== void 0) return { jsonrpc: "2.0", id: id2, ...cached2 };
|
|
27635
|
+
}
|
|
27636
|
+
await this.start(method);
|
|
27637
|
+
const response = await this.forward(message);
|
|
27638
|
+
const answer = CACHED_METHODS.has(method) ? cacheable(response) : void 0;
|
|
27639
|
+
if (answer) {
|
|
27640
|
+
this.cache.answers[listKey(method, message.params)] = answer;
|
|
27641
|
+
this.writeCache();
|
|
27642
|
+
}
|
|
27643
|
+
return response;
|
|
27644
|
+
} catch (error) {
|
|
27645
|
+
return { jsonrpc: "2.0", id: id2, error: { code: -32603, message: error instanceof Error ? error.message : String(error) } };
|
|
27646
|
+
}
|
|
27647
|
+
}
|
|
27648
|
+
/** A client notification or a client's answer to a server request. Never starts the server. */
|
|
27649
|
+
handleOther(message) {
|
|
27650
|
+
if (message.method === "notifications/initialized") return;
|
|
27651
|
+
if (this.child && this.initResult !== void 0) this.send(message);
|
|
27652
|
+
}
|
|
27653
|
+
openStream(response) {
|
|
27654
|
+
this.stream?.end();
|
|
27655
|
+
this.stream = response;
|
|
27656
|
+
response.writeHead(200, { "content-type": "text/event-stream", "cache-control": "no-cache", connection: "keep-alive" });
|
|
27657
|
+
response.flushHeaders();
|
|
27658
|
+
response.on("close", () => {
|
|
27659
|
+
if (this.stream === response) this.stream = void 0;
|
|
27660
|
+
});
|
|
27661
|
+
for (const message of this.outbox.splice(0)) this.toClient(message);
|
|
27662
|
+
}
|
|
27663
|
+
toClient(message) {
|
|
27664
|
+
if (!this.stream) {
|
|
27665
|
+
this.outbox.push(message);
|
|
27666
|
+
return;
|
|
27667
|
+
}
|
|
27668
|
+
this.stream.write(`event: message
|
|
27669
|
+
data: ${JSON.stringify(message)}
|
|
27670
|
+
|
|
27671
|
+
`);
|
|
27672
|
+
}
|
|
27673
|
+
// ---- the server side -------------------------------------------------------------------------
|
|
27674
|
+
/** `reason` is the client method that needed the real server — the line that says why it ran. */
|
|
27675
|
+
start(reason) {
|
|
27676
|
+
this.ready ??= this.spawnAndInitialize(reason).catch((error) => {
|
|
27677
|
+
this.ready = void 0;
|
|
27678
|
+
throw error;
|
|
27679
|
+
});
|
|
27680
|
+
return this.ready;
|
|
27681
|
+
}
|
|
27682
|
+
async spawnAndInitialize(reason) {
|
|
27683
|
+
this.log(`${this.name}: starting ${this.server.command} for ${reason}`);
|
|
27684
|
+
const child = spawn(this.server.command, this.server.args ?? [], {
|
|
27685
|
+
cwd: this.context.cwd,
|
|
27686
|
+
env: { ...this.context.env, ...this.server.env ?? {} },
|
|
27687
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
27688
|
+
});
|
|
27689
|
+
this.child = child;
|
|
27690
|
+
this.stderrTail = "";
|
|
27691
|
+
let buffered = "";
|
|
27692
|
+
child.stdout.setEncoding("utf8");
|
|
27693
|
+
child.stdout.on("data", (chunk) => {
|
|
27694
|
+
buffered += chunk;
|
|
27695
|
+
let newline;
|
|
27696
|
+
while ((newline = buffered.indexOf("\n")) >= 0) {
|
|
27697
|
+
const line = buffered.slice(0, newline).trim();
|
|
27698
|
+
buffered = buffered.slice(newline + 1);
|
|
27699
|
+
if (!line) continue;
|
|
27700
|
+
let parsed;
|
|
27701
|
+
try {
|
|
27702
|
+
parsed = JSON.parse(line);
|
|
27703
|
+
} catch {
|
|
27704
|
+
continue;
|
|
27705
|
+
}
|
|
27706
|
+
this.fromChild(parsed);
|
|
27707
|
+
}
|
|
27708
|
+
});
|
|
27709
|
+
child.stderr.setEncoding("utf8");
|
|
27710
|
+
child.stderr.on("data", (chunk) => {
|
|
27711
|
+
this.stderrTail = (this.stderrTail + chunk).slice(-STDERR_TAIL_BYTES);
|
|
27712
|
+
});
|
|
27713
|
+
child.stdin.on("error", () => {
|
|
27714
|
+
});
|
|
27715
|
+
const exited = new Promise((_resolve, reject) => {
|
|
27716
|
+
const fail = (detail) => {
|
|
27717
|
+
if (this.child === child) this.reset(detail);
|
|
27718
|
+
reject(new Error(detail));
|
|
27719
|
+
};
|
|
27720
|
+
child.once("error", (error) => fail(`MCP server "${this.name}" could not start: ${error.message}`));
|
|
27721
|
+
child.once("exit", (code, signal) => fail(`MCP server "${this.name}" exited (${signal ?? `code ${code}`})${this.stderrTail ? `: ${this.stderrTail.trim()}` : ""}`));
|
|
27722
|
+
});
|
|
27723
|
+
exited.catch(() => {
|
|
27724
|
+
});
|
|
27725
|
+
const params = {
|
|
27726
|
+
protocolVersion: this.clientInitParams?.protocolVersion ?? FALLBACK_PROTOCOL_VERSION,
|
|
27727
|
+
capabilities: this.clientInitParams?.capabilities ?? {},
|
|
27728
|
+
clientInfo: this.clientInitParams?.clientInfo ?? { name: "frizz-lazy-mcp", version: "1" }
|
|
27729
|
+
};
|
|
27730
|
+
const response = await Promise.race([this.internalRequest("initialize", params), exited]);
|
|
27731
|
+
if (response.error) throw new Error(`MCP server "${this.name}" refused initialize: ${response.error.message}`);
|
|
27732
|
+
this.initResult = response.result;
|
|
27733
|
+
this.send({ jsonrpc: "2.0", method: "notifications/initialized" });
|
|
27734
|
+
this.cache.initialize = response.result;
|
|
27735
|
+
this.writeCache();
|
|
27736
|
+
void this.refreshLists();
|
|
27737
|
+
}
|
|
27738
|
+
/** Re-read every cached list from the live server; a difference updates the cache and tells the client. */
|
|
27739
|
+
async refreshLists() {
|
|
27740
|
+
const changed = /* @__PURE__ */ new Set();
|
|
27741
|
+
for (const key of Object.keys(this.cache.answers)) {
|
|
27742
|
+
const space = key.indexOf(" ");
|
|
27743
|
+
const method = space < 0 ? key : key.slice(0, space);
|
|
27744
|
+
const params = space < 0 ? void 0 : JSON.parse(key.slice(space + 1));
|
|
27745
|
+
const response = await this.internalRequest(method, params).catch(() => void 0);
|
|
27746
|
+
const answer = response && cacheable(response);
|
|
27747
|
+
if (!answer) continue;
|
|
27748
|
+
if (JSON.stringify(answer) !== JSON.stringify(this.cache.answers[key])) {
|
|
27749
|
+
this.cache.answers[key] = answer;
|
|
27750
|
+
changed.add(LIST_CHANGED[method] ?? "");
|
|
27751
|
+
}
|
|
27752
|
+
}
|
|
27753
|
+
if (changed.size === 0) return;
|
|
27754
|
+
this.writeCache();
|
|
27755
|
+
for (const notification of changed) if (notification) this.toClient({ jsonrpc: "2.0", method: notification });
|
|
27756
|
+
this.log(`${this.name}: cached lists were stale; sent ${[...changed].join(", ")}`);
|
|
27757
|
+
}
|
|
27758
|
+
internalRequest(method, params) {
|
|
27759
|
+
const id2 = `frizz-lazy-${++this.nextInternalId}`;
|
|
27760
|
+
return new Promise((resolve3) => {
|
|
27761
|
+
this.internal.set(id2, resolve3);
|
|
27762
|
+
this.send({ jsonrpc: "2.0", id: id2, method, ...params === void 0 ? {} : { params } });
|
|
27763
|
+
});
|
|
27764
|
+
}
|
|
27765
|
+
forward(message) {
|
|
27766
|
+
return new Promise((resolve3) => {
|
|
27767
|
+
this.forwarded.set(JSON.stringify(message.id), resolve3);
|
|
27768
|
+
this.send(message);
|
|
27769
|
+
});
|
|
27770
|
+
}
|
|
27771
|
+
send(message) {
|
|
27772
|
+
this.child?.stdin?.write(`${JSON.stringify(message)}
|
|
27773
|
+
`);
|
|
27774
|
+
}
|
|
27775
|
+
fromChild(message) {
|
|
27776
|
+
if (isResponse(message)) {
|
|
27777
|
+
const key = typeof message.id === "string" && message.id.startsWith("frizz-lazy-") ? message.id : void 0;
|
|
27778
|
+
const waiter = key !== void 0 ? this.internal.get(key) : this.forwarded.get(JSON.stringify(message.id));
|
|
27779
|
+
if (key !== void 0) this.internal.delete(key);
|
|
27780
|
+
else this.forwarded.delete(JSON.stringify(message.id));
|
|
27781
|
+
waiter?.(message);
|
|
27782
|
+
return;
|
|
27783
|
+
}
|
|
27784
|
+
this.toClient(message);
|
|
27785
|
+
}
|
|
27786
|
+
/** The child is gone: fail everything waiting on it, and let the next request start a fresh one. */
|
|
27787
|
+
reset(detail) {
|
|
27788
|
+
this.log(detail);
|
|
27789
|
+
this.child = void 0;
|
|
27790
|
+
this.ready = void 0;
|
|
27791
|
+
this.initResult = void 0;
|
|
27792
|
+
const error = { code: -32e3, message: detail };
|
|
27793
|
+
for (const [id2, waiter] of this.forwarded) waiter({ jsonrpc: "2.0", id: JSON.parse(id2), error });
|
|
27794
|
+
for (const [id2, waiter] of this.internal) waiter({ jsonrpc: "2.0", id: id2, error });
|
|
27795
|
+
this.forwarded.clear();
|
|
27796
|
+
this.internal.clear();
|
|
27797
|
+
}
|
|
27798
|
+
async close() {
|
|
27799
|
+
this.stream?.end();
|
|
27800
|
+
const child = this.child;
|
|
27801
|
+
if (!child) return;
|
|
27802
|
+
this.child = void 0;
|
|
27803
|
+
await new Promise((resolve3) => {
|
|
27804
|
+
const timer = setTimeout(() => {
|
|
27805
|
+
try {
|
|
27806
|
+
child.kill("SIGKILL");
|
|
27807
|
+
} catch {
|
|
27808
|
+
}
|
|
27809
|
+
;
|
|
27810
|
+
resolve3();
|
|
27811
|
+
}, 2e3);
|
|
27812
|
+
child.once("exit", () => {
|
|
27813
|
+
clearTimeout(timer);
|
|
27814
|
+
resolve3();
|
|
27815
|
+
});
|
|
27816
|
+
try {
|
|
27817
|
+
child.stdin?.end();
|
|
27818
|
+
} catch {
|
|
27819
|
+
}
|
|
27820
|
+
try {
|
|
27821
|
+
child.kill("SIGTERM");
|
|
27822
|
+
} catch {
|
|
27823
|
+
}
|
|
27824
|
+
});
|
|
27825
|
+
}
|
|
27826
|
+
};
|
|
27827
|
+
function readBody(request) {
|
|
27828
|
+
return new Promise((resolve3, reject) => {
|
|
27829
|
+
let size = 0;
|
|
27830
|
+
const chunks = [];
|
|
27831
|
+
request.on("data", (chunk) => {
|
|
27832
|
+
size += chunk.length;
|
|
27833
|
+
if (size > MAX_BODY_BYTES) {
|
|
27834
|
+
reject(new Error("body too large"));
|
|
27835
|
+
request.destroy();
|
|
27836
|
+
return;
|
|
27837
|
+
}
|
|
27838
|
+
chunks.push(chunk);
|
|
27839
|
+
});
|
|
27840
|
+
request.on("end", () => resolve3(Buffer.concat(chunks).toString("utf8")));
|
|
27841
|
+
request.on("error", reject);
|
|
27842
|
+
});
|
|
27843
|
+
}
|
|
27844
|
+
function startLazyMcpHost(options) {
|
|
27845
|
+
const log = options.log ?? (() => {
|
|
27846
|
+
});
|
|
27847
|
+
const token = randomBytes(32).toString("hex");
|
|
27848
|
+
const servers = /* @__PURE__ */ new Map();
|
|
27849
|
+
let mounted = 0;
|
|
27850
|
+
const httpServer = http.createServer((request, response) => {
|
|
27851
|
+
const route = /^\/mcp\/([A-Za-z0-9]+)$/.exec((request.url ?? "").split("?")[0] ?? "");
|
|
27852
|
+
const server = route ? servers.get(route[1]) : void 0;
|
|
27853
|
+
if (!server) {
|
|
27854
|
+
response.writeHead(404).end();
|
|
27855
|
+
return;
|
|
27856
|
+
}
|
|
27857
|
+
if (request.headers.authorization !== `Bearer ${token}`) {
|
|
27858
|
+
response.writeHead(403).end();
|
|
27859
|
+
return;
|
|
27860
|
+
}
|
|
27861
|
+
if (request.method === "GET") {
|
|
27862
|
+
server.openStream(response);
|
|
27863
|
+
return;
|
|
27864
|
+
}
|
|
27865
|
+
if (request.method !== "POST") {
|
|
27866
|
+
response.writeHead(405, { allow: "GET, POST" }).end();
|
|
27867
|
+
return;
|
|
27868
|
+
}
|
|
27869
|
+
void (async () => {
|
|
27870
|
+
let parsed;
|
|
27871
|
+
try {
|
|
27872
|
+
parsed = JSON.parse(await readBody(request));
|
|
27873
|
+
} catch {
|
|
27874
|
+
response.writeHead(400, { "content-type": "application/json" });
|
|
27875
|
+
response.end(JSON.stringify({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } }));
|
|
27876
|
+
return;
|
|
27877
|
+
}
|
|
27878
|
+
const messages = Array.isArray(parsed) ? parsed : [parsed];
|
|
27879
|
+
const requests = messages.filter(isRequest);
|
|
27880
|
+
for (const message of messages) if (!isRequest(message)) server.handleOther(message);
|
|
27881
|
+
if (requests.length === 0) {
|
|
27882
|
+
response.writeHead(202).end();
|
|
27883
|
+
return;
|
|
27884
|
+
}
|
|
27885
|
+
const answers = await Promise.all(requests.map((message) => server.handleRequest(message)));
|
|
27886
|
+
response.writeHead(200, { "content-type": "application/json" });
|
|
27887
|
+
response.end(JSON.stringify(Array.isArray(parsed) ? answers : answers[0]));
|
|
27888
|
+
})();
|
|
27889
|
+
});
|
|
27890
|
+
httpServer.requestTimeout = 0;
|
|
27891
|
+
return new Promise((resolve3, reject) => {
|
|
27892
|
+
httpServer.once("error", reject);
|
|
27893
|
+
httpServer.listen(0, "127.0.0.1", () => {
|
|
27894
|
+
httpServer.off("error", reject);
|
|
27895
|
+
const address = httpServer.address();
|
|
27896
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
27897
|
+
resolve3({
|
|
27898
|
+
port,
|
|
27899
|
+
mount(stdio, context) {
|
|
27900
|
+
mkdirSync4(options.cacheDir, { recursive: true, mode: 448 });
|
|
27901
|
+
const out = {};
|
|
27902
|
+
for (const [name, definition] of Object.entries(stdio)) {
|
|
27903
|
+
const slot = String(++mounted);
|
|
27904
|
+
const cachePath = join6(options.cacheDir, `${lazyMcpCacheKey(definition, context.cwd)}.json`);
|
|
27905
|
+
servers.set(slot, new LazyServer(name, definition, context, cachePath, context.log ?? log));
|
|
27906
|
+
out[name] = { type: "http", url: `http://127.0.0.1:${port}/mcp/${slot}`, headers: { Authorization: `Bearer ${token}` } };
|
|
27907
|
+
}
|
|
27908
|
+
return out;
|
|
27909
|
+
},
|
|
27910
|
+
running: () => [...servers.values()].filter((server) => server.isRunning).map((server) => server.name),
|
|
27911
|
+
async close() {
|
|
27912
|
+
await Promise.all([...servers.values()].map((server) => server.close()));
|
|
27913
|
+
httpServer.closeAllConnections();
|
|
27914
|
+
await new Promise((done) => httpServer.close(() => done()));
|
|
27915
|
+
}
|
|
27916
|
+
});
|
|
27917
|
+
});
|
|
27918
|
+
});
|
|
27919
|
+
}
|
|
27920
|
+
|
|
27524
27921
|
// packages/server/src/backend/types.ts
|
|
27525
27922
|
var CLAUDE_WORKER_ENV = {
|
|
27526
27923
|
CLAUDE_CODE_TOTAL_TOKENS_REMINDER: "infinite",
|
|
@@ -27557,14 +27954,14 @@ function socketPathOwnership(socketPath) {
|
|
|
27557
27954
|
}
|
|
27558
27955
|
|
|
27559
27956
|
// packages/server/src/backend/claude-broker-diagnostics.ts
|
|
27560
|
-
import { appendFileSync as appendFileSync2, mkdirSync as
|
|
27561
|
-
import { join as
|
|
27957
|
+
import { appendFileSync as appendFileSync2, mkdirSync as mkdirSync5, readFileSync as readFileSync5, renameSync as renameSync3, statSync as statSync3 } from "node:fs";
|
|
27958
|
+
import { join as join7 } from "node:path";
|
|
27562
27959
|
var MAX_BYTES = 4 * 1024 * 1024;
|
|
27563
27960
|
function appendRecord(path, record) {
|
|
27564
27961
|
try {
|
|
27565
|
-
|
|
27962
|
+
mkdirSync5(join7(path, ".."), { recursive: true });
|
|
27566
27963
|
try {
|
|
27567
|
-
if (statSync3(path).size > MAX_BYTES)
|
|
27964
|
+
if (statSync3(path).size > MAX_BYTES) renameSync3(path, `${path}.1`);
|
|
27568
27965
|
} catch {
|
|
27569
27966
|
}
|
|
27570
27967
|
appendFileSync2(path, `${JSON.stringify(record)}
|
|
@@ -27595,7 +27992,15 @@ var IDLE_EXIT_MS = 6 * 60 * 60 * 1e3;
|
|
|
27595
27992
|
var REACHABILITY_CHECK_MS = 3e4;
|
|
27596
27993
|
var REACHABILITY_STRIKES = 2;
|
|
27597
27994
|
var EVENT_ERROR_TOLERANCE = 5;
|
|
27598
|
-
function
|
|
27995
|
+
function mountLazily(project, frizz, host, context) {
|
|
27996
|
+
if (!host) return project;
|
|
27997
|
+
const stdio = {};
|
|
27998
|
+
for (const [name, server] of Object.entries(project)) {
|
|
27999
|
+
if ((server.type === void 0 || server.type === "stdio") && !(frizz && name in frizz)) stdio[name] = server;
|
|
28000
|
+
}
|
|
28001
|
+
return { ...project, ...host.mount(stdio, context) };
|
|
28002
|
+
}
|
|
28003
|
+
function runClaudeBroker(config, options = {}) {
|
|
27599
28004
|
const generation = config.generation ?? randomUUID();
|
|
27600
28005
|
let client = null;
|
|
27601
28006
|
const MAX_BACKLOG_FRAMES = 2e4;
|
|
@@ -27641,7 +28046,18 @@ function runClaudeBroker(config) {
|
|
|
27641
28046
|
// approved `.mcp.json` (and the operator's remote user-scope servers) under the frizz mount, which
|
|
27642
28047
|
// wins a name collision. Read at every fork, not once per project, so a `.mcp.json` edit reaches the
|
|
27643
28048
|
// next dispatch the way it reaches the next plain `claude`. project-mcp-servers.ts has the reasons.
|
|
27644
|
-
|
|
28049
|
+
// Stdio servers are not started here: the lazy host stands in for each one and starts the real
|
|
28050
|
+
// command on the first tool call (lazy-mcp-host.ts has the measurement that motivated it).
|
|
28051
|
+
mcpServers: workerMcpServers(
|
|
28052
|
+
mountLazily(projectMcpServers(config.cwd, { env }), config.mcpServers, options.lazyMcpHost, {
|
|
28053
|
+
cwd: config.cwd,
|
|
28054
|
+
// What the CLI itself would have spawned the server with: the env the factory hands it, after
|
|
28055
|
+
// the same sanitizing (no inherited NODE_OPTIONS, no nub shim on PATH).
|
|
28056
|
+
env: sanitizeProviderChildEnvironment(env),
|
|
28057
|
+
log: (message) => writeDiagnostic?.({ kind: "stderr", message: `[lazy-mcp] ${message}`, truncated: false })
|
|
28058
|
+
}),
|
|
28059
|
+
config.mcpServers
|
|
28060
|
+
),
|
|
27645
28061
|
strictMcpConfig: true,
|
|
27646
28062
|
allowedTools: config.allowedTools,
|
|
27647
28063
|
// NO `disallowedTools` here, and the asymmetry with the argv path (WORKER_DISALLOWED_TOOLS →
|
|
@@ -27809,7 +28225,7 @@ function runClaudeBroker(config) {
|
|
|
27809
28225
|
const recordOwner = () => {
|
|
27810
28226
|
if (!config.recordPath) return null;
|
|
27811
28227
|
try {
|
|
27812
|
-
return JSON.parse(
|
|
28228
|
+
return JSON.parse(readFileSync6(config.recordPath, "utf8")).daemonPid;
|
|
27813
28229
|
} catch {
|
|
27814
28230
|
return null;
|
|
27815
28231
|
}
|
|
@@ -27857,6 +28273,8 @@ function runClaudeBroker(config) {
|
|
|
27857
28273
|
}
|
|
27858
28274
|
await handle.close().catch(() => {
|
|
27859
28275
|
});
|
|
28276
|
+
await options.lazyMcpHost?.close().catch(() => {
|
|
28277
|
+
});
|
|
27860
28278
|
if (config.recordPath) process.exit(code);
|
|
27861
28279
|
}
|
|
27862
28280
|
const reach = setInterval(() => {
|
|
@@ -27888,7 +28306,7 @@ function runClaudeBroker(config) {
|
|
|
27888
28306
|
if (config.recordPath) {
|
|
27889
28307
|
const record = { daemonPid: process.pid, socketPath: config.socketPath, sessionId: config.sessionId, generation, createdAt: (/* @__PURE__ */ new Date()).toISOString(), capabilities: BROKER_CAPABILITIES, compactionWindow: claudeCompactionWindowOf(config.workerEnv) };
|
|
27890
28308
|
try {
|
|
27891
|
-
|
|
28309
|
+
writeFileSync4(config.recordPath, JSON.stringify(record), { mode: 384 });
|
|
27892
28310
|
} catch {
|
|
27893
28311
|
}
|
|
27894
28312
|
}
|
|
@@ -27933,7 +28351,10 @@ ${error.stack ?? ""}`.slice(0, 2e3) : String(error));
|
|
|
27933
28351
|
${reason.stack ?? ""}`.slice(0, 2e3) : String(reason));
|
|
27934
28352
|
process.exit(1);
|
|
27935
28353
|
});
|
|
27936
|
-
|
|
28354
|
+
const lazyMcpHost = lazyMcpEnabled() ? startLazyMcpHost({ cacheDir: join8(frizzRoots().data, "mcp-cache") }) : Promise.resolve(void 0);
|
|
28355
|
+
void lazyMcpHost.catch(() => void 0).then((host) => {
|
|
28356
|
+
running = runClaudeBroker(config, { lazyMcpHost: host });
|
|
28357
|
+
});
|
|
27937
28358
|
} else if (startedAsProcessEntry()) {
|
|
27938
28359
|
throw new Error("claude session broker started without FRIZZ_CLAUDE_BROKER");
|
|
27939
28360
|
}
|