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.
Files changed (100) hide show
  1. package/dist/claude-agent-broker.js +434 -13
  2. package/dist/dev-child.js +218 -72
  3. package/package.json +1 -1
  4. package/runtime/cc-worker/bin/frizz-mcp.mjs +22 -11
  5. package/web-dist/assets/{abnfDiagram-VRR7QNED-CPgDRCPL.js → abnfDiagram-VRR7QNED-NqOfYLl_.js} +1 -1
  6. package/web-dist/assets/{arc-BHLpLJBo.js → arc-D9UsqPOW.js} +1 -1
  7. package/web-dist/assets/architecture-TIHT7OUA-CYx5mu8e.js +1 -0
  8. package/web-dist/assets/{architectureDiagram-ZJ3FMSHR-D0qwvPWx.js → architectureDiagram-ZJ3FMSHR-CRU2_T_H.js} +1 -1
  9. package/web-dist/assets/{blockDiagram-677ZJIJ3-C3NPa6bJ.js → blockDiagram-677ZJIJ3-DZT8OjJK.js} +1 -1
  10. package/web-dist/assets/{c4Diagram-LMCZKHZV-BW9GHi19.js → c4Diagram-LMCZKHZV-k-Ft1ThH.js} +1 -1
  11. package/web-dist/assets/channel-CD-hjjIL.js +1 -0
  12. package/web-dist/assets/{chunk-32BRIVSS-DHkNUXJc.js → chunk-32BRIVSS-DDMMpuZb.js} +1 -1
  13. package/web-dist/assets/{chunk-52WLFC77-CD7yIcMj.js → chunk-52WLFC77-BW9KF5RJ.js} +1 -1
  14. package/web-dist/assets/{chunk-C7G6YPKG-DCNeb-B6.js → chunk-C7G6YPKG-B4caDIAT.js} +1 -1
  15. package/web-dist/assets/{chunk-EX3LRPZG-YD5FxMO3.js → chunk-EX3LRPZG-Dyz0PnIY.js} +1 -1
  16. package/web-dist/assets/{chunk-FWX5IMBZ-XPo-YcnN.js → chunk-FWX5IMBZ-Dxhqduv9.js} +2 -2
  17. package/web-dist/assets/{chunk-HOUHSVGY-D6MFIjXX.js → chunk-HOUHSVGY-Du7Ln4sO.js} +1 -1
  18. package/web-dist/assets/{chunk-ICXQ74PX-B4YNEdT-.js → chunk-ICXQ74PX-sNRNHZp6.js} +1 -1
  19. package/web-dist/assets/{chunk-MOJQB5TN-DmeXNe3e.js → chunk-MOJQB5TN-BsgPysrE.js} +1 -1
  20. package/web-dist/assets/{chunk-OGEWGWER-DOiD4wAh.js → chunk-OGEWGWER-DZ288hvO.js} +1 -1
  21. package/web-dist/assets/{chunk-PUDLZKDR-1SEr54_e.js → chunk-PUDLZKDR-DuU7e-Sm.js} +1 -1
  22. package/web-dist/assets/{chunk-Q4XR5HBZ-CyWVwYh2.js → chunk-Q4XR5HBZ-BO2aVRAy.js} +1 -1
  23. package/web-dist/assets/{chunk-RYQCIY6F-ClBJ_iGr.js → chunk-RYQCIY6F-pQoqlm5b.js} +1 -1
  24. package/web-dist/assets/{chunk-V7JOEXUC-Bz8VXwYP.js → chunk-V7JOEXUC-DkkQI0OQ.js} +1 -1
  25. package/web-dist/assets/{chunk-VAUOI2AC-DawR4suA.js → chunk-VAUOI2AC-D3_hzRfs.js} +1 -1
  26. package/web-dist/assets/{chunk-VR4S4FIN-B-_noBva.js → chunk-VR4S4FIN-BBnj_eoh.js} +1 -1
  27. package/web-dist/assets/{chunk-WYO6CB5R-DL1c5T7_.js → chunk-WYO6CB5R-BqxQ9C2U.js} +1 -1
  28. package/web-dist/assets/{chunk-XXDRQBXY-BtfDQurg.js → chunk-XXDRQBXY-i1ZlWoHe.js} +1 -1
  29. package/web-dist/assets/{chunk-ZGVPDNZ5-Du753kJ-.js → chunk-ZGVPDNZ5-CsZazrsC.js} +1 -1
  30. package/web-dist/assets/classDiagram-OUVF2IWQ-B-BSDhSx.js +1 -0
  31. package/web-dist/assets/classDiagram-v2-EOCWNBFH-B-BSDhSx.js +1 -0
  32. package/web-dist/assets/{cose-bilkent-JH36ORCC-B92GSF8h.js → cose-bilkent-JH36ORCC-BmEzvocR.js} +1 -1
  33. package/web-dist/assets/{cynefin-VYW2F7L2-K0fFe9n6.js → cynefin-VYW2F7L2-DZw7i9wG.js} +1 -1
  34. package/web-dist/assets/{cynefinDiagram-TSTJHNR4-DPizF8tw.js → cynefinDiagram-TSTJHNR4-vngpN5Da.js} +1 -1
  35. package/web-dist/assets/{dagre-VKFMJZFB-CUyDVdiG.js → dagre-VKFMJZFB-DkIdWq34.js} +1 -1
  36. package/web-dist/assets/{diagram-FQU43EPY-B6T_JCaJ.js → diagram-FQU43EPY-BB6mK3b6.js} +1 -1
  37. package/web-dist/assets/{diagram-G47NLZAW-CoDvaB51.js → diagram-G47NLZAW-DNs0n26T.js} +1 -1
  38. package/web-dist/assets/{diagram-NH7WQ7WH-o5OSs1Ze.js → diagram-NH7WQ7WH-DOg3HmEF.js} +1 -1
  39. package/web-dist/assets/{diagram-OA4YK3LP-CZLlB0ak.js → diagram-OA4YK3LP-BVKuqpaY.js} +1 -1
  40. package/web-dist/assets/{diagram-WEI45ONY-2VF6u7pk.js → diagram-WEI45ONY-BVnqmqct.js} +1 -1
  41. package/web-dist/assets/{dist-C2Rccr_W.js → dist-DuviCA4e.js} +1 -1
  42. package/web-dist/assets/{ebnfDiagram-CCIWWBDH-D9GSCcoU.js → ebnfDiagram-CCIWWBDH-BrqvjM1n.js} +1 -1
  43. package/web-dist/assets/{erDiagram-Q63AITRT-BLwWP_S8.js → erDiagram-Q63AITRT-Du1wclw9.js} +1 -1
  44. package/web-dist/assets/eventmodeling-45OFAUF4-BPCrKw7m.js +1 -0
  45. package/web-dist/assets/flowDiagram-23GEKE2U-AA0QqWn2.js +1 -0
  46. package/web-dist/assets/{ganttDiagram-NO4QXBWP-BDe4wbPG.js → ganttDiagram-NO4QXBWP-CSiHPJrQ.js} +1 -1
  47. package/web-dist/assets/{gitGraph-TEB2WS4Q-DtGgkTGs.js → gitGraph-TEB2WS4Q-7-fFcwxj.js} +1 -1
  48. package/web-dist/assets/{gitGraphDiagram-IHSO6WYX-Cz2LctIP.js → gitGraphDiagram-IHSO6WYX-CElQAu0f.js} +1 -1
  49. package/web-dist/assets/index-BWqGEWRg.js +330 -0
  50. package/web-dist/assets/index-W88ZnKOr.css +2 -0
  51. package/web-dist/assets/{info-DKCQHKI2-Crwk5sJH.js → info-DKCQHKI2-CXWr1yIe.js} +1 -1
  52. package/web-dist/assets/{infoDiagram-FWYZ7A6U-DkHHoxhz.js → infoDiagram-FWYZ7A6U-gN_v0GbL.js} +1 -1
  53. package/web-dist/assets/{ishikawaDiagram-FXEZZL3T-CXMQf65c.js → ishikawaDiagram-FXEZZL3T-je5d5eKU.js} +1 -1
  54. package/web-dist/assets/{journeyDiagram-5HDEW3XC-BMdDsuH7.js → journeyDiagram-5HDEW3XC-CybyC_OE.js} +1 -1
  55. package/web-dist/assets/{kanban-definition-HUTT4EX6-BLp4VuCG.js → kanban-definition-HUTT4EX6-DthSkUsp.js} +1 -1
  56. package/web-dist/assets/{line-DOl515UP.js → line-C6dQfGfT.js} +1 -1
  57. package/web-dist/assets/{linear-DXvWeSoC.js → linear-zrRCwmn0.js} +1 -1
  58. package/web-dist/assets/{mermaid-parser.core-CqGam-b6.js → mermaid-parser.core-DGsIiQR5.js} +3 -3
  59. package/web-dist/assets/{mermaid.core-DawC2x_0.js → mermaid.core-Dk0TeuEE.js} +3 -3
  60. package/web-dist/assets/{mindmap-definition-LN4V7U3C-DlsS0Y-V.js → mindmap-definition-LN4V7U3C-Wo-S6GUr.js} +1 -1
  61. package/web-dist/assets/{packet-7NZHBO7P-CmlVYVem.js → packet-7NZHBO7P-BjJcy3q1.js} +1 -1
  62. package/web-dist/assets/{pegDiagram-2B236MQR-Cx0g5nsx.js → pegDiagram-2B236MQR-BBD9ezK8.js} +1 -1
  63. package/web-dist/assets/{pie-RZYD4A2V-C0PsjAlT.js → pie-RZYD4A2V-B9UJsCc6.js} +1 -1
  64. package/web-dist/assets/{pieDiagram-ENE6RG2P-CSuFf6VT.js → pieDiagram-ENE6RG2P-DvLH3PU6.js} +1 -1
  65. package/web-dist/assets/{quadrantDiagram-ABIIQ3AL-CuQtvBia.js → quadrantDiagram-ABIIQ3AL-BwC1BKho.js} +1 -1
  66. package/web-dist/assets/{radar-I7S5WNFK-gk0WSCd9.js → radar-I7S5WNFK-BziFtIY8.js} +1 -1
  67. package/web-dist/assets/{railroad-3IZDKUUU-CovQLWaQ.js → railroad-3IZDKUUU-DPD8gyCw.js} +1 -1
  68. package/web-dist/assets/railroad-abnf-AHOZXSZD-C6wFrQk2.js +1 -0
  69. package/web-dist/assets/railroad-ebnf-EBAXGLYW-yv1OIO4r.js +1 -0
  70. package/web-dist/assets/railroad-peg-LSFZ7HO6-BNocqLQb.js +1 -0
  71. package/web-dist/assets/{railroadDiagram-RFXS5EU6-RU1nu6mz.js → railroadDiagram-RFXS5EU6-DkS_57bW.js} +1 -1
  72. package/web-dist/assets/{requirementDiagram-TGXJPOKE-VoYjpOAN.js → requirementDiagram-TGXJPOKE-DT4YnV0v.js} +1 -1
  73. package/web-dist/assets/{sankeyDiagram-HTMAVEWB-DOVqKs1B.js → sankeyDiagram-HTMAVEWB-DMhUiLXu.js} +1 -1
  74. package/web-dist/assets/{sequenceDiagram-DBY2YBRQ-DWMCuKM1.js → sequenceDiagram-DBY2YBRQ-BzyFG5mU.js} +1 -1
  75. package/web-dist/assets/{src-T1UUKJjT.js → src-Bpw4w4N9.js} +1 -1
  76. package/web-dist/assets/{stateDiagram-2N3HPSRC-mvFnj-9X.js → stateDiagram-2N3HPSRC-DI7bXIvV.js} +1 -1
  77. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BnPQRodM.js +1 -0
  78. package/web-dist/assets/{swimlanes-5IMT3BWC-kRCjOJ75.js → swimlanes-5IMT3BWC-ocEwjrkf.js} +1 -1
  79. package/web-dist/assets/swimlanesDiagram-G3AALYLV-Cmi6u5Ox.js +8 -0
  80. package/web-dist/assets/{timeline-definition-FHXFAJF6-Z8oX_ic6.js → timeline-definition-FHXFAJF6-Cx606xig.js} +1 -1
  81. package/web-dist/assets/{treeView-QDETBFTQ-BNFv3tbG.js → treeView-QDETBFTQ-BBjaqvh7.js} +1 -1
  82. package/web-dist/assets/{treemap-6X3UGDF4-DFxnrL9n.js → treemap-6X3UGDF4-Bn8fQ4vt.js} +1 -1
  83. package/web-dist/assets/{vennDiagram-L72KCM5P-rBeP_pn_.js → vennDiagram-L72KCM5P-DaKXvRdc.js} +1 -1
  84. package/web-dist/assets/{wardley-OPB4EBWU-BovK6GPr.js → wardley-OPB4EBWU-CK6Oy13G.js} +1 -1
  85. package/web-dist/assets/{wardleyDiagram-EHGQE667-B1yRRFsz.js → wardleyDiagram-EHGQE667-esMnbbXF.js} +1 -1
  86. package/web-dist/assets/{xychartDiagram-FW5EYKEG-IShFUxtM.js → xychartDiagram-FW5EYKEG-D3DoqcNr.js} +1 -1
  87. package/web-dist/index.html +2 -2
  88. package/web-dist/assets/architecture-TIHT7OUA-BxHw4J0J.js +0 -1
  89. package/web-dist/assets/channel-CgUG0O7S.js +0 -1
  90. package/web-dist/assets/classDiagram-OUVF2IWQ-CpxiJqgs.js +0 -1
  91. package/web-dist/assets/classDiagram-v2-EOCWNBFH-CpxiJqgs.js +0 -1
  92. package/web-dist/assets/eventmodeling-45OFAUF4-C-XvAPEG.js +0 -1
  93. package/web-dist/assets/flowDiagram-23GEKE2U-DYBwekLd.js +0 -1
  94. package/web-dist/assets/index-B9jaLEKn.js +0 -330
  95. package/web-dist/assets/index-Dubos_kO.css +0 -2
  96. package/web-dist/assets/railroad-abnf-AHOZXSZD-DSq7zK8A.js +0 -1
  97. package/web-dist/assets/railroad-ebnf-EBAXGLYW-icFIsnio.js +0 -1
  98. package/web-dist/assets/railroad-peg-LSFZ7HO6-Csep5xwu.js +0 -1
  99. package/web-dist/assets/stateDiagram-v2-6OUMAXLB-BkzW7JgJ.js +0 -1
  100. 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 readFileSync5, realpathSync as realpathSync2, unlinkSync as unlinkSync3, writeFileSync as writeFileSync3 } from "node:fs";
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
- ...options.mcpServers && Object.keys(options.mcpServers).length > 0 ? { extraArgs: { "mcp-config": writeMcpConfigFile(sessionId, options.mcpServers) } } : {},
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
- return new RealClaudeQueryHandle(raw, input, sessionId, lifecycleAbort, redact, diagnostic);
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 mkdirSync4, readFileSync as readFileSync4, renameSync as renameSync2, statSync as statSync3 } from "node:fs";
27561
- import { join as join6 } from "node:path";
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
- mkdirSync4(join6(path, ".."), { recursive: true });
27962
+ mkdirSync5(join7(path, ".."), { recursive: true });
27566
27963
  try {
27567
- if (statSync3(path).size > MAX_BYTES) renameSync2(path, `${path}.1`);
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 runClaudeBroker(config) {
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
- mcpServers: workerMcpServers(projectMcpServers(config.cwd, { env }), config.mcpServers),
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(readFileSync5(config.recordPath, "utf8")).daemonPid;
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
- writeFileSync3(config.recordPath, JSON.stringify(record), { mode: 384 });
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
- running = runClaudeBroker(config);
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
  }