zidane 7.2.5 → 8.0.4
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/LICENSE +21 -0
- package/README.md +34 -6
- package/dist/{acp-D-tVyUjw.js → acp-DQDhi2dj.js} +230 -77
- package/dist/acp-DQDhi2dj.js.map +1 -0
- package/dist/acp-cli.d.ts +1 -1
- package/dist/acp-cli.js +9 -66
- package/dist/acp-cli.js.map +1 -1
- package/dist/acp.d.ts +10 -590
- package/dist/acp.d.ts.map +1 -1
- package/dist/acp.js +3 -3
- package/dist/{agent-D0zZnGbt.js → agent-DSLNmBp0.js} +14401 -13214
- package/dist/agent-DSLNmBp0.js.map +1 -0
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -2
- package/dist/aliasing-lVFbm81n.js +217 -0
- package/dist/aliasing-lVFbm81n.js.map +1 -0
- package/dist/{anthropic-C2aole0G.js → anthropic-CD00LhUP.js} +1442 -1264
- package/dist/anthropic-CD00LhUP.js.map +1 -0
- package/dist/artifacts-CV2hUKnL.js.map +1 -1
- package/dist/{atomic-write-B7zr1ddF.js → atomic-write-Ijgf2Mhc.js} +13 -3
- package/dist/atomic-write-Ijgf2Mhc.js.map +1 -0
- package/dist/call-tool-dHlbGOIY.js +70 -0
- package/dist/call-tool-dHlbGOIY.js.map +1 -0
- package/dist/chat/pure.d.ts +372 -6
- package/dist/chat/pure.d.ts.map +1 -1
- package/dist/chat/pure.js +35 -244
- package/dist/chat/pure.js.map +1 -1
- package/dist/{approvals-DKaJZYlm.js → chat-JPJJ8uu5.js} +15879 -14631
- package/dist/chat-JPJJ8uu5.js.map +1 -0
- package/dist/chat.d.ts +4 -6
- package/dist/chat.js +23 -12
- package/dist/cli-shared-C-YCaglL.js +159 -0
- package/dist/cli-shared-C-YCaglL.js.map +1 -0
- package/dist/client-core-BQSyDALT.d.ts +2115 -0
- package/dist/client-core-BQSyDALT.d.ts.map +1 -0
- package/dist/codemode-worker.d.ts +1 -0
- package/dist/codemode-worker.js +155 -0
- package/dist/codemode-worker.js.map +1 -0
- package/dist/codemode.d.ts +352 -0
- package/dist/codemode.d.ts.map +1 -0
- package/dist/codemode.js +5 -0
- package/dist/connection-Dr-Js4yZ.d.ts +28 -0
- package/dist/connection-Dr-Js4yZ.d.ts.map +1 -0
- package/dist/connection-loop-gdh22LTI.js +112 -0
- package/dist/connection-loop-gdh22LTI.js.map +1 -0
- package/dist/context-segments-BHKlagL6.js +1016 -0
- package/dist/context-segments-BHKlagL6.js.map +1 -0
- package/dist/{context-transport-T_6AYIvy.js → context-transport-DSMTUsK1.js} +3 -3
- package/dist/{context-transport-T_6AYIvy.js.map → context-transport-DSMTUsK1.js.map} +1 -1
- package/dist/contexts/daytona.d.ts +19 -8
- package/dist/contexts/daytona.d.ts.map +1 -1
- package/dist/contexts/daytona.js +1 -440
- package/dist/contexts/docker.d.ts +36 -7
- package/dist/contexts/docker.d.ts.map +1 -1
- package/dist/contexts/docker.js +1 -456
- package/dist/contexts/e2b.d.ts +68 -13
- package/dist/contexts/e2b.d.ts.map +1 -1
- package/dist/contexts/e2b.js +1 -382
- package/dist/contexts/sandbox.d.ts +1 -1
- package/dist/contexts/sandbox.js +5 -0
- package/dist/contexts/sandbox.js.map +1 -1
- package/dist/{contexts-CRE9pw07.js → contexts-d7UolYot.js} +1059 -524
- package/dist/contexts-d7UolYot.js.map +1 -0
- package/dist/contexts.d.ts +3 -3
- package/dist/contexts.js +1 -1
- package/dist/cred-keys-GpBg71cP.js +105 -0
- package/dist/cred-keys-GpBg71cP.js.map +1 -0
- package/dist/{auth-BG2rVKSt.js → credentials-JttEuRON.js} +55 -206
- package/dist/credentials-JttEuRON.js.map +1 -0
- package/dist/{cursor-iojRXHF9.js → cursor-CdjaQTBW.js} +3 -3
- package/dist/{cursor-iojRXHF9.js.map → cursor-CdjaQTBW.js.map} +1 -1
- package/dist/daemon-cli.d.ts +1 -1
- package/dist/daemon-cli.js +3 -2
- package/dist/daemon-cli.js.map +1 -1
- package/dist/daytona-DG3omipZ.js +513 -0
- package/dist/daytona-DG3omipZ.js.map +1 -0
- package/dist/defaults-Cx3zvDOV.js +23 -0
- package/dist/defaults-Cx3zvDOV.js.map +1 -0
- package/dist/dispatcher-BMlu2BmZ.js +4941 -0
- package/dist/dispatcher-BMlu2BmZ.js.map +1 -0
- package/dist/docker-BZhMMJUp.js +727 -0
- package/dist/docker-BZhMMJUp.js.map +1 -0
- package/dist/e2b-CEVOdRib.js +480 -0
- package/dist/e2b-CEVOdRib.js.map +1 -0
- package/dist/edit-utils-EGosADZq.js +234 -0
- package/dist/edit-utils-EGosADZq.js.map +1 -0
- package/dist/environment-8wsKE2Ln.js +214 -0
- package/dist/environment-8wsKE2Ln.js.map +1 -0
- package/dist/eval-BNcvle_J.js +1595 -0
- package/dist/eval-BNcvle_J.js.map +1 -0
- package/dist/eval.d.ts +2 -2
- package/dist/eval.js +1 -1489
- package/dist/executor-Cx5-8yqi.js +621 -0
- package/dist/executor-Cx5-8yqi.js.map +1 -0
- package/dist/extension-authoring-DyGGmq9V.js +385 -0
- package/dist/extension-authoring-DyGGmq9V.js.map +1 -0
- package/dist/extensions.d.ts +2 -2
- package/dist/extensions.js +3 -3
- package/dist/{fetch-url-DX75gpL0.js → fetch-url-Ct2TjDWt.js} +4 -4
- package/dist/{fetch-url-DX75gpL0.js.map → fetch-url-Ct2TjDWt.js.map} +1 -1
- package/dist/{format-BF745pLL.js → format-C2NQEVUH.js} +39 -2
- package/dist/format-C2NQEVUH.js.map +1 -0
- package/dist/format-bytes-C7tfc3Zm.js +55 -0
- package/dist/format-bytes-C7tfc3Zm.js.map +1 -0
- package/dist/headless-C_rNUuYC.js +960 -0
- package/dist/headless-C_rNUuYC.js.map +1 -0
- package/dist/headless.d.ts +1 -1
- package/dist/headless.js +1 -413
- package/dist/{history-search-C4bk1Wz_.js → history-search-CcYzF8-0.js} +7 -3
- package/dist/history-search-CcYzF8-0.js.map +1 -0
- package/dist/history-search-worker.d.ts +1 -1
- package/dist/history-search-worker.js +1 -1
- package/dist/host-D7NrI03V.js +378 -0
- package/dist/host-D7NrI03V.js.map +1 -0
- package/dist/host-signals-BcP5QKUD.js +118 -0
- package/dist/host-signals-BcP5QKUD.js.map +1 -0
- package/dist/identity-CwfO8K-t.js +26 -0
- package/dist/identity-CwfO8K-t.js.map +1 -0
- package/dist/{index-BgN1T3lk.d.ts → index-BGe1fQT7.d.ts} +10987 -9320
- package/dist/index-BGe1fQT7.d.ts.map +1 -0
- package/dist/index-BVQsIKGn.d.ts +448 -0
- package/dist/index-BVQsIKGn.d.ts.map +1 -0
- package/dist/{index-C6NEd5y_.d.ts → index-CHABJd-L.d.ts} +3386 -502
- package/dist/index-CHABJd-L.d.ts.map +1 -0
- package/dist/{index-C37_ppWv.d.ts → index-D6a7qXpg.d.ts} +2 -3
- package/dist/index-D6a7qXpg.d.ts.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +27 -23
- package/dist/{completion-core-BRo86R_L.js → inline-ghost-BVRQeGoL.js} +50 -2
- package/dist/inline-ghost-BVRQeGoL.js.map +1 -0
- package/dist/ipc/remote.d.ts +313 -0
- package/dist/ipc/remote.d.ts.map +1 -0
- package/dist/ipc/remote.js +4 -0
- package/dist/ipc.d.ts +6 -964
- package/dist/ipc.js +403 -4
- package/dist/ipc.js.map +1 -0
- package/dist/{isolation-Kfoof0MM.d.ts → isolation-D9L650W5.d.ts} +100 -15
- package/dist/isolation-D9L650W5.d.ts.map +1 -0
- package/dist/json-rpc--HXQti1E.js +350 -0
- package/dist/json-rpc--HXQti1E.js.map +1 -0
- package/dist/links-j8rED1sR.js +603 -0
- package/dist/links-j8rED1sR.js.map +1 -0
- package/dist/local-terminal-D8QThwo_.js +252 -0
- package/dist/local-terminal-D8QThwo_.js.map +1 -0
- package/dist/{logger--C1VQjwJ.js → logger-Gtqlayx8.js} +199 -173
- package/dist/logger-Gtqlayx8.js.map +1 -0
- package/dist/logs-view-CWG8aUAG.js.map +1 -1
- package/dist/{mcp-CgNd-f3F.js → mcp-_wecH9Qe.js} +608 -562
- package/dist/mcp-_wecH9Qe.js.map +1 -0
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +2 -2
- package/dist/media-sniff-UKijEaiQ.js.map +1 -1
- package/dist/{messages-2jcOA_5R.js → messages-BqiwcD2Q.js} +1017 -1014
- package/dist/messages-BqiwcD2Q.js.map +1 -0
- package/dist/{oauth-DxHO17JE.js → oauth-Px_pBB9W.js} +5 -5
- package/dist/{oauth-DxHO17JE.js.map → oauth-Px_pBB9W.js.map} +1 -1
- package/dist/{openai-compat-Dy56weK8.js → openai-compat-CFrpH7uq.js} +432 -589
- package/dist/openai-compat-CFrpH7uq.js.map +1 -0
- package/dist/orchestration-BCGmB8Ps.js +511 -0
- package/dist/orchestration-BCGmB8Ps.js.map +1 -0
- package/dist/output/stream-json.d.ts +1 -2
- package/dist/output/stream-json.d.ts.map +1 -1
- package/dist/output/stream-json.js +1 -1
- package/dist/output/terminal.d.ts +1 -2
- package/dist/output/terminal.d.ts.map +1 -1
- package/dist/output/terminal.js +13 -7
- package/dist/output/terminal.js.map +1 -1
- package/dist/{plugins-gbI7SBA6.js → plugins-DS7mahzZ.js} +20 -101
- package/dist/plugins-DS7mahzZ.js.map +1 -0
- package/dist/plugins.d.ts +1 -1
- package/dist/plugins.js +1 -2
- package/dist/prelude-Cxo4t74B.js +517 -0
- package/dist/prelude-Cxo4t74B.js.map +1 -0
- package/dist/presets.d.ts +1 -1
- package/dist/presets.js +1 -2
- package/dist/project-root-B8zADr4o.js +167 -0
- package/dist/project-root-B8zADr4o.js.map +1 -0
- package/dist/project-user-paths-BSgtrRxl.js +58 -0
- package/dist/project-user-paths-BSgtrRxl.js.map +1 -0
- package/dist/{prompts-Pqc3JdW_.d.ts → prompts-DYcvqShf.d.ts} +14 -11
- package/dist/prompts-DYcvqShf.d.ts.map +1 -0
- package/dist/protocol-DnhqqSmE.js +13 -0
- package/dist/protocol-DnhqqSmE.js.map +1 -0
- package/dist/providers/anthropic.d.ts +2 -2
- package/dist/providers/anthropic.js +1 -2
- package/dist/providers/arcee.d.ts +1 -1
- package/dist/providers/arcee.js +1 -1
- package/dist/providers/baseten.d.ts +1 -1
- package/dist/providers/baseten.js +1 -1
- package/dist/providers/cerebras.d.ts +1 -1
- package/dist/providers/cerebras.js +3 -2
- package/dist/providers/cerebras.js.map +1 -1
- package/dist/providers/local.d.ts +1 -1
- package/dist/providers/local.js +1 -1
- package/dist/providers/openai-compat.d.ts +1 -1
- package/dist/providers/openai-compat.js +1 -2
- package/dist/providers/openai.d.ts +1 -1
- package/dist/providers/openai.js +15 -12
- package/dist/providers/openai.js.map +1 -1
- package/dist/providers/openrouter.d.ts +1 -1
- package/dist/providers/openrouter.js +3 -2
- package/dist/providers/openrouter.js.map +1 -1
- package/dist/providers/xai.d.ts +1 -1
- package/dist/providers/xai.js +3 -3
- package/dist/{providers-BVhnFyiY.js → providers-XUR7F4Hi.js} +4 -4
- package/dist/{providers-BVhnFyiY.js.map → providers-XUR7F4Hi.js.map} +1 -1
- package/dist/providers.d.ts +1 -1
- package/dist/providers.js +4 -4
- package/dist/registry-D6ShBWJU.js +5234 -0
- package/dist/registry-D6ShBWJU.js.map +1 -0
- package/dist/remote-hosts-BtA_9ZGp.js +97 -0
- package/dist/remote-hosts-BtA_9ZGp.js.map +1 -0
- package/dist/restate.d.ts +2 -3
- package/dist/restate.d.ts.map +1 -1
- package/dist/restate.js +631 -572
- package/dist/restate.js.map +1 -1
- package/dist/run-summary-CklLiZ4y.js +288 -0
- package/dist/run-summary-CklLiZ4y.js.map +1 -0
- package/dist/{sandbox-BH0WQkXn.d.ts → sandbox-C5dR5DwQ.d.ts} +6 -3
- package/dist/sandbox-C5dR5DwQ.d.ts.map +1 -0
- package/dist/session/sqlite.d.ts +1 -2
- package/dist/session/sqlite.d.ts.map +1 -1
- package/dist/session/sqlite.js +1 -616
- package/dist/session-factory-BUGY5P40.js +1576 -0
- package/dist/session-factory-BUGY5P40.js.map +1 -0
- package/dist/{session-BqRP1hS7.js → session-xh9JXu6i.js} +94 -52
- package/dist/session-xh9JXu6i.js.map +1 -0
- package/dist/session.d.ts +1 -1
- package/dist/session.js +2 -2
- package/dist/settings-keys-Ckf29NeM.js +677 -0
- package/dist/settings-keys-Ckf29NeM.js.map +1 -0
- package/dist/shell-path-Dqaoow2J.js +412 -0
- package/dist/shell-path-Dqaoow2J.js.map +1 -0
- package/dist/{skills-BC3dodWp.js → skills-DKqZJm4X.js} +430 -470
- package/dist/skills-DKqZJm4X.js.map +1 -0
- package/dist/skills.d.ts +2 -2
- package/dist/skills.js +1 -2
- package/dist/source-mPExaUm8.js +381 -0
- package/dist/source-mPExaUm8.js.map +1 -0
- package/dist/sqlite-DV1DQzN2.js +698 -0
- package/dist/sqlite-DV1DQzN2.js.map +1 -0
- package/dist/{src-CkNQKFKr.js → src-BRMYRJib.js} +903 -1121
- package/dist/src-BRMYRJib.js.map +1 -0
- package/dist/{stdio-DPpBDPx5.js → stdio-C6YyoW3I.js} +4 -4
- package/dist/{stdio-DPpBDPx5.js.map → stdio-C6YyoW3I.js.map} +1 -1
- package/dist/{stdio-loader-Bbx19dMZ.js → stdio-loader-C830Vg-Z.js} +4 -4
- package/dist/{stdio-loader-Bbx19dMZ.js.map → stdio-loader-C830Vg-Z.js.map} +1 -1
- package/dist/system-prompt-MNVHatSD.d.ts.map +1 -1
- package/dist/timeout-DEaqslLO.d.ts.map +1 -1
- package/dist/todo-edits-Dn251YUP.js +1790 -0
- package/dist/todo-edits-Dn251YUP.js.map +1 -0
- package/dist/{turn-operations-DrbJlpq9.js → tool-formatters-IQFDrdrs.js} +1456 -2120
- package/dist/tool-formatters-IQFDrdrs.js.map +1 -0
- package/dist/tool-state-Dku6YqAB.js +989 -0
- package/dist/tool-state-Dku6YqAB.js.map +1 -0
- package/dist/tools/fetch-url.d.ts +1 -2
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +1 -2
- package/dist/tools/prompts.d.ts +1 -1
- package/dist/tools/prompts.js +7 -7
- package/dist/tools/prompts.js.map +1 -1
- package/dist/tools/web-search.d.ts +1 -2
- package/dist/tools/web-search.d.ts.map +1 -1
- package/dist/tools/web-search.js +2 -2
- package/dist/tools/web-search.js.map +1 -1
- package/dist/tools-_DnT3AGM.js +578 -0
- package/dist/tools-_DnT3AGM.js.map +1 -0
- package/dist/tools.d.ts +3 -3
- package/dist/tools.js +4 -2
- package/dist/tui.d.ts +534 -517
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +34933 -27243
- package/dist/tui.js.map +1 -1
- package/dist/turn-operations-B8sokwi5.js +333 -0
- package/dist/turn-operations-B8sokwi5.js.map +1 -0
- package/dist/turn-operations-DQX5ZZni.d.ts +3089 -0
- package/dist/turn-operations-DQX5ZZni.d.ts.map +1 -0
- package/dist/types-C7ZFOztE.d.ts +132 -0
- package/dist/types-C7ZFOztE.d.ts.map +1 -0
- package/dist/types-CH5XpIHw.js +114 -0
- package/dist/types-CH5XpIHw.js.map +1 -0
- package/dist/{types-CWgENVLY.js → types-Cu0ZGDu9.js} +10 -6
- package/dist/types-Cu0ZGDu9.js.map +1 -0
- package/dist/types-IqegbJEE.d.ts +624 -0
- package/dist/types-IqegbJEE.d.ts.map +1 -0
- package/dist/types.d.ts +4 -4
- package/dist/types.js +1 -1
- package/dist/url-D8HCl65b.js +1259 -0
- package/dist/url-D8HCl65b.js.map +1 -0
- package/dist/wasm-BVT-j5cl.js +33 -0
- package/dist/wasm-BVT-j5cl.js.map +1 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js +355 -0
- package/dist/workspace-exec-stream-SV2R9d5S.js.map +1 -0
- package/dist/ws-CIHMvlSh.js +952 -0
- package/dist/ws-CIHMvlSh.js.map +1 -0
- package/dist/ws-client-N7NxzvQp.js +230 -0
- package/dist/ws-client-N7NxzvQp.js.map +1 -0
- package/dist/{xai-CazjStOK.js → xai-CvJTeLrI.js} +165 -5
- package/dist/xai-CvJTeLrI.js.map +1 -0
- package/dist/xdg-zweed0-t.js +117 -0
- package/dist/xdg-zweed0-t.js.map +1 -0
- package/docs/ACP.md +52 -0
- package/docs/ARCHITECTURE.md +103 -6
- package/docs/ARTIFACTS.md +8 -4
- package/docs/CHAT.md +165 -14
- package/docs/EXECUTION_CONTEXT.md +8 -0
- package/docs/EXTENSIONS.md +123 -2
- package/docs/HEADLESS.md +2 -0
- package/docs/IPC.md +775 -64
- package/docs/MACHINES.md +456 -0
- package/docs/PANELS.md +3 -0
- package/docs/README.md +4 -0
- package/docs/RESTATE.md +3 -2
- package/docs/RUN_IN_BACKGROUND.md +10 -2
- package/docs/SERVER.md +1085 -0
- package/docs/SKILL.md +6 -1
- package/docs/TERMINAL.md +75 -0
- package/docs/TUI.md +65 -14
- package/package.json +58 -26
- package/scripts/eval.ts +4 -0
- package/dist/acp-D-tVyUjw.js.map +0 -1
- package/dist/agent-D0zZnGbt.js.map +0 -1
- package/dist/anthropic-C2aole0G.js.map +0 -1
- package/dist/approvals-DKaJZYlm.js.map +0 -1
- package/dist/atomic-write-B7zr1ddF.js.map +0 -1
- package/dist/auth-BG2rVKSt.js.map +0 -1
- package/dist/completion-core-BRo86R_L.js.map +0 -1
- package/dist/contexts/daytona.js.map +0 -1
- package/dist/contexts/docker.js.map +0 -1
- package/dist/contexts/e2b.js.map +0 -1
- package/dist/contexts-CRE9pw07.js.map +0 -1
- package/dist/eval.js.map +0 -1
- package/dist/format-BF745pLL.js.map +0 -1
- package/dist/glob-DluQFSw1.js +0 -270
- package/dist/glob-DluQFSw1.js.map +0 -1
- package/dist/headless.js.map +0 -1
- package/dist/history-search-C4bk1Wz_.js.map +0 -1
- package/dist/index-BgN1T3lk.d.ts.map +0 -1
- package/dist/index-C37_ppWv.d.ts.map +0 -1
- package/dist/index-C6NEd5y_.d.ts.map +0 -1
- package/dist/ipc-BKVIIOqH.js +0 -331
- package/dist/ipc-BKVIIOqH.js.map +0 -1
- package/dist/ipc.d.ts.map +0 -1
- package/dist/isolation-Kfoof0MM.d.ts.map +0 -1
- package/dist/json-rpc-rSWMUh9b.js +0 -192
- package/dist/json-rpc-rSWMUh9b.js.map +0 -1
- package/dist/logger--C1VQjwJ.js.map +0 -1
- package/dist/mcp-CgNd-f3F.js.map +0 -1
- package/dist/messages-2jcOA_5R.js.map +0 -1
- package/dist/openai-compat-Dy56weK8.js.map +0 -1
- package/dist/output-cap-C9zukcCj.js +0 -100
- package/dist/output-cap-C9zukcCj.js.map +0 -1
- package/dist/panels-B6gRk4bf.js +0 -789
- package/dist/panels-B6gRk4bf.js.map +0 -1
- package/dist/plugins-gbI7SBA6.js.map +0 -1
- package/dist/prompts-Pqc3JdW_.d.ts.map +0 -1
- package/dist/run-summary-BU_NgwUT.js +0 -269
- package/dist/run-summary-BU_NgwUT.js.map +0 -1
- package/dist/sandbox-BH0WQkXn.d.ts.map +0 -1
- package/dist/session/sqlite.js.map +0 -1
- package/dist/session-BqRP1hS7.js.map +0 -1
- package/dist/shell-path-DfdXrGg9.js +0 -1549
- package/dist/shell-path-DfdXrGg9.js.map +0 -1
- package/dist/skills-BC3dodWp.js.map +0 -1
- package/dist/src-CkNQKFKr.js.map +0 -1
- package/dist/todo-edits-UOwgxGKu.js +0 -528
- package/dist/todo-edits-UOwgxGKu.js.map +0 -1
- package/dist/tool-formatters-DoRLnfuD.d.ts +0 -759
- package/dist/tool-formatters-DoRLnfuD.d.ts.map +0 -1
- package/dist/transcript-anchors-BeudMbXx.d.ts +0 -1762
- package/dist/transcript-anchors-BeudMbXx.d.ts.map +0 -1
- package/dist/turn-operations-C8bMBDl_.d.ts +0 -1258
- package/dist/turn-operations-C8bMBDl_.d.ts.map +0 -1
- package/dist/turn-operations-DrbJlpq9.js.map +0 -1
- package/dist/types-CWgENVLY.js.map +0 -1
- package/dist/xai-CazjStOK.js.map +0 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { l as toolOutputByteLength, u as toolResultToText } from "./types-CWgENVLY.js";
|
|
2
|
-
import { n as shellQuote } from "./shell-quote-BmnhZmdM.js";
|
|
3
1
|
import { u as errorMessage } from "./errors-DKiMrOKq.js";
|
|
4
|
-
import {
|
|
2
|
+
import { d as toolResultToText, u as toolOutputByteLength } from "./types-Cu0ZGDu9.js";
|
|
5
3
|
import { a as sniffImageMediaTypeFromBase64, n as reconcileImageMediaType, o as sniffMediaTypeFromBase64, r as reconcileMediaType } from "./media-sniff-UKijEaiQ.js";
|
|
6
|
-
import {
|
|
4
|
+
import { n as shellQuote } from "./shell-quote-BmnhZmdM.js";
|
|
5
|
+
import { n as originPinnedFetch } from "./origin-pinned-fetch-CqG8VZIq.js";
|
|
7
6
|
import { Buffer } from "node:buffer";
|
|
8
7
|
import { mkdirSync } from "node:fs";
|
|
8
|
+
import { sep } from "node:path";
|
|
9
9
|
//#region src/mcp/oauth-provider.ts
|
|
10
10
|
/**
|
|
11
11
|
* In-memory store — primarily for tests, but valid as a no-persistence option
|
|
@@ -39,6 +39,7 @@ var McpOAuthProvider = class {
|
|
|
39
39
|
onAuthorizationUrl;
|
|
40
40
|
clientName;
|
|
41
41
|
_scope;
|
|
42
|
+
_state;
|
|
42
43
|
codeVerifierValue;
|
|
43
44
|
constructor(opts) {
|
|
44
45
|
this.name = opts.name;
|
|
@@ -47,6 +48,11 @@ var McpOAuthProvider = class {
|
|
|
47
48
|
this.onAuthorizationUrl = opts.onAuthorizationUrl;
|
|
48
49
|
this.clientName = opts.clientName ?? DEFAULT_CLIENT_NAME;
|
|
49
50
|
this._scope = opts.scope;
|
|
51
|
+
this._state = opts.state;
|
|
52
|
+
}
|
|
53
|
+
/** The configured `state` (the SDK sends nothing when it is empty). */
|
|
54
|
+
state() {
|
|
55
|
+
return this._state ?? "";
|
|
50
56
|
}
|
|
51
57
|
/**
|
|
52
58
|
* ALWAYS returns a URL — the placeholder in non-interactive mode — and
|
|
@@ -98,7 +104,7 @@ var McpOAuthProvider = class {
|
|
|
98
104
|
this.codeVerifierValue = verifier;
|
|
99
105
|
}
|
|
100
106
|
codeVerifier() {
|
|
101
|
-
if (!this.codeVerifierValue) throw new Error(`MCP OAuth: code verifier missing for "${this.name}"
|
|
107
|
+
if (!this.codeVerifierValue) throw new Error(`MCP OAuth: code verifier missing for "${this.name}". the flow must call saveCodeVerifier() before the redirect`);
|
|
102
108
|
return this.codeVerifierValue;
|
|
103
109
|
}
|
|
104
110
|
async redirectToAuthorization(url) {
|
|
@@ -190,157 +196,6 @@ function hasAuthorizationHeader(headers) {
|
|
|
190
196
|
return false;
|
|
191
197
|
}
|
|
192
198
|
//#endregion
|
|
193
|
-
//#region src/mcp/sse-to-json-fetch.ts
|
|
194
|
-
/**
|
|
195
|
-
* `fetch` shim that converts streamable-http POST responses with
|
|
196
|
-
* `Content-Type: text/event-stream` into synthetic `application/json`
|
|
197
|
-
* responses, preserving every original header (notably `mcp-session-id`).
|
|
198
|
-
*
|
|
199
|
-
* Why this exists
|
|
200
|
-
* ----------------
|
|
201
|
-
* The MCP SDK's streamable-http transport handles POST responses two ways
|
|
202
|
-
* depending on `content-type`:
|
|
203
|
-
*
|
|
204
|
-
* - `application/json` → `await response.json()` (works on every runtime).
|
|
205
|
-
* - `text/event-stream` → `body.pipeThrough(TextDecoderStream)
|
|
206
|
-
* .pipeThrough(EventSourceParserStream)`
|
|
207
|
-
* and forward each parsed event to `onmessage`.
|
|
208
|
-
*
|
|
209
|
-
* On Bun + MCP SDK 1.29.x the second pipeline silently drops the parsed
|
|
210
|
-
* event: the full `event: message\ndata: {...}\n\n` arrives intact, the
|
|
211
|
-
* stream closes, but `onmessage` is never called. Bootstrap then waits the
|
|
212
|
-
* full `bootstrapTimeout` (10s default) and the agent loses every tool the
|
|
213
|
-
* server would have exposed.
|
|
214
|
-
*
|
|
215
|
-
* The MCP spec lets clients accept either content type
|
|
216
|
-
* (`Accept: application/json, text/event-stream`), so flipping the stream
|
|
217
|
-
* to JSON at the boundary is a transparent, server-agnostic workaround.
|
|
218
|
-
*
|
|
219
|
-
* Scope of the shim
|
|
220
|
-
* -----------------
|
|
221
|
-
* - POST + `text/event-stream` → drain, parse SSE message events, return
|
|
222
|
-
* a synthetic `application/json` response. Single event becomes a JSON
|
|
223
|
-
* object (matching the shape the SDK expects from non-streaming
|
|
224
|
-
* servers); multiple events become a JSON array (the SDK already
|
|
225
|
-
* iterates `Array.isArray(data)` from a JSON response).
|
|
226
|
-
* - GET (long-lived `_startOrAuthSse` listener) → untouched. Per-event
|
|
227
|
-
* latency matters there and the SSE pipeline isn't always broken on
|
|
228
|
-
* GET in the same way (different code path inside Bun's stream impl).
|
|
229
|
-
* - 202 / non-SSE / malformed SSE / no body → passthrough.
|
|
230
|
-
*
|
|
231
|
-
* Runtime gating
|
|
232
|
-
* --------------
|
|
233
|
-
* `sseToJsonFetchIfNeeded()` only returns a wrapper on Bun. Node + browser
|
|
234
|
-
* runtimes get `undefined` and the SDK uses global `fetch` directly — no
|
|
235
|
-
* extra buffer-and-redrain on the happy path, and no risk of collapsing a
|
|
236
|
-
* future progress-streaming response into a single batched array. If you
|
|
237
|
-
* need to apply the shim unconditionally (testing, custom hosts), call
|
|
238
|
-
* `sseToJsonFetch()` directly.
|
|
239
|
-
*
|
|
240
|
-
* Cleanup
|
|
241
|
-
* -------
|
|
242
|
-
* Remove this file and its `fetch:` injection in `createTransport` once
|
|
243
|
-
* either Bun fixes the `pipeThrough` chain or the SDK switches off the
|
|
244
|
-
* streaming pipeline by default.
|
|
245
|
-
*/
|
|
246
|
-
/**
|
|
247
|
-
* Detect whether we're running on Bun. The `Bun` global is set by the
|
|
248
|
-
* runtime itself and isn't faked by Bun's Node-compat layer.
|
|
249
|
-
*/
|
|
250
|
-
function isBunRuntime() {
|
|
251
|
-
return typeof globalThis.Bun !== "undefined";
|
|
252
|
-
}
|
|
253
|
-
/**
|
|
254
|
-
* Returns the `sseToJsonFetch` wrapper only on runtimes that need it
|
|
255
|
-
* (currently Bun). Returns `undefined` everywhere else, which makes the
|
|
256
|
-
* SDK fall back to the global `fetch` and keeps the streaming pipeline
|
|
257
|
-
* intact on Node where it works correctly.
|
|
258
|
-
*
|
|
259
|
-
* Designed as the value to pass directly to `StreamableHTTPClientTransport`'s
|
|
260
|
-
* `fetch` option:
|
|
261
|
-
*
|
|
262
|
-
* new StreamableHTTPClientTransport(url, {
|
|
263
|
-
* fetch: sseToJsonFetchIfNeeded(),
|
|
264
|
-
* })
|
|
265
|
-
*/
|
|
266
|
-
function sseToJsonFetchIfNeeded() {
|
|
267
|
-
return isBunRuntime() ? sseToJsonFetch() : void 0;
|
|
268
|
-
}
|
|
269
|
-
/**
|
|
270
|
-
* Wrap a `fetch` implementation so streamable-http POST responses that come
|
|
271
|
-
* back as `text/event-stream` are converted to JSON. Pass the result as
|
|
272
|
-
* `opts.fetch` to `StreamableHTTPClientTransport`.
|
|
273
|
-
*
|
|
274
|
-
* Always-on (i.e. unconditional) — for the runtime-gated entry point,
|
|
275
|
-
* use {@link sseToJsonFetchIfNeeded} instead.
|
|
276
|
-
*/
|
|
277
|
-
function sseToJsonFetch(baseFetch = fetch) {
|
|
278
|
-
return async function sseToJsonWrappedFetch(input, init) {
|
|
279
|
-
const response = await baseFetch(input, init);
|
|
280
|
-
if ((init?.method ?? "GET").toString().toUpperCase() !== "POST") return response;
|
|
281
|
-
const contentType = response.headers.get("content-type");
|
|
282
|
-
if (!contentType || !contentType.includes("text/event-stream")) return response;
|
|
283
|
-
if (!response.body) return response;
|
|
284
|
-
let raw;
|
|
285
|
-
try {
|
|
286
|
-
raw = await response.text();
|
|
287
|
-
} catch {
|
|
288
|
-
return response;
|
|
289
|
-
}
|
|
290
|
-
const events = parseSseDataEvents(raw);
|
|
291
|
-
return synthesizeJsonResponse(response, events.length === 1 ? events[0] : events);
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
/**
|
|
295
|
-
* Parse a buffered SSE body into the JSON payloads of its `message` events.
|
|
296
|
-
*
|
|
297
|
-
* Skips:
|
|
298
|
-
* - SSE comments (lines starting with `:`).
|
|
299
|
-
* - Non-default event types (`event: foo` ≠ `message`). The MCP server
|
|
300
|
-
* only ever emits `message` events for JSON-RPC; anything else is out of
|
|
301
|
-
* band and the SDK wouldn't have surfaced it to `onmessage` either.
|
|
302
|
-
* - Malformed `data:` payloads (anything that fails `JSON.parse`).
|
|
303
|
-
*/
|
|
304
|
-
function parseSseDataEvents(raw) {
|
|
305
|
-
const events = [];
|
|
306
|
-
for (const block of raw.split(/\r?\n\r?\n/)) {
|
|
307
|
-
if (!block.trim()) continue;
|
|
308
|
-
const dataLines = [];
|
|
309
|
-
let isMessageEvent = true;
|
|
310
|
-
for (const line of block.split(/\r?\n/)) {
|
|
311
|
-
if (line.startsWith(":")) continue;
|
|
312
|
-
if (line.startsWith("event:")) {
|
|
313
|
-
const eventType = line.slice(6).trim();
|
|
314
|
-
if (eventType && eventType !== "message") isMessageEvent = false;
|
|
315
|
-
} else if (line.startsWith("data:")) {
|
|
316
|
-
const value = line.slice(5);
|
|
317
|
-
dataLines.push(value.startsWith(" ") ? value.slice(1) : value);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
if (!isMessageEvent || dataLines.length === 0) continue;
|
|
321
|
-
try {
|
|
322
|
-
events.push(JSON.parse(dataLines.join("\n")));
|
|
323
|
-
} catch {}
|
|
324
|
-
}
|
|
325
|
-
return events;
|
|
326
|
-
}
|
|
327
|
-
/**
|
|
328
|
-
* Build a `Response` mirroring the original's status / statusText / headers
|
|
329
|
-
* but with a JSON body and `content-type: application/json`. Header
|
|
330
|
-
* preservation is the whole point — `mcp-session-id` is set by the server
|
|
331
|
-
* on the initialize POST and must round-trip into the SDK's
|
|
332
|
-
* `_sessionId` capture (`response.headers.get('mcp-session-id')`).
|
|
333
|
-
*/
|
|
334
|
-
function synthesizeJsonResponse(original, payload) {
|
|
335
|
-
const headers = new Headers(original.headers);
|
|
336
|
-
headers.set("content-type", "application/json");
|
|
337
|
-
return new Response(JSON.stringify(payload), {
|
|
338
|
-
status: original.status,
|
|
339
|
-
statusText: original.statusText,
|
|
340
|
-
headers
|
|
341
|
-
});
|
|
342
|
-
}
|
|
343
|
-
//#endregion
|
|
344
199
|
//#region src/mcp/tolerant-client.ts
|
|
345
200
|
/**
|
|
346
201
|
* Async factory — builds an instance of the tolerant MCP client. The
|
|
@@ -395,155 +250,235 @@ async function createTolerantClient(info, options) {
|
|
|
395
250
|
return new TolerantMcpClient(info, options);
|
|
396
251
|
}
|
|
397
252
|
//#endregion
|
|
398
|
-
//#region src/mcp/
|
|
399
|
-
/**
|
|
400
|
-
* Non-owning view of a live MCP connection: the SAME tools and
|
|
401
|
-
* instructions, but a no-op `close`. Handed to consumers that must never
|
|
402
|
-
* tear down transports they don't own — the loop's per-call
|
|
403
|
-
* `ToolContext.mcpConnection` and child agents spawned off the parent's
|
|
404
|
-
* connection — because only the owning agent's destroy path may close the
|
|
405
|
-
* real thing, and a doc comment alone is too weak a guard. Single shared
|
|
406
|
-
* builder so the call sites can't drift when this shape grows a field.
|
|
407
|
-
*/
|
|
408
|
-
function nonOwningMcpView(connection) {
|
|
409
|
-
return {
|
|
410
|
-
tools: connection.tools,
|
|
411
|
-
...connection.instructions !== void 0 ? { instructions: connection.instructions } : {},
|
|
412
|
-
close: async () => {}
|
|
413
|
-
};
|
|
414
|
-
}
|
|
415
|
-
/**
|
|
416
|
-
* Recover a discovered tool's `{ server, toolName }` from its namespaced name
|
|
417
|
-
* (`mcp_<server>_<tool>`). Both server and tool names can contain underscores,
|
|
418
|
-
* so this matches against the known server list by longest server-name prefix
|
|
419
|
-
* rather than splitting blindly. Tools that match no configured server (custom
|
|
420
|
-
* connectors with a different naming scheme) fall back to
|
|
421
|
-
* `{ server: '', toolName: <namespacedName> }`.
|
|
422
|
-
*/
|
|
423
|
-
function deriveMcpToolMeta(namespacedName, servers) {
|
|
424
|
-
let best;
|
|
425
|
-
for (const server of servers) {
|
|
426
|
-
const prefix = `mcp_${server.name}_`;
|
|
427
|
-
if (namespacedName.startsWith(prefix) && (!best || server.name.length > best.name.length)) best = server;
|
|
428
|
-
}
|
|
429
|
-
if (best) return {
|
|
430
|
-
server: best.name,
|
|
431
|
-
toolName: namespacedName.slice(`mcp_${best.name}_`.length)
|
|
432
|
-
};
|
|
433
|
-
return {
|
|
434
|
-
server: "",
|
|
435
|
-
toolName: namespacedName
|
|
436
|
-
};
|
|
437
|
-
}
|
|
253
|
+
//#region src/mcp/tool-result.ts
|
|
438
254
|
/**
|
|
439
|
-
*
|
|
440
|
-
*
|
|
441
|
-
*
|
|
442
|
-
*
|
|
255
|
+
* Lossy flattener — converts MCP `CallToolResult.content` blocks to a single
|
|
256
|
+
* string. Text blocks are extracted; non-text blocks are JSON-stringified.
|
|
257
|
+
*
|
|
258
|
+
* Use this only at UI / log boundaries that require a string. The agent
|
|
259
|
+
* loop itself routes through {@link normalizeMcpBlocks} so image blocks
|
|
260
|
+
* survive into provider-native tool_result content (Anthropic blocks,
|
|
261
|
+
* OpenAI companion-user-message).
|
|
443
262
|
*/
|
|
444
|
-
function
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
263
|
+
function resultToString(content) {
|
|
264
|
+
if (!content || !Array.isArray(content)) return "";
|
|
265
|
+
return content.map((block) => {
|
|
266
|
+
if (block && typeof block === "object" && block.type === "text") {
|
|
267
|
+
const text = block.text;
|
|
268
|
+
if (typeof text === "string") return text;
|
|
269
|
+
}
|
|
270
|
+
return JSON.stringify(block);
|
|
271
|
+
}).join("\n");
|
|
448
272
|
}
|
|
449
|
-
const DEFAULT_MCP_BOOTSTRAP_TIMEOUT_MS = 1e4;
|
|
450
|
-
const DEFAULT_MCP_CLOSE_TIMEOUT_MS = 5e3;
|
|
451
273
|
/**
|
|
452
|
-
*
|
|
453
|
-
* listener used to bring down the whole `Promise.all` in
|
|
454
|
-
* `connectMcpServers`, defeating the batch's partial-failure tolerance.
|
|
455
|
-
*
|
|
456
|
-
* Failures are swallowed; under `ZIDANE_DEBUG` they're traced to stderr
|
|
457
|
-
* so the misbehaving listener is still discoverable when a host opts in.
|
|
274
|
+
* Resolve the media type for an audio/video content block whose `data` we have.
|
|
458
275
|
*
|
|
459
|
-
* `
|
|
460
|
-
*
|
|
276
|
+
* MCP declares `mimeType` on these blocks, but it's frequently missing or
|
|
277
|
+
* imprecise. Strategy: when the declared type is a recognized member of the
|
|
278
|
+
* kind's family, reconcile it against the bytes (override only on a positive
|
|
279
|
+
* different match); otherwise sniff from scratch; otherwise fall back to a
|
|
280
|
+
* sensible default for the kind.
|
|
461
281
|
*/
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/mcp] hook "${event}" listener rejected: ${errorMessage(err)}\n`);
|
|
468
|
-
}
|
|
282
|
+
function resolveMediaType(kind, declared, b64) {
|
|
283
|
+
const prefix = `${kind}/`;
|
|
284
|
+
const fallback = kind === "audio" ? "audio/wav" : "video/mp4";
|
|
285
|
+
if (typeof declared === "string" && declared.startsWith(prefix)) return reconcileMediaType(kind, declared, b64);
|
|
286
|
+
return sniffMediaTypeFromBase64(kind, b64) ?? fallback;
|
|
469
287
|
}
|
|
470
288
|
/**
|
|
471
|
-
*
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*/
|
|
476
|
-
async function closeTransportQuietly(transport) {
|
|
477
|
-
if (!transport) return;
|
|
478
|
-
try {
|
|
479
|
-
await transport.close();
|
|
480
|
-
} catch {}
|
|
481
|
-
}
|
|
482
|
-
/**
|
|
483
|
-
* Read the child PID off a stdio transport. Structural — only
|
|
484
|
-
* `StdioClientTransport` exposes `pid`; HTTP/SSE transports return `null`.
|
|
485
|
-
* Returns `null` once the child has exited (the SDK clears its process
|
|
486
|
-
* reference on the child's `close` event), so a kill based on this value
|
|
487
|
-
* never targets a recycled PID.
|
|
488
|
-
*/
|
|
489
|
-
function stdioPidOf(transport) {
|
|
490
|
-
if (transport?.__contextScoped === true) return null;
|
|
491
|
-
const pid = transport?.pid;
|
|
492
|
-
return typeof pid === "number" ? pid : null;
|
|
493
|
-
}
|
|
494
|
-
/**
|
|
495
|
-
* Last-resort reaper for a stdio child whose transport close wedged. By the
|
|
496
|
-
* time this runs, the graceful path (stdin EOF → SIGTERM escalation inside
|
|
497
|
-
* the SDK, where the installed version implements it) has been abandoned —
|
|
498
|
-
* SIGKILL is the only signal a hung child can't ignore. ESRCH (already
|
|
499
|
-
* exited) is swallowed.
|
|
289
|
+
* Turn an embedded-resource blob into the matching media block, choosing the
|
|
290
|
+
* kind from the declared `mimeType` prefix when recognized, else by sniffing
|
|
291
|
+
* the bytes (image → audio → video). Returns `null` when nothing identifies it
|
|
292
|
+
* as media, so the caller can fall back to the JSON-stringify path.
|
|
500
293
|
*/
|
|
501
|
-
function
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
294
|
+
function mediaBlockFromBlob(mimeType, b64) {
|
|
295
|
+
const image = sniffImageMediaTypeFromBase64(b64);
|
|
296
|
+
const audio = sniffMediaTypeFromBase64("audio", b64);
|
|
297
|
+
const video = sniffMediaTypeFromBase64("video", b64);
|
|
298
|
+
if (mimeType?.startsWith("image/")) {
|
|
299
|
+
if (image) return {
|
|
300
|
+
type: "image",
|
|
301
|
+
mediaType: image,
|
|
302
|
+
data: b64
|
|
303
|
+
};
|
|
304
|
+
if (audio) return {
|
|
305
|
+
type: "audio",
|
|
306
|
+
mediaType: audio,
|
|
307
|
+
data: b64
|
|
308
|
+
};
|
|
309
|
+
if (video) return {
|
|
310
|
+
type: "video",
|
|
311
|
+
mediaType: video,
|
|
312
|
+
data: b64
|
|
313
|
+
};
|
|
314
|
+
return {
|
|
315
|
+
type: "image",
|
|
316
|
+
mediaType: mimeType,
|
|
317
|
+
data: b64
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
if (mimeType?.startsWith("audio/")) {
|
|
321
|
+
if (audio) return {
|
|
322
|
+
type: "audio",
|
|
323
|
+
mediaType: reconcileMediaType("audio", mimeType, b64),
|
|
324
|
+
data: b64
|
|
325
|
+
};
|
|
326
|
+
if (image) return {
|
|
327
|
+
type: "image",
|
|
328
|
+
mediaType: image,
|
|
329
|
+
data: b64
|
|
330
|
+
};
|
|
331
|
+
if (video) return {
|
|
332
|
+
type: "video",
|
|
333
|
+
mediaType: video,
|
|
334
|
+
data: b64
|
|
335
|
+
};
|
|
336
|
+
return {
|
|
337
|
+
type: "audio",
|
|
338
|
+
mediaType: mimeType,
|
|
339
|
+
data: b64
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (mimeType?.startsWith("video/")) {
|
|
343
|
+
if (video) return {
|
|
344
|
+
type: "video",
|
|
345
|
+
mediaType: reconcileMediaType("video", mimeType, b64),
|
|
346
|
+
data: b64
|
|
347
|
+
};
|
|
348
|
+
if (image) return {
|
|
349
|
+
type: "image",
|
|
350
|
+
mediaType: image,
|
|
351
|
+
data: b64
|
|
352
|
+
};
|
|
353
|
+
if (audio) return {
|
|
354
|
+
type: "audio",
|
|
355
|
+
mediaType: audio,
|
|
356
|
+
data: b64
|
|
357
|
+
};
|
|
358
|
+
return {
|
|
359
|
+
type: "video",
|
|
360
|
+
mediaType: mimeType,
|
|
361
|
+
data: b64
|
|
362
|
+
};
|
|
363
|
+
}
|
|
364
|
+
if (image) return {
|
|
365
|
+
type: "image",
|
|
366
|
+
mediaType: image,
|
|
367
|
+
data: b64
|
|
368
|
+
};
|
|
369
|
+
if (audio) return {
|
|
370
|
+
type: "audio",
|
|
371
|
+
mediaType: audio,
|
|
372
|
+
data: b64
|
|
373
|
+
};
|
|
374
|
+
if (video) return {
|
|
375
|
+
type: "video",
|
|
376
|
+
mediaType: video,
|
|
377
|
+
data: b64
|
|
378
|
+
};
|
|
379
|
+
return null;
|
|
506
380
|
}
|
|
507
381
|
/**
|
|
508
|
-
*
|
|
509
|
-
* an actionable hint string. Conservative — only matches the signals we've
|
|
510
|
-
* actually observed in the wild against MCP servers, so a real outage on
|
|
511
|
-
* the right transport doesn't get misdiagnosed.
|
|
382
|
+
* Normalize MCP `CallToolResult.content` to zidane's {@link ToolResultContent[]} shape.
|
|
512
383
|
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
515
|
-
*
|
|
516
|
-
*
|
|
517
|
-
*
|
|
518
|
-
*
|
|
519
|
-
*
|
|
384
|
+
* Handles the MCP content block types:
|
|
385
|
+
* - `text` → preserved as `{type:'text', text}`
|
|
386
|
+
* - `image` → preserved as `{type:'image', mediaType, data}` (MCP uses `mimeType`)
|
|
387
|
+
* - `audio` → `{type:'audio', mediaType, data}`; mediaType reconciled/sniffed when
|
|
388
|
+
* the declared `mimeType` is missing or not an `audio/*` type
|
|
389
|
+
* - `video` → `{type:'video', mediaType, data}` (non-standard, by analogy)
|
|
390
|
+
* - `resource` with embedded text → flattened to a text block
|
|
391
|
+
* - `resource` with embedded blob → routed to image/audio/video by sniffing bytes
|
|
392
|
+
* first, with declared `mimeType` as a fallback when bytes are unidentifiable
|
|
393
|
+
* (it's OPTIONAL on EmbeddedResource per the MCP spec, and often imprecise)
|
|
394
|
+
* - Any unrecognized block → JSON-stringified fallback text block (lossy but safe)
|
|
395
|
+
*
|
|
396
|
+
* Returns `null` when the input is not an array — callers should fall back to an empty
|
|
397
|
+
* result in that case.
|
|
520
398
|
*/
|
|
521
|
-
function
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (
|
|
399
|
+
function normalizeMcpBlocks(content) {
|
|
400
|
+
if (!Array.isArray(content)) return null;
|
|
401
|
+
const out = [];
|
|
402
|
+
for (const raw of content) {
|
|
403
|
+
if (!raw || typeof raw !== "object") continue;
|
|
404
|
+
const block = raw;
|
|
405
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
406
|
+
out.push({
|
|
407
|
+
type: "text",
|
|
408
|
+
text: block.text
|
|
409
|
+
});
|
|
410
|
+
continue;
|
|
411
|
+
}
|
|
412
|
+
if (block.type === "image" && typeof block.data === "string") {
|
|
413
|
+
const declared = typeof block.mimeType === "string" ? block.mimeType : typeof block.mediaType === "string" ? block.mediaType : "image/png";
|
|
414
|
+
const mediaType = reconcileImageMediaType(declared, block.data);
|
|
415
|
+
out.push({
|
|
416
|
+
type: "image",
|
|
417
|
+
mediaType,
|
|
418
|
+
data: block.data
|
|
419
|
+
});
|
|
420
|
+
continue;
|
|
421
|
+
}
|
|
422
|
+
if (block.type === "audio" && typeof block.data === "string") {
|
|
423
|
+
out.push({
|
|
424
|
+
type: "audio",
|
|
425
|
+
mediaType: resolveMediaType("audio", block.mimeType ?? block.mediaType, block.data),
|
|
426
|
+
data: block.data
|
|
427
|
+
});
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (block.type === "video" && typeof block.data === "string") {
|
|
431
|
+
out.push({
|
|
432
|
+
type: "video",
|
|
433
|
+
mediaType: resolveMediaType("video", block.mimeType ?? block.mediaType, block.data),
|
|
434
|
+
data: block.data
|
|
435
|
+
});
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
if (block.type === "resource" && block.resource && typeof block.resource === "object") {
|
|
439
|
+
const res = block.resource;
|
|
440
|
+
if (typeof res.text === "string") {
|
|
441
|
+
out.push({
|
|
442
|
+
type: "text",
|
|
443
|
+
text: res.text
|
|
444
|
+
});
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
if (typeof res.blob === "string") {
|
|
448
|
+
const mediaBlock = mediaBlockFromBlob(typeof res.mimeType === "string" ? res.mimeType : void 0, res.blob);
|
|
449
|
+
if (mediaBlock) {
|
|
450
|
+
out.push(mediaBlock);
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
out.push({
|
|
456
|
+
type: "text",
|
|
457
|
+
text: JSON.stringify(block)
|
|
458
|
+
});
|
|
528
459
|
}
|
|
529
|
-
return
|
|
460
|
+
return out;
|
|
530
461
|
}
|
|
531
462
|
/**
|
|
532
|
-
*
|
|
533
|
-
* hint while preserving the original message + stack + cause. Returns the
|
|
534
|
-
* input error verbatim when no hint applies.
|
|
463
|
+
* Route the MCP result content through the narrowest appropriate zidane shape:
|
|
535
464
|
*
|
|
536
|
-
*
|
|
537
|
-
*
|
|
538
|
-
*
|
|
465
|
+
* - All blocks are `text` → return a joined string (smaller wire payload,
|
|
466
|
+
* string-friendly for hook consumers that don't need structured access).
|
|
467
|
+
* - Any block is non-text → return a structured `ToolResultContent[]`.
|
|
468
|
+
* - Empty / non-array input → return `''`.
|
|
539
469
|
*/
|
|
540
|
-
function
|
|
541
|
-
const
|
|
542
|
-
if (!
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
|
|
470
|
+
function packMcpResult(content) {
|
|
471
|
+
const normalized = normalizeMcpBlocks(content);
|
|
472
|
+
if (!normalized || normalized.length === 0) return "";
|
|
473
|
+
const parts = [];
|
|
474
|
+
for (const block of normalized) {
|
|
475
|
+
if (block.type !== "text") return normalized;
|
|
476
|
+
parts.push(block.text);
|
|
477
|
+
}
|
|
478
|
+
return parts.join("\n");
|
|
546
479
|
}
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/mcp/server-config.ts
|
|
547
482
|
/**
|
|
548
483
|
* The config a transport is actually built from.
|
|
549
484
|
*
|
|
@@ -560,69 +495,15 @@ function withTransportMismatchHint(config, err) {
|
|
|
560
495
|
*/
|
|
561
496
|
function resolveTransportConfig(config) {
|
|
562
497
|
return config.preExpanded ? config : expandMcpConfigEnv(config);
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
* behind for every plugin the user had switched OFF, on every discovery pass.
|
|
573
|
-
* Spawn time is the first moment the answer is unambiguous.
|
|
574
|
-
*
|
|
575
|
-
* Gated on `preExpanded` AND `env.PLUGIN_DATA`. `preExpanded` is the explicit
|
|
576
|
-
* loader-set marker; `env.PLUGIN_DATA` supplies the path. Requiring both keeps
|
|
577
|
-
* a hand-written `mcps.json` that happens to set `PLUGIN_DATA` in its own env
|
|
578
|
-
* from triggering directory creation off a value we never validated — a
|
|
579
|
-
* filesystem side effect should not key off a user-settable name when an
|
|
580
|
-
* unambiguous marker is already on the config.
|
|
581
|
-
*
|
|
582
|
-
* `cwd` is created too when it sits inside the data directory. A plugin may
|
|
583
|
-
* legitimately declare `cwd: "${PLUGIN_DATA}/work"`, and spawning with a
|
|
584
|
-
* non-existent `cwd` fails with a bare ENOENT. A `cwd` under `PLUGIN_ROOT` is
|
|
585
|
-
* NOT created — that path is package content the author was supposed to ship,
|
|
586
|
-
* and silently conjuring it would hide the mistake.
|
|
587
|
-
*
|
|
588
|
-
* Failures are swallowed deliberately: the spawn immediately after produces a
|
|
589
|
-
* far better diagnostic than a thrown mkdir, and this must not be the thing
|
|
590
|
-
* that takes a server down.
|
|
591
|
-
*/
|
|
592
|
-
function ensurePluginDataDir(config) {
|
|
593
|
-
if (!config.preExpanded) return;
|
|
594
|
-
const dataDir = config.env?.PLUGIN_DATA;
|
|
595
|
-
if (!dataDir) return;
|
|
596
|
-
try {
|
|
597
|
-
mkdirSync(dataDir, { recursive: true });
|
|
598
|
-
if (config.cwd && config.cwd !== dataDir && config.cwd.startsWith(dataDir + sep)) mkdirSync(config.cwd, { recursive: true });
|
|
599
|
-
} catch {}
|
|
600
|
-
}
|
|
601
|
-
/**
|
|
602
|
-
* Origin-pinned `fetch` for a remote server that opted into header pinning,
|
|
603
|
-
* or `undefined` when the server didn't (the overwhelmingly common case, and
|
|
604
|
-
* the one where we leave redirect handling to the platform).
|
|
605
|
-
*
|
|
606
|
-
* Deliberately does NOT require `config.headers`: the credential most worth
|
|
607
|
-
* pinning is often the one the config never mentions. `bootstrapServer`
|
|
608
|
-
* attaches an OAuth provider to a FLAGLESS server whenever the host can build
|
|
609
|
-
* one and stored tokens exist, and an Agent Plugins `mcp.json` cannot declare
|
|
610
|
-
* `auth` at all — so a plugin's remote server routinely ends up with an
|
|
611
|
-
* SDK-injected `Authorization` header and an empty `config.headers`.
|
|
612
|
-
* `originPinnedFetch` always protects `authorization` for the same reason.
|
|
613
|
-
*/
|
|
614
|
-
function pinnedFetchFor(config, baseFetch) {
|
|
615
|
-
if (!config.pinHeaderOrigin || !config.url) return void 0;
|
|
616
|
-
return originPinnedFetch(config.url, Object.keys(config.headers ?? {}), baseFetch ?? fetch);
|
|
617
|
-
}
|
|
618
|
-
function inferTransport(raw) {
|
|
619
|
-
if (raw.transport === "stdio" || raw.transport === "sse" || raw.transport === "streamable-http") return raw.transport;
|
|
620
|
-
if (raw.type === "stdio" || raw.type === "sse" || raw.type === "streamable-http" || raw.type === "http") return raw.type === "http" ? "streamable-http" : raw.type;
|
|
621
|
-
if (raw.command) return "stdio";
|
|
622
|
-
if (raw.httpUrl) return "streamable-http";
|
|
623
|
-
if (raw.sseUrl) return "sse";
|
|
624
|
-
if (raw.url) return "streamable-http";
|
|
625
|
-
throw new Error(`Cannot infer MCP transport from config: ${JSON.stringify(raw)}`);
|
|
498
|
+
}
|
|
499
|
+
function inferTransport(raw) {
|
|
500
|
+
if (raw.transport === "stdio" || raw.transport === "sse" || raw.transport === "streamable-http") return raw.transport;
|
|
501
|
+
if (raw.type === "stdio" || raw.type === "sse" || raw.type === "streamable-http" || raw.type === "http") return raw.type === "http" ? "streamable-http" : raw.type;
|
|
502
|
+
if (raw.command) return "stdio";
|
|
503
|
+
if (raw.httpUrl) return "streamable-http";
|
|
504
|
+
if (raw.sseUrl) return "sse";
|
|
505
|
+
if (raw.url) return "streamable-http";
|
|
506
|
+
throw new Error(`Cannot infer MCP transport from config: ${JSON.stringify(raw)}`);
|
|
626
507
|
}
|
|
627
508
|
/**
|
|
628
509
|
* Placeholder grammar for {@link expandEnvPlaceholders}: `${VAR}` and
|
|
@@ -652,7 +533,7 @@ function expandEnvPlaceholders(value, env = process.env) {
|
|
|
652
533
|
if (resolved !== void 0 && resolved !== "") return resolved;
|
|
653
534
|
if (fallback !== void 0) return fallback;
|
|
654
535
|
if (resolved !== void 0) return resolved;
|
|
655
|
-
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/mcp] env placeholder "${match}" is not set
|
|
536
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/mcp] env placeholder "${match}" is not set, passed through as written\n`);
|
|
656
537
|
return match;
|
|
657
538
|
});
|
|
658
539
|
}
|
|
@@ -684,6 +565,34 @@ function expandMcpConfigEnv(config, env = process.env) {
|
|
|
684
565
|
...config.headers ? { headers: expandEnvRecord(config.headers, env) } : {}
|
|
685
566
|
};
|
|
686
567
|
}
|
|
568
|
+
/**
|
|
569
|
+
* A config from a source that must never read this process's environment
|
|
570
|
+
* (a machine's project file, `docs/MACHINES.md`): every `${VAR}` placeholder
|
|
571
|
+
* resolved NOW to its `:-default`, else to the empty string, so the
|
|
572
|
+
* transport's own expansion has nothing of this process's secrets to put
|
|
573
|
+
* into a command, an env value, a URL or a header.
|
|
574
|
+
*/
|
|
575
|
+
function withoutEnvPlaceholders(config) {
|
|
576
|
+
const strip = (value) => {
|
|
577
|
+
let out = value;
|
|
578
|
+
for (let pass = 0; pass < 4 && ENV_PLACEHOLDER_RE.test(out); pass++) {
|
|
579
|
+
ENV_PLACEHOLDER_RE.lastIndex = 0;
|
|
580
|
+
out = out.replace(ENV_PLACEHOLDER_RE, (_match, _name, fallback) => fallback ?? "");
|
|
581
|
+
}
|
|
582
|
+
ENV_PLACEHOLDER_RE.lastIndex = 0;
|
|
583
|
+
return out.replace(ENV_PLACEHOLDER_RE, "");
|
|
584
|
+
};
|
|
585
|
+
const record = (r) => Object.fromEntries(Object.entries(r).map(([k, v]) => [k, strip(v)]));
|
|
586
|
+
return {
|
|
587
|
+
...config,
|
|
588
|
+
...config.command !== void 0 ? { command: strip(config.command) } : {},
|
|
589
|
+
...config.args ? { args: config.args.map((arg) => typeof arg === "string" ? strip(arg) : arg) } : {},
|
|
590
|
+
...config.env ? { env: record(config.env) } : {},
|
|
591
|
+
...config.cwd !== void 0 ? { cwd: strip(config.cwd) } : {},
|
|
592
|
+
...config.url !== void 0 ? { url: strip(config.url) } : {},
|
|
593
|
+
...config.headers ? { headers: record(config.headers) } : {}
|
|
594
|
+
};
|
|
595
|
+
}
|
|
687
596
|
function normalizeOne(name, raw) {
|
|
688
597
|
const transport = inferTransport(raw);
|
|
689
598
|
const url = raw.url ?? raw.httpUrl ?? raw.sseUrl;
|
|
@@ -755,230 +664,212 @@ function normalizeMcpServers(input) {
|
|
|
755
664
|
}
|
|
756
665
|
throw new Error(`Unsupported MCP server config shape: ${typeof input}`);
|
|
757
666
|
}
|
|
667
|
+
//#endregion
|
|
668
|
+
//#region src/mcp/sse-to-json-fetch.ts
|
|
758
669
|
/**
|
|
759
|
-
*
|
|
760
|
-
*
|
|
670
|
+
* `fetch` shim that converts streamable-http POST responses with
|
|
671
|
+
* `Content-Type: text/event-stream` into synthetic `application/json`
|
|
672
|
+
* responses, preserving every original header (notably `mcp-session-id`).
|
|
761
673
|
*
|
|
762
|
-
*
|
|
763
|
-
*
|
|
764
|
-
*
|
|
765
|
-
*
|
|
674
|
+
* Why this exists
|
|
675
|
+
* ----------------
|
|
676
|
+
* The MCP SDK's streamable-http transport handles POST responses two ways
|
|
677
|
+
* depending on `content-type`:
|
|
678
|
+
*
|
|
679
|
+
* - `application/json` → `await response.json()` (works on every runtime).
|
|
680
|
+
* - `text/event-stream` → `body.pipeThrough(TextDecoderStream)
|
|
681
|
+
* .pipeThrough(EventSourceParserStream)`
|
|
682
|
+
* and forward each parsed event to `onmessage`.
|
|
683
|
+
*
|
|
684
|
+
* On Bun + MCP SDK 1.29.x the second pipeline silently drops the parsed
|
|
685
|
+
* event: the full `event: message\ndata: {...}\n\n` arrives intact, the
|
|
686
|
+
* stream closes, but `onmessage` is never called. Bootstrap then waits the
|
|
687
|
+
* full `bootstrapTimeout` (10s default) and the agent loses every tool the
|
|
688
|
+
* server would have exposed.
|
|
689
|
+
*
|
|
690
|
+
* The MCP spec lets clients accept either content type
|
|
691
|
+
* (`Accept: application/json, text/event-stream`), so flipping the stream
|
|
692
|
+
* to JSON at the boundary is a transparent, server-agnostic workaround.
|
|
693
|
+
*
|
|
694
|
+
* Scope of the shim
|
|
695
|
+
* -----------------
|
|
696
|
+
* - POST + `text/event-stream` → drain, parse SSE message events, return
|
|
697
|
+
* a synthetic `application/json` response. Single event becomes a JSON
|
|
698
|
+
* object (matching the shape the SDK expects from non-streaming
|
|
699
|
+
* servers); multiple events become a JSON array (the SDK already
|
|
700
|
+
* iterates `Array.isArray(data)` from a JSON response).
|
|
701
|
+
* - GET (long-lived `_startOrAuthSse` listener) → untouched. Per-event
|
|
702
|
+
* latency matters there and the SSE pipeline isn't always broken on
|
|
703
|
+
* GET in the same way (different code path inside Bun's stream impl).
|
|
704
|
+
* - 202 / non-SSE / malformed SSE / no body → passthrough.
|
|
705
|
+
*
|
|
706
|
+
* Runtime gating
|
|
707
|
+
* --------------
|
|
708
|
+
* `sseToJsonFetchIfNeeded()` only returns a wrapper on Bun. Node + browser
|
|
709
|
+
* runtimes get `undefined` and the SDK uses global `fetch` directly — no
|
|
710
|
+
* extra buffer-and-redrain on the happy path, and no risk of collapsing a
|
|
711
|
+
* future progress-streaming response into a single batched array. If you
|
|
712
|
+
* need to apply the shim unconditionally (testing, custom hosts), call
|
|
713
|
+
* `sseToJsonFetch()` directly.
|
|
714
|
+
*
|
|
715
|
+
* Cleanup
|
|
716
|
+
* -------
|
|
717
|
+
* Remove this file and its `fetch:` injection in `createTransport` once
|
|
718
|
+
* either Bun fixes the `pipeThrough` chain or the SDK switches off the
|
|
719
|
+
* streaming pipeline by default.
|
|
766
720
|
*/
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
}
|
|
774
|
-
return JSON.stringify(block);
|
|
775
|
-
}).join("\n");
|
|
721
|
+
/**
|
|
722
|
+
* Detect whether we're running on Bun. The `Bun` global is set by the
|
|
723
|
+
* runtime itself and isn't faked by Bun's Node-compat layer.
|
|
724
|
+
*/
|
|
725
|
+
function isBunRuntime() {
|
|
726
|
+
return typeof globalThis.Bun !== "undefined";
|
|
776
727
|
}
|
|
777
728
|
/**
|
|
778
|
-
*
|
|
729
|
+
* Returns the `sseToJsonFetch` wrapper only on runtimes that need it
|
|
730
|
+
* (currently Bun). Returns `undefined` everywhere else, which makes the
|
|
731
|
+
* SDK fall back to the global `fetch` and keeps the streaming pipeline
|
|
732
|
+
* intact on Node where it works correctly.
|
|
779
733
|
*
|
|
780
|
-
*
|
|
781
|
-
*
|
|
782
|
-
*
|
|
783
|
-
*
|
|
784
|
-
*
|
|
734
|
+
* Designed as the value to pass directly to `StreamableHTTPClientTransport`'s
|
|
735
|
+
* `fetch` option:
|
|
736
|
+
*
|
|
737
|
+
* new StreamableHTTPClientTransport(url, {
|
|
738
|
+
* fetch: sseToJsonFetchIfNeeded(),
|
|
739
|
+
* })
|
|
785
740
|
*/
|
|
786
|
-
function
|
|
787
|
-
|
|
788
|
-
const fallback = kind === "audio" ? "audio/wav" : "video/mp4";
|
|
789
|
-
if (typeof declared === "string" && declared.startsWith(prefix)) return reconcileMediaType(kind, declared, b64);
|
|
790
|
-
return sniffMediaTypeFromBase64(kind, b64) ?? fallback;
|
|
741
|
+
function sseToJsonFetchIfNeeded() {
|
|
742
|
+
return isBunRuntime() ? sseToJsonFetch() : void 0;
|
|
791
743
|
}
|
|
792
744
|
/**
|
|
793
|
-
*
|
|
794
|
-
*
|
|
795
|
-
*
|
|
796
|
-
*
|
|
745
|
+
* Wrap a `fetch` implementation so streamable-http POST responses that come
|
|
746
|
+
* back as `text/event-stream` are converted to JSON. Pass the result as
|
|
747
|
+
* `opts.fetch` to `StreamableHTTPClientTransport`.
|
|
748
|
+
*
|
|
749
|
+
* Always-on (i.e. unconditional) — for the runtime-gated entry point,
|
|
750
|
+
* use {@link sseToJsonFetchIfNeeded} instead.
|
|
797
751
|
*/
|
|
798
|
-
function
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
if (
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
if (video) return {
|
|
814
|
-
type: "video",
|
|
815
|
-
mediaType: video,
|
|
816
|
-
data: b64
|
|
817
|
-
};
|
|
818
|
-
return {
|
|
819
|
-
type: "image",
|
|
820
|
-
mediaType: mimeType,
|
|
821
|
-
data: b64
|
|
822
|
-
};
|
|
823
|
-
}
|
|
824
|
-
if (mimeType?.startsWith("audio/")) {
|
|
825
|
-
if (audio) return {
|
|
826
|
-
type: "audio",
|
|
827
|
-
mediaType: reconcileMediaType("audio", mimeType, b64),
|
|
828
|
-
data: b64
|
|
829
|
-
};
|
|
830
|
-
if (image) return {
|
|
831
|
-
type: "image",
|
|
832
|
-
mediaType: image,
|
|
833
|
-
data: b64
|
|
834
|
-
};
|
|
835
|
-
if (video) return {
|
|
836
|
-
type: "video",
|
|
837
|
-
mediaType: video,
|
|
838
|
-
data: b64
|
|
839
|
-
};
|
|
840
|
-
return {
|
|
841
|
-
type: "audio",
|
|
842
|
-
mediaType: mimeType,
|
|
843
|
-
data: b64
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
if (mimeType?.startsWith("video/")) {
|
|
847
|
-
if (video) return {
|
|
848
|
-
type: "video",
|
|
849
|
-
mediaType: reconcileMediaType("video", mimeType, b64),
|
|
850
|
-
data: b64
|
|
851
|
-
};
|
|
852
|
-
if (image) return {
|
|
853
|
-
type: "image",
|
|
854
|
-
mediaType: image,
|
|
855
|
-
data: b64
|
|
856
|
-
};
|
|
857
|
-
if (audio) return {
|
|
858
|
-
type: "audio",
|
|
859
|
-
mediaType: audio,
|
|
860
|
-
data: b64
|
|
861
|
-
};
|
|
862
|
-
return {
|
|
863
|
-
type: "video",
|
|
864
|
-
mediaType: mimeType,
|
|
865
|
-
data: b64
|
|
866
|
-
};
|
|
867
|
-
}
|
|
868
|
-
if (image) return {
|
|
869
|
-
type: "image",
|
|
870
|
-
mediaType: image,
|
|
871
|
-
data: b64
|
|
872
|
-
};
|
|
873
|
-
if (audio) return {
|
|
874
|
-
type: "audio",
|
|
875
|
-
mediaType: audio,
|
|
876
|
-
data: b64
|
|
877
|
-
};
|
|
878
|
-
if (video) return {
|
|
879
|
-
type: "video",
|
|
880
|
-
mediaType: video,
|
|
881
|
-
data: b64
|
|
752
|
+
function sseToJsonFetch(baseFetch = fetch) {
|
|
753
|
+
return async function sseToJsonWrappedFetch(input, init) {
|
|
754
|
+
const response = await baseFetch(input, init);
|
|
755
|
+
if ((init?.method ?? "GET").toString().toUpperCase() !== "POST") return response;
|
|
756
|
+
const contentType = response.headers.get("content-type");
|
|
757
|
+
if (!contentType || !contentType.includes("text/event-stream")) return response;
|
|
758
|
+
if (!response.body) return response;
|
|
759
|
+
let raw;
|
|
760
|
+
try {
|
|
761
|
+
raw = await response.text();
|
|
762
|
+
} catch {
|
|
763
|
+
return response;
|
|
764
|
+
}
|
|
765
|
+
const events = parseSseDataEvents(raw);
|
|
766
|
+
return synthesizeJsonResponse(response, events.length === 1 ? events[0] : events);
|
|
882
767
|
};
|
|
883
|
-
return null;
|
|
884
768
|
}
|
|
885
769
|
/**
|
|
886
|
-
*
|
|
887
|
-
*
|
|
888
|
-
* Handles the MCP content block types:
|
|
889
|
-
* - `text` → preserved as `{type:'text', text}`
|
|
890
|
-
* - `image` → preserved as `{type:'image', mediaType, data}` (MCP uses `mimeType`)
|
|
891
|
-
* - `audio` → `{type:'audio', mediaType, data}`; mediaType reconciled/sniffed when
|
|
892
|
-
* the declared `mimeType` is missing or not an `audio/*` type
|
|
893
|
-
* - `video` → `{type:'video', mediaType, data}` (non-standard, by analogy)
|
|
894
|
-
* - `resource` with embedded text → flattened to a text block
|
|
895
|
-
* - `resource` with embedded blob → routed to image/audio/video by sniffing bytes
|
|
896
|
-
* first, with declared `mimeType` as a fallback when bytes are unidentifiable
|
|
897
|
-
* (it's OPTIONAL on EmbeddedResource per the MCP spec, and often imprecise)
|
|
898
|
-
* - Any unrecognized block → JSON-stringified fallback text block (lossy but safe)
|
|
770
|
+
* Parse a buffered SSE body into the JSON payloads of its `message` events.
|
|
899
771
|
*
|
|
900
|
-
*
|
|
901
|
-
*
|
|
772
|
+
* Skips:
|
|
773
|
+
* - SSE comments (lines starting with `:`).
|
|
774
|
+
* - Non-default event types (`event: foo` ≠ `message`). The MCP server
|
|
775
|
+
* only ever emits `message` events for JSON-RPC; anything else is out of
|
|
776
|
+
* band and the SDK wouldn't have surfaced it to `onmessage` either.
|
|
777
|
+
* - Malformed `data:` payloads (anything that fails `JSON.parse`).
|
|
902
778
|
*/
|
|
903
|
-
function
|
|
904
|
-
|
|
905
|
-
const
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
const mediaType = reconcileImageMediaType(typeof block.mimeType === "string" ? block.mimeType : typeof block.mediaType === "string" ? block.mediaType : "image/png", block.data);
|
|
918
|
-
out.push({
|
|
919
|
-
type: "image",
|
|
920
|
-
mediaType,
|
|
921
|
-
data: block.data
|
|
922
|
-
});
|
|
923
|
-
continue;
|
|
924
|
-
}
|
|
925
|
-
if (block.type === "audio" && typeof block.data === "string") {
|
|
926
|
-
out.push({
|
|
927
|
-
type: "audio",
|
|
928
|
-
mediaType: resolveMediaType("audio", block.mimeType ?? block.mediaType, block.data),
|
|
929
|
-
data: block.data
|
|
930
|
-
});
|
|
931
|
-
continue;
|
|
932
|
-
}
|
|
933
|
-
if (block.type === "video" && typeof block.data === "string") {
|
|
934
|
-
out.push({
|
|
935
|
-
type: "video",
|
|
936
|
-
mediaType: resolveMediaType("video", block.mimeType ?? block.mediaType, block.data),
|
|
937
|
-
data: block.data
|
|
938
|
-
});
|
|
939
|
-
continue;
|
|
940
|
-
}
|
|
941
|
-
if (block.type === "resource" && block.resource && typeof block.resource === "object") {
|
|
942
|
-
const res = block.resource;
|
|
943
|
-
if (typeof res.text === "string") {
|
|
944
|
-
out.push({
|
|
945
|
-
type: "text",
|
|
946
|
-
text: res.text
|
|
947
|
-
});
|
|
948
|
-
continue;
|
|
949
|
-
}
|
|
950
|
-
if (typeof res.blob === "string") {
|
|
951
|
-
const mediaBlock = mediaBlockFromBlob(typeof res.mimeType === "string" ? res.mimeType : void 0, res.blob);
|
|
952
|
-
if (mediaBlock) {
|
|
953
|
-
out.push(mediaBlock);
|
|
954
|
-
continue;
|
|
955
|
-
}
|
|
779
|
+
function parseSseDataEvents(raw) {
|
|
780
|
+
const events = [];
|
|
781
|
+
for (const block of raw.split(/\r?\n\r?\n/)) {
|
|
782
|
+
if (!block.trim()) continue;
|
|
783
|
+
const dataLines = [];
|
|
784
|
+
let isMessageEvent = true;
|
|
785
|
+
for (const line of block.split(/\r?\n/)) {
|
|
786
|
+
if (line.startsWith(":")) continue;
|
|
787
|
+
if (line.startsWith("event:")) {
|
|
788
|
+
const eventType = line.slice(6).trim();
|
|
789
|
+
if (eventType && eventType !== "message") isMessageEvent = false;
|
|
790
|
+
} else if (line.startsWith("data:")) {
|
|
791
|
+
const value = line.slice(5);
|
|
792
|
+
dataLines.push(value.startsWith(" ") ? value.slice(1) : value);
|
|
956
793
|
}
|
|
957
794
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
}
|
|
795
|
+
if (!isMessageEvent || dataLines.length === 0) continue;
|
|
796
|
+
try {
|
|
797
|
+
events.push(JSON.parse(dataLines.join("\n")));
|
|
798
|
+
} catch {}
|
|
962
799
|
}
|
|
963
|
-
return
|
|
800
|
+
return events;
|
|
964
801
|
}
|
|
965
802
|
/**
|
|
966
|
-
*
|
|
803
|
+
* Build a `Response` mirroring the original's status / statusText / headers
|
|
804
|
+
* but with a JSON body and `content-type: application/json`. Header
|
|
805
|
+
* preservation is the whole point — `mcp-session-id` is set by the server
|
|
806
|
+
* on the initialize POST and must round-trip into the SDK's
|
|
807
|
+
* `_sessionId` capture (`response.headers.get('mcp-session-id')`).
|
|
808
|
+
*/
|
|
809
|
+
function synthesizeJsonResponse(original, payload) {
|
|
810
|
+
const headers = new Headers(original.headers);
|
|
811
|
+
headers.set("content-type", "application/json");
|
|
812
|
+
return new Response(JSON.stringify(payload), {
|
|
813
|
+
status: original.status,
|
|
814
|
+
statusText: original.statusText,
|
|
815
|
+
headers
|
|
816
|
+
});
|
|
817
|
+
}
|
|
818
|
+
//#endregion
|
|
819
|
+
//#region src/mcp/transport.ts
|
|
820
|
+
/**
|
|
821
|
+
* Create an Agent Plugin server's `${PLUGIN_DATA}` directory, immediately
|
|
822
|
+
* before the process that needs it is spawned.
|
|
967
823
|
*
|
|
968
|
-
*
|
|
969
|
-
*
|
|
970
|
-
*
|
|
971
|
-
*
|
|
824
|
+
* Creation lives here rather than in the plugin loader because loading is not
|
|
825
|
+
* consent. A host must load every plugin it finds on disk just to learn its
|
|
826
|
+
* name — identity lives in the manifest — and the user's `enabledPlugins` gate
|
|
827
|
+
* only applies afterwards. Creating at load time therefore left a directory
|
|
828
|
+
* behind for every plugin the user had switched OFF, on every discovery pass.
|
|
829
|
+
* Spawn time is the first moment the answer is unambiguous.
|
|
830
|
+
*
|
|
831
|
+
* Gated on `preExpanded` AND `env.PLUGIN_DATA`. `preExpanded` is the explicit
|
|
832
|
+
* loader-set marker; `env.PLUGIN_DATA` supplies the path. Requiring both keeps
|
|
833
|
+
* a hand-written `mcps.json` that happens to set `PLUGIN_DATA` in its own env
|
|
834
|
+
* from triggering directory creation off a value we never validated — a
|
|
835
|
+
* filesystem side effect should not key off a user-settable name when an
|
|
836
|
+
* unambiguous marker is already on the config.
|
|
837
|
+
*
|
|
838
|
+
* `cwd` is created too when it sits inside the data directory. A plugin may
|
|
839
|
+
* legitimately declare `cwd: "${PLUGIN_DATA}/work"`, and spawning with a
|
|
840
|
+
* non-existent `cwd` fails with a bare ENOENT. A `cwd` under `PLUGIN_ROOT` is
|
|
841
|
+
* NOT created — that path is package content the author was supposed to ship,
|
|
842
|
+
* and silently conjuring it would hide the mistake.
|
|
843
|
+
*
|
|
844
|
+
* Failures are swallowed deliberately: the spawn immediately after produces a
|
|
845
|
+
* far better diagnostic than a thrown mkdir, and this must not be the thing
|
|
846
|
+
* that takes a server down.
|
|
972
847
|
*/
|
|
973
|
-
function
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
}
|
|
981
|
-
|
|
848
|
+
function ensurePluginDataDir(config) {
|
|
849
|
+
if (!config.preExpanded) return;
|
|
850
|
+
const dataDir = config.env?.PLUGIN_DATA;
|
|
851
|
+
if (!dataDir) return;
|
|
852
|
+
try {
|
|
853
|
+
mkdirSync(dataDir, { recursive: true });
|
|
854
|
+
if (config.cwd && config.cwd !== dataDir && config.cwd.startsWith(dataDir + sep)) mkdirSync(config.cwd, { recursive: true });
|
|
855
|
+
} catch {}
|
|
856
|
+
}
|
|
857
|
+
/**
|
|
858
|
+
* Origin-pinned `fetch` for a remote server that opted into header pinning,
|
|
859
|
+
* or `undefined` when the server didn't (the overwhelmingly common case, and
|
|
860
|
+
* the one where we leave redirect handling to the platform).
|
|
861
|
+
*
|
|
862
|
+
* Deliberately does NOT require `config.headers`: the credential most worth
|
|
863
|
+
* pinning is often the one the config never mentions. `bootstrapServer`
|
|
864
|
+
* attaches an OAuth provider to a FLAGLESS server whenever the host can build
|
|
865
|
+
* one and stored tokens exist, and an Agent Plugins `mcp.json` cannot declare
|
|
866
|
+
* `auth` at all — so a plugin's remote server routinely ends up with an
|
|
867
|
+
* SDK-injected `Authorization` header and an empty `config.headers`.
|
|
868
|
+
* `originPinnedFetch` always protects `authorization` for the same reason.
|
|
869
|
+
*/
|
|
870
|
+
function pinnedFetchFor(config, baseFetch) {
|
|
871
|
+
if (!config.pinHeaderOrigin || !config.url) return void 0;
|
|
872
|
+
return originPinnedFetch(config.url, Object.keys(config.headers ?? {}), baseFetch ?? fetch);
|
|
982
873
|
}
|
|
983
874
|
/**
|
|
984
875
|
* Create the appropriate MCP transport for a server config.
|
|
@@ -1001,7 +892,7 @@ async function createTransport(rawConfig, authProvider, hooks, execution) {
|
|
|
1001
892
|
case "stdio": {
|
|
1002
893
|
if (config.scope === "context") {
|
|
1003
894
|
if (!execution) throw new Error(`MCP server "${config.name}" requests \`scope: 'context'\`, but no execution context was supplied to the connector. Pass \`execution\` to connectMcpServers(), or use \`scope: 'host'\`.`);
|
|
1004
|
-
const { createContextStdioTransport } = await import("./context-transport-
|
|
895
|
+
const { createContextStdioTransport } = await import("./context-transport-DSMTUsK1.js");
|
|
1005
896
|
const executionHandle = await execution.getHandle();
|
|
1006
897
|
let commandLine = [config.command, ...config.args ?? []].map(shellQuote).join(" ");
|
|
1007
898
|
let env = config.env;
|
|
@@ -1020,7 +911,7 @@ async function createTransport(rawConfig, authProvider, hooks, execution) {
|
|
|
1020
911
|
...hooks ? { hooks } : {}
|
|
1021
912
|
});
|
|
1022
913
|
}
|
|
1023
|
-
const { StdioClientTransport, getDefaultEnvironment } = await import("./stdio-loader-
|
|
914
|
+
const { StdioClientTransport, getDefaultEnvironment } = await import("./stdio-loader-C830Vg-Z.js");
|
|
1024
915
|
const mergedEnv = config.env && !config.strictEnv ? {
|
|
1025
916
|
...getDefaultEnvironment(),
|
|
1026
917
|
...config.env
|
|
@@ -1060,7 +951,7 @@ async function createTransport(rawConfig, authProvider, hooks, execution) {
|
|
|
1060
951
|
/**
|
|
1061
952
|
* Split a stdio-transport child's stderr into lines and surface them via `mcp:warn`.
|
|
1062
953
|
*/
|
|
1063
|
-
const MAX_MCP_STDERR_LINE_CHARS =
|
|
954
|
+
const MAX_MCP_STDERR_LINE_CHARS = 65536;
|
|
1064
955
|
/**
|
|
1065
956
|
* Line-splitting core shared by the two stderr pumps: the Node-stream one
|
|
1066
957
|
* below (host stdio children) and the async-iterable one in
|
|
@@ -1113,6 +1004,156 @@ function attachStderrWarnPump(transport, serverName, hooks) {
|
|
|
1113
1004
|
stream.on("close", flush);
|
|
1114
1005
|
stream.on("error", flush);
|
|
1115
1006
|
}
|
|
1007
|
+
//#endregion
|
|
1008
|
+
//#region src/mcp/index.ts
|
|
1009
|
+
/**
|
|
1010
|
+
* Non-owning view of a live MCP connection: the SAME tools and
|
|
1011
|
+
* instructions, but a no-op `close`. Handed to consumers that must never
|
|
1012
|
+
* tear down transports they don't own — the loop's per-call
|
|
1013
|
+
* `ToolContext.mcpConnection` and child agents spawned off the parent's
|
|
1014
|
+
* connection — because only the owning agent's destroy path may close the
|
|
1015
|
+
* real thing, and a doc comment alone is too weak a guard. Single shared
|
|
1016
|
+
* builder so the call sites can't drift when this shape grows a field.
|
|
1017
|
+
*/
|
|
1018
|
+
function nonOwningMcpView(connection) {
|
|
1019
|
+
return {
|
|
1020
|
+
tools: connection.tools,
|
|
1021
|
+
...connection.instructions !== void 0 ? { instructions: connection.instructions } : {},
|
|
1022
|
+
close: async () => {}
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Recover a discovered tool's `{ server, toolName }` from its namespaced name
|
|
1027
|
+
* (`mcp_<server>_<tool>`). Both server and tool names can contain underscores,
|
|
1028
|
+
* so this matches against the known server list by longest server-name prefix
|
|
1029
|
+
* rather than splitting blindly. Tools that match no configured server (custom
|
|
1030
|
+
* connectors with a different naming scheme) fall back to
|
|
1031
|
+
* `{ server: '', toolName: <namespacedName> }`.
|
|
1032
|
+
*/
|
|
1033
|
+
function deriveMcpToolMeta(namespacedName, servers) {
|
|
1034
|
+
let best;
|
|
1035
|
+
for (const server of servers) {
|
|
1036
|
+
const prefix = `mcp_${server.name}_`;
|
|
1037
|
+
if (namespacedName.startsWith(prefix) && (!best || server.name.length > best.name.length)) best = server;
|
|
1038
|
+
}
|
|
1039
|
+
if (best) return {
|
|
1040
|
+
server: best.name,
|
|
1041
|
+
toolName: namespacedName.slice(`mcp_${best.name}_`.length)
|
|
1042
|
+
};
|
|
1043
|
+
return {
|
|
1044
|
+
server: "",
|
|
1045
|
+
toolName: namespacedName
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Apply an {@link McpToolWrap} to every entry of a discovered-tool map,
|
|
1050
|
+
* resolving each tool's {@link McpToolMeta} from `servers`. Returns a fresh
|
|
1051
|
+
* map; the input is untouched. Used by the agent to apply the
|
|
1052
|
+
* `AgentOptions.mcpToolWrap` hook to discovered tools.
|
|
1053
|
+
*/
|
|
1054
|
+
function wrapDiscoveredMcpTools(tools, wrap, servers) {
|
|
1055
|
+
const wrapped = {};
|
|
1056
|
+
for (const [name, def] of Object.entries(tools)) wrapped[name] = wrap(def, deriveMcpToolMeta(name, servers));
|
|
1057
|
+
return wrapped;
|
|
1058
|
+
}
|
|
1059
|
+
const DEFAULT_MCP_BOOTSTRAP_TIMEOUT_MS = 1e4;
|
|
1060
|
+
const DEFAULT_MCP_CLOSE_TIMEOUT_MS = 5e3;
|
|
1061
|
+
/**
|
|
1062
|
+
* Fire a hook without letting a listener's rejection escape. A throwing
|
|
1063
|
+
* listener used to bring down the whole `Promise.all` in
|
|
1064
|
+
* `connectMcpServers`, defeating the batch's partial-failure tolerance.
|
|
1065
|
+
*
|
|
1066
|
+
* Failures are swallowed; under `ZIDANE_DEBUG` they're traced to stderr
|
|
1067
|
+
* so the misbehaving listener is still discoverable when a host opts in.
|
|
1068
|
+
*
|
|
1069
|
+
* `args` is variadic to match `Hookable.callHook`'s spread shape; in
|
|
1070
|
+
* practice every `AgentHooks` event takes a single context object.
|
|
1071
|
+
*/
|
|
1072
|
+
async function safeCallHook(hooks, event, ...args) {
|
|
1073
|
+
if (!hooks) return;
|
|
1074
|
+
try {
|
|
1075
|
+
await hooks.callHook(event, ...args);
|
|
1076
|
+
} catch (err) {
|
|
1077
|
+
if (process.env.ZIDANE_DEBUG) process.stderr.write(`[zidane/mcp] hook "${event}" listener rejected: ${errorMessage(err)}\n`);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Close a transport best-effort. The catch swallows everything — by the
|
|
1082
|
+
* time we're closing, the bootstrap has already failed and the only thing
|
|
1083
|
+
* worse than a leaked transport is a leaked transport AND a new error
|
|
1084
|
+
* masking the original cause.
|
|
1085
|
+
*/
|
|
1086
|
+
async function closeTransportQuietly(transport) {
|
|
1087
|
+
if (!transport) return;
|
|
1088
|
+
try {
|
|
1089
|
+
await transport.close();
|
|
1090
|
+
} catch {}
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Read the child PID off a stdio transport. Structural — only
|
|
1094
|
+
* `StdioClientTransport` exposes `pid`; HTTP/SSE transports return `null`.
|
|
1095
|
+
* Returns `null` once the child has exited (the SDK clears its process
|
|
1096
|
+
* reference on the child's `close` event), so a kill based on this value
|
|
1097
|
+
* never targets a recycled PID.
|
|
1098
|
+
*/
|
|
1099
|
+
function stdioPidOf(transport) {
|
|
1100
|
+
if (transport?.__contextScoped === true) return null;
|
|
1101
|
+
const pid = transport?.pid;
|
|
1102
|
+
return typeof pid === "number" ? pid : null;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* Last-resort reaper for a stdio child whose transport close wedged. By the
|
|
1106
|
+
* time this runs, the graceful path (stdin EOF → SIGTERM escalation inside
|
|
1107
|
+
* the SDK, where the installed version implements it) has been abandoned —
|
|
1108
|
+
* SIGKILL is the only signal a hung child can't ignore. ESRCH (already
|
|
1109
|
+
* exited) is swallowed.
|
|
1110
|
+
*/
|
|
1111
|
+
function killStdioChild(pid) {
|
|
1112
|
+
if (pid == null) return;
|
|
1113
|
+
try {
|
|
1114
|
+
process.kill(pid, "SIGKILL");
|
|
1115
|
+
} catch {}
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Detect failure signatures that point at a transport mismatch and return
|
|
1119
|
+
* an actionable hint string. Conservative — only matches the signals we've
|
|
1120
|
+
* actually observed in the wild against MCP servers, so a real outage on
|
|
1121
|
+
* the right transport doesn't get misdiagnosed.
|
|
1122
|
+
*
|
|
1123
|
+
* Streamable-HTTP against an SSE-only endpoint:
|
|
1124
|
+
* - The transport opens an `initialize` POST. SSE-only servers respond
|
|
1125
|
+
* with `405 Method Not Allowed` or `404 Not Found` (their `/sse` GET
|
|
1126
|
+
* endpoint is the only handler). The SDK wraps these as
|
|
1127
|
+
* `Error POSTing to endpoint (HTTP 40X): ...`.
|
|
1128
|
+
* - Some proxies return `400` with a `text/event-stream` mismatch on
|
|
1129
|
+
* POST; same root cause, same fix.
|
|
1130
|
+
*/
|
|
1131
|
+
function buildTransportMismatchHint(config, err) {
|
|
1132
|
+
const msg = err.message;
|
|
1133
|
+
if (config.transport === "streamable-http") {
|
|
1134
|
+
if (/\bHTTP\s*40[45]\b/i.test(msg) || /\(40[45]\)/.test(msg)) return "This server may not implement the streamable-HTTP transport. Try `transport: 'sse'` (or `sseUrl` instead of `url`) on this server's config.";
|
|
1135
|
+
}
|
|
1136
|
+
if (config.transport === "stdio" && config.scope === "context") {
|
|
1137
|
+
if (/connection closed/i.test(msg) || /before initialization|closed during connect/i.test(msg)) return `The server exited during startup. With \`scope: 'context'\` it runs INSIDE the execution context, so \`${config.command}\` must exist in that image (check \`mcp:warn\` for the child's stderr, e.g. "command not found"). Install the runtime in the context image, or use \`scope: 'host'\` if this server does not need the workspace filesystem.`;
|
|
1138
|
+
}
|
|
1139
|
+
return null;
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* Wrap a bootstrap failure so the surfaced `Error` carries an actionable
|
|
1143
|
+
* hint while preserving the original message + stack + cause. Returns the
|
|
1144
|
+
* input error verbatim when no hint applies.
|
|
1145
|
+
*
|
|
1146
|
+
* `cause` (ES2022) gives hosts that introspect a way back to the raw SDK
|
|
1147
|
+
* error; the human-readable hint is the new message body that lands in
|
|
1148
|
+
* logs / hook payloads.
|
|
1149
|
+
*/
|
|
1150
|
+
function withTransportMismatchHint(config, err) {
|
|
1151
|
+
const hint = buildTransportMismatchHint(config, err);
|
|
1152
|
+
if (!hint) return err;
|
|
1153
|
+
const augmented = new Error(`${err.message}\nHint: ${hint}`, { cause: err });
|
|
1154
|
+
if (err.stack) augmented.stack = err.stack;
|
|
1155
|
+
return augmented;
|
|
1156
|
+
}
|
|
1116
1157
|
/**
|
|
1117
1158
|
* True when an error from `client.connect()` indicates the user needs to
|
|
1118
1159
|
* (re-)authenticate against the MCP server. Two signal families:
|
|
@@ -1314,9 +1355,9 @@ function wrapConnectWithHookFire(connect, fire) {
|
|
|
1314
1355
|
*/
|
|
1315
1356
|
async function bootstrapServer(config, _clientFactory, hooks, options) {
|
|
1316
1357
|
const start = Date.now();
|
|
1317
|
-
if (config.enabledTools && config.disabledTools) return emitConfigFailure(config, hooks, `MCP server "${config.name}":
|
|
1358
|
+
if (config.enabledTools && config.disabledTools) return emitConfigFailure(config, hooks, `MCP server "${config.name}": set enabledTools or disabledTools, not both.`);
|
|
1318
1359
|
if (config.lazyConnect && (!config.cachedTools || config.cachedTools.length === 0)) return emitConfigFailure(config, hooks, `MCP server "${config.name}": lazyConnect requires cachedTools (the host must pre-populate the tool schemas so they can be advertised without a connection).`);
|
|
1319
|
-
if (config.lazyConnect && config.auth === "oauth") return emitConfigFailure(config, hooks, `MCP server "${config.name}": lazyConnect
|
|
1360
|
+
if (config.lazyConnect && config.auth === "oauth") return emitConfigFailure(config, hooks, `MCP server "${config.name}": lazyConnect doesn't work with auth: 'oauth'. OAuth servers must complete the handshake at bootstrap so a login affordance can surface before the model commits to a tool call.`);
|
|
1320
1361
|
await safeCallHook(hooks, "mcp:bootstrap:start", {
|
|
1321
1362
|
name: config.name,
|
|
1322
1363
|
transport: config.transport
|
|
@@ -1520,7 +1561,7 @@ function makeLazyConnector(config, _clientFactory, authProvider, hooks, executio
|
|
|
1520
1561
|
liveTransport = null;
|
|
1521
1562
|
(hooks?.callHook("mcp:warn", {
|
|
1522
1563
|
name: config.name,
|
|
1523
|
-
message: "connection closed
|
|
1564
|
+
message: "connection closed, reconnecting on the next tool call"
|
|
1524
1565
|
}))?.catch(() => {});
|
|
1525
1566
|
}
|
|
1526
1567
|
};
|
|
@@ -1655,6 +1696,9 @@ async function applyMcpToolFilters(config, tools, hooks) {
|
|
|
1655
1696
|
* a fresh empty object schema instead of failing the run. (Fresh per call so a
|
|
1656
1697
|
* downstream in-place mutation can't bleed across degraded tools.)
|
|
1657
1698
|
*/
|
|
1699
|
+
function isPlainObject(value) {
|
|
1700
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1701
|
+
}
|
|
1658
1702
|
function toObjectSchema(schema) {
|
|
1659
1703
|
if (typeof schema === "object" && schema !== null && !Array.isArray(schema)) return schema;
|
|
1660
1704
|
return {
|
|
@@ -1712,7 +1756,8 @@ function buildMcpToolDef(config, getClient, tool, namespacedName, hooks, invalid
|
|
|
1712
1756
|
spec: {
|
|
1713
1757
|
name: namespacedName,
|
|
1714
1758
|
description: tool.description || "",
|
|
1715
|
-
inputSchema: toObjectSchema(tool.inputSchema)
|
|
1759
|
+
inputSchema: toObjectSchema(tool.inputSchema),
|
|
1760
|
+
...isPlainObject(tool.outputSchema) ? { outputSchema: tool.outputSchema } : {}
|
|
1716
1761
|
},
|
|
1717
1762
|
execute: async (input, ctx) => {
|
|
1718
1763
|
const { turnId, callId, signal } = ctx;
|
|
@@ -1784,7 +1829,7 @@ function buildMcpToolDef(config, getClient, tool, namespacedName, hooks, invalid
|
|
|
1784
1829
|
}, void 0, {
|
|
1785
1830
|
timeout,
|
|
1786
1831
|
signal: rpcSignal
|
|
1787
|
-
}), timeout, `MCP tool "${tool.name}" on server "${config.name}" timed out after ${timeout}ms. The server may still be processing the request
|
|
1832
|
+
}), timeout, `MCP tool "${tool.name}" on server "${config.name}" timed out after ${timeout}ms. The server may still be processing the request, so check its effect before retrying, and do not immediately retry the identical call.`, signal);
|
|
1788
1833
|
};
|
|
1789
1834
|
let result;
|
|
1790
1835
|
try {
|
|
@@ -1803,6 +1848,7 @@ function buildMcpToolDef(config, getClient, tool, namespacedName, hooks, invalid
|
|
|
1803
1848
|
const detail = (typeof output === "string" ? output : toolResultToText(output)).trim();
|
|
1804
1849
|
throw new Error(detail || `MCP tool "${tool.name}" on server "${config.name}" reported an error with no diagnostic content`);
|
|
1805
1850
|
}
|
|
1851
|
+
if (result.structuredContent !== void 0) ctx.reportStructured?.(result.structuredContent);
|
|
1806
1852
|
const transformCtx = {
|
|
1807
1853
|
turnId,
|
|
1808
1854
|
callId,
|
|
@@ -1910,6 +1956,6 @@ async function raceWithTimeoutAndSignal(task, timeoutMs, timeoutMessage, signal)
|
|
|
1910
1956
|
}
|
|
1911
1957
|
}
|
|
1912
1958
|
//#endregion
|
|
1913
|
-
export {
|
|
1959
|
+
export { createMemoryMcpCredentialStore as _, attachStderrWarnPump as a, expandEnvPlaceholders as c, resolveTransportConfig as d, withoutEnvPlaceholders as f, McpOAuthProvider as g, createTolerantClient as h, wrapDiscoveredMcpTools as i, expandMcpConfigEnv as l, resultToString as m, deriveMcpToolMeta as n, createStderrLineEmitter as o, normalizeMcpBlocks as p, nonOwningMcpView as r, sseToJsonFetchIfNeeded as s, connectMcpServers as t, normalizeMcpServers as u, hasAuthorizationHeader as v, isOAuthTokenRejectionError as y };
|
|
1914
1960
|
|
|
1915
|
-
//# sourceMappingURL=mcp-
|
|
1961
|
+
//# sourceMappingURL=mcp-_wecH9Qe.js.map
|