chainlesschain 0.162.188 → 0.162.193
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/README.md +61 -11
- package/bin/chainlesschain.js +7 -52
- package/completions/_cc +209 -0
- package/completions/cc.bash +18 -0
- package/completions/cc.fish +13 -0
- package/completions/cc.ps1 +226 -0
- package/package.json +10 -2
- package/src/assets/web-panel/assets/{AIOps-Brr1LVcE.js → AIOps-DBLkaTje.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DQU74DKG.js → ActionButton-BAy-Qebo.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CYp9178y.js → Analytics-Dfb1Mlau.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DW4SCYsW.js → AppLayout-BbFkN8m0.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CYlD_ed1.js → Artifacts-kM4-cEEH.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CWl1rZOi.js → Audit-BSQfYWf3.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BOR-LPsa.js → BackgroundAgents-CWDbI_fG.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DLpG7j1X.js → Backup-BxLPbq_Q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-B35K9wdr.js → BaseInput-DuIbdrp6.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CwUJb9gB.js → Chat-NEmjGahU.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-1v6aMS1m.js → ChatBubbleRenderer-DfJEo-5_.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-pi3-tPNW.js → Checkbox-CaoeG3Ns.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-FyNF35eL.js → Codegen-BFf_g3xY.js} +1 -1
- package/src/assets/web-panel/assets/{Col-tGt_JDEr.js → Col-CNOd8mdQ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CC7LonQX.js → Community-BhpDqz-i.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BGXrHHjE.js → Compact-CQ42T7Z6.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DSW0TBZ8.js → Compliance-DI5PcLMN.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bhk7rbvE.js → Cowork-wYYZe0wR.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DxyiQ2NZ.js → Cron-Dlq03DD_.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B2ZkxHok.js → Crosschain-BzqS8PP3.js} +1 -1
- package/src/assets/web-panel/assets/{DID-yOJV99eJ.js → DID-It55Yem7.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CKsttz7D.js → Dashboard-9HkH6oxn.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CVFldXSL.js → Dropdown-00e2YWRB.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-Bufv638J.js → EmailListRenderer-By-ugRDL.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-4ncjoIjL.js → FamilyGuardDashboard-C396yu5m.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DsknEDoX.js → Federation-BME5sbZY.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cqe5jWXA.js → FormItemContext-B4TEZCr-.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-NAJaFKxc.js → GenericCardRenderer-_9CX9ieY.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CszKQpnl.js → Git-BrS6WVSr.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BXGodXi6.js → Governance-BMdCWnKF.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CkoYuG2c.js → Inference-Bcp3vzcA.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DFlEr_T0.js → KnowledgeGraph-DlLuqGY5.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CVaENddA.js → Logs-D-W_6x09.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-DElGPxCZ.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CDlLT0fw.js → Marketplace-Dzn2guvI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DdwOyszN.js → McpTools-uPict-KL.js} +6 -6
- package/src/assets/web-panel/assets/{Memory-DKQnkKuG.js → Memory-BCw43kOb.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DGseqlzJ.js → MobileBridge-DmV8Wv7F.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-COBbEOlj.js → MobileProjects-C-HaCmSM.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-O61Vwyli.js → Mtc-C6jxJ34Y.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-Dw2SyasS.js → MtcAudit-Za8ExwhQ.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-hSu_l8kf.js → Multisig-KKDbuEgb.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CX1Epurr.js → NLProgramming-Dj97zbCk.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-R9xX8o8_.js → Notes-DBXvMraU.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-jtSNLCuK.js → NotificationSettings-DOxITLYl.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-7DVjZwMM.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Pu2WO-U3.js → Organization-D1i73mjb.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BjnqUyFp.js → Overflow-kW6Q3rvg.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-jjkvz44c.js → P2P-C-v_8p2v.js} +2 -2
- package/src/assets/web-panel/assets/PdhVaultBrowser-tUbbY64W.js +7 -0
- package/src/assets/web-panel/assets/{Permissions-D4yDpivd.js → Permissions-DkHxEZ58.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-0qsL6iH1.js → PersonalDataHub-DWuMPGrK.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-Cx7Ybrk2.js → Pipeline-30IYXWWm.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-PbfR_M_V.js → Privacy-DZNQ4RrH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CyV6Gsub.js → ProjectInit-De2MCElf.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C7KZ2Wdk.js → ProjectSettings-5uHk9wsC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DQDjO-yK.js → Projects-XjDfrFLb.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DyFVBBMX.js → Providers-Bgwj6gcE.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzB1BC9_.js → QrScannerModal-gKql9umv.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-Bk636mC1.js → QuickAsk-BWvmwkh7.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DoWxHIT3.js → Recommend-Uok4hZPd.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-oOVIv9wq.js → RemoteSession-DLJzTk9a.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BucoReGG.js → Reputation-ZVwIPMSO.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DzmOdJU7.js → Row-CN04HxWR.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C1tK7KGU.js → RssFeed-BdSrEG4g.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CSnInOqk.js → Search-5PwVd0IR.js} +1 -1
- package/src/assets/web-panel/assets/{Security-dlejr15l.js → Security-DdBo4zjN.js} +3 -3
- package/src/assets/web-panel/assets/{Services-BioPZuNh.js → Services-D3letU7M.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DWMibwpC.js → Skeleton--Jb83f1v.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BAAnx1B2.js → Skills-Bg-CulHX.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-jWNp4hNO.js → Sla-7QOa7S-X.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-y5kqKha-.js → SpeechSettings-DQwppjRw.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-B9GNsc3e.js → SyncSettings-dNPQGL-s.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-BDBYKRen.js → Tasks-Ggs-Z48q.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-DRiE5eaN.js → Templates-78TYHgRO.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BGlAvExF.js → Tenant-xHwwv9vO.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-B8uzrbNY.js → Terminal-BJXSJfm4.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Df48iveY.js → TimelineRenderer-fLbjRhJw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CcmHndN6.js → Tokens-oMSBSBmB.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPkWxmQ2.js → Trigger-CfnY_wX_.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DTb5a5AV.js → Trust-AdyTgYKO.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CBmg5vch.js → UkeySign-C5K_81dS.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B0mZrz4O.js → VideoEditing-vcGVDqki.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bxs7_mpd.js → Wallet-hhOc3hlY.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C8x3cMxU.js → WebAuthn-CVloC11s.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-DPrNznrP.js → WorkflowEditor-CYeE32R5.js} +1 -1
- package/src/assets/web-panel/assets/{chat-D3bYWu7S.js → chat-CNROR5bN.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DY-PwMu4.js → colors-Cjx-cnwU.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-LDk10Kw6.js → compact-item-DOIfelvD.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-C-2MZnFq.js → createContext-DfA7UZ8u.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ChcCBGs7.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BQIBjcnB.js → hasIn-BMSCzcfM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQVb2dWp.js → index-5-HXgOX5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdXnYMCZ.js → index-AWg-0M1S.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cx6D_dx2.js → index-B4iYzpkL.js} +1 -1
- package/src/assets/web-panel/assets/{index-_HRO_cIu.js → index-B5Udeb_6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlRYqt3S.js → index-B7gf77CJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrnRO9yE.js → index-BEMwBBHk.js} +1 -1
- package/src/assets/web-panel/assets/{index-VCh5zNJo.js → index-BM1y_9hM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRI3ngNg.js → index-BUonmKSE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3GNvExG.js → index-BbQ2eQ8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-DSvo_bp0.js → index-BemJ2u_E.js} +1 -1
- package/src/assets/web-panel/assets/{index-L6qixyLc.js → index-BolaOQxx.js} +1 -1
- package/src/assets/web-panel/assets/{index-BI4KgcYC.js → index-BxOk9vkG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CY4KnaO9.js → index-By61ojUx.js} +1 -1
- package/src/assets/web-panel/assets/{index-L3smcoQX.js → index-C2K9SNVX.js} +1 -1
- package/src/assets/web-panel/assets/{index-BX2b9Orc.js → index-C3L5gdiD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cz4b6Z_7.js → index-C7xn0Nub.js} +1 -1
- package/src/assets/web-panel/assets/{index-COG61sRv.js → index-CAnvhwg6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C7FC8cP2.js → index-CEKMcOyU.js} +1 -1
- package/src/assets/web-panel/assets/{index-BN-xdSWZ.js → index-CNC5onm4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY2FIrmz.js → index-CVL27kXC.js} +1 -1
- package/src/assets/web-panel/assets/{index-CiTvBAr-.js → index-CVm1_i3t.js} +1 -1
- package/src/assets/web-panel/assets/{index-DhRt9cYJ.js → index-CdYBye8B.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5pgtZnY.js → index-ClNMrqEY.js} +1 -1
- package/src/assets/web-panel/assets/{index-7xKu7Fns.js → index-Cs6l7uRu.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4K1weKc.js → index-CshQbilz.js} +1 -1
- package/src/assets/web-panel/assets/{index-k7C_oDCO.js → index-Cyweh0Hy.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3Oq-DLL.js → index-DBpWXFed.js} +3 -3
- package/src/assets/web-panel/assets/{index-DYyLJPlF.js → index-DHtpHZAQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dh-tkpKQ.js → index-DLmn0BoK.js} +1 -1
- package/src/assets/web-panel/assets/{index-8JC9BJa1.js → index-DPAWBrMf.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dd2Km97U.js → index-DjEsxO5h.js} +1 -1
- package/src/assets/web-panel/assets/{index-KFZO_lcx.js → index-FBc_CJBd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTIZpPtG.js → index-Icdoqmzt.js} +1 -1
- package/src/assets/web-panel/assets/index-Q2IajL54.js +1 -0
- package/src/assets/web-panel/assets/index-bRkHgOMc.js +1 -0
- package/src/assets/web-panel/assets/{index-lmm9p-jm.js → index-dCd8cSUq.js} +1 -1
- package/src/assets/web-panel/assets/{index-HasPt4-h.js → index-m2vG9gG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CaslYch6.js → index-mTpHoEzr.js} +1 -1
- package/src/assets/web-panel/assets/{index-CbR6ROOa.js → index-ti_lnTWl.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-zQPSHf2C.js → initDefaultProps-Bu8k7H1Y.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CKCStGVk.js → motion-s3iVK41k.js} +1 -1
- package/src/assets/web-panel/assets/{move-BufVDGYF.js → move-B6B-E5Jz.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DcaFxy5D.js → mtc-parser-BiYgTidA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-EScpkGdo.js → omit-pU1RdUDA.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-72hG_qfT.js → pickAttrs-Cdoq_SeM.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CMFWiH20.js → placementArrow-DIuvAn2K.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DKQHS41p.js → responsiveObserve-8w16YR2D.js} +1 -1
- package/src/assets/web-panel/assets/{slide-QVAFHTIQ.js → slide-VZ01cZe3.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DIz38e2G.js → statusUtils-lh8YMN8Z.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-zK5cyLby.js → styleChecker-CmDyreec.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DhZngk62.js → useFlexGapSupport-hiJWMU5y.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BvV2cvkg.js → useFs-Thnu-Iaj.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CRmxb9l3.js → usePersonalDataHub-LBdOxjwY.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CgQIbobP.js → vnode-Cg6lFY_X.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D4XrrO0R.js → zoom-DVfsR1YO.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/cli-help.js +181 -0
- package/src/command-help-index.json +181 -0
- package/src/command-manifest.json +1107 -175
- package/src/command-surface-policy.js +197 -0
- package/src/commands/agent.js +98 -33
- package/src/commands/artifacts.js +400 -2
- package/src/commands/background-session.js +41 -3
- package/src/commands/checkpoint-managed.js +1059 -0
- package/src/commands/checkpoint-restore-recovery.js +1476 -0
- package/src/commands/checkpoint.js +950 -22
- package/src/commands/compact.js +101 -25
- package/src/commands/config.js +506 -20
- package/src/commands/doctor.js +4 -1
- package/src/commands/hub.js +1 -1
- package/src/commands/mcp.js +688 -24
- package/src/commands/pack.js +16 -2
- package/src/commands/plugin.js +20 -12
- package/src/commands/session-mcp-recovery.js +218 -0
- package/src/commands/session.js +470 -110
- package/src/commands/setup.js +36 -10
- package/src/commands/skill.js +181 -110
- package/src/commands/status.js +21 -5
- package/src/commands/team-distributed.js +3324 -0
- package/src/commands/team.js +1130 -74
- package/src/commands/update.js +68 -6
- package/src/constants.js +11 -0
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/action-protocol.js +10 -0
- package/src/gateways/ws/session-protocol.js +555 -27
- package/src/gateways/ws/ws-agent-handler.js +541 -14
- package/src/gateways/ws/ws-session-gateway.js +129 -22
- package/src/gateways/ws/ws-session-state.js +431 -0
- package/src/harness/background-task-manager.js +233 -36
- package/src/harness/jsonl-session-store.js +2551 -167
- package/src/harness/mcp-client.js +802 -61
- package/src/harness/prompt-compressor.js +227 -27
- package/src/harness/session-index.js +89 -38
- package/src/harness/session-list-index.js +254 -0
- package/src/harness/structured-handoff.js +410 -0
- package/src/lazy-dispatch.js +747 -84
- package/src/lib/__tests__/logger.test.js +50 -2
- package/src/lib/advisor-runtime.js +1047 -0
- package/src/lib/agent-core.js +2 -0
- package/src/lib/agent-sandbox.js +120 -26
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/agent-team/task-lease.js +271 -3
- package/src/lib/agent-team/team-adjudication.js +1501 -0
- package/src/lib/agent-team/team-budget.js +4 -1
- package/src/lib/agent-team/team-control-store.js +582 -0
- package/src/lib/agent-team/team-distributed-queue.js +5246 -0
- package/src/lib/agent-team/team-process-checkpoint.js +211 -0
- package/src/lib/agent-team/team-runner.js +543 -24
- package/src/lib/agent-team/team-worktree.js +1403 -95
- package/src/lib/artifact-store.js +131 -8
- package/src/lib/background-agent-supervisor.js +185 -13
- package/src/lib/background-interaction-journal.js +37 -9
- package/src/lib/background-launch-profile.js +1058 -0
- package/src/lib/checkpoint-restore-already-completed-controller.js +902 -0
- package/src/lib/checkpoint-restore-orchestrator.js +987 -0
- package/src/lib/checkpoint-restore-partial-rollback-controller.js +1953 -0
- package/src/lib/checkpoint-restore-recovery-controller.js +689 -0
- package/src/lib/checkpoint-restore-recovery.js +884 -0
- package/src/lib/checkpoint-restore-retention.js +126 -0
- package/src/lib/checkpoint-restore-saga.js +5079 -0
- package/src/lib/checkpoint-restore-session-recovery.js +1178 -0
- package/src/lib/checkpoint-restore-workspace-target-verifier.js +270 -0
- package/src/lib/checkpoint-store.js +1688 -97
- package/src/lib/checkpoint-timeline-authority.js +522 -0
- package/src/lib/checkpoint-timeline.js +642 -0
- package/src/lib/config-keys.js +18 -119
- package/src/lib/config-manager.js +446 -27
- package/src/lib/config-redaction.js +57 -0
- package/src/lib/config-schema.js +853 -0
- package/src/lib/cowork-mcp-tools.js +5 -0
- package/src/lib/cowork-task-runner.js +458 -63
- package/src/lib/delivery-coordinator.js +1234 -0
- package/src/lib/delivery-evidence.js +450 -0
- package/src/lib/doctor-checkup.js +199 -14
- package/src/lib/executable-path.js +34 -0
- package/src/lib/fatal-handler.js +16 -0
- package/src/lib/file-checkpoint.js +4909 -186
- package/src/lib/file-lines.js +125 -0
- package/src/lib/headless-config-command.js +16 -5
- package/src/lib/hook-runner.cjs +139 -38
- package/src/lib/hooks-v2-workspace-context.js +10 -0
- package/src/lib/impacted-gate-selector.js +424 -0
- package/src/lib/interaction-adapter.js +62 -0
- package/src/lib/jsonl-session-store.js +1 -35
- package/src/lib/logger.js +66 -14
- package/src/lib/managed-checkpoint-render.js +60 -0
- package/src/lib/managed-tool-checkpoint.js +194 -0
- package/src/lib/mcp-call-ledger-store.js +1069 -0
- package/src/lib/mcp-call-ledger.js +747 -0
- package/src/lib/mcp-client.js +5 -0
- package/src/lib/mcp-conflict-scheduler.js +468 -0
- package/src/lib/mcp-effect-contract.js +102 -0
- package/src/lib/mcp-headers-helper-trust.js +145 -0
- package/src/lib/mcp-headers-helper.js +616 -0
- package/src/lib/mcp-host-recovery-runtime.js +131 -0
- package/src/lib/mcp-ledger-recovery-admission.js +1065 -0
- package/src/lib/mcp-recovery-adjudication.js +443 -0
- package/src/lib/output-context.js +86 -0
- package/src/lib/packer/file-identity.js +170 -0
- package/src/lib/packer/index.js +3 -0
- package/src/lib/packer/native-update-state.js +277 -0
- package/src/lib/packer/pack-update-applier.js +1983 -136
- package/src/lib/packer/pack-update-checker.js +34 -0
- package/src/lib/packer/pack-update-downloader.js +875 -86
- package/src/lib/packer/pack-update-signature.js +86 -0
- package/src/lib/packer/pkg-config-generator.js +6 -0
- package/src/lib/paths.js +65 -10
- package/src/lib/plan-mode.js +831 -14
- package/src/lib/plan-persistence.js +540 -0
- package/src/lib/plugin-runtime/hooks.js +227 -69
- package/src/lib/plugin-runtime/plugin-options.js +189 -44
- package/src/lib/pr-automation-policy.js +146 -8
- package/src/lib/process-execution-broker/credential-agent.js +7 -0
- package/src/lib/process-execution-broker/index.js +491 -13
- package/src/lib/process-execution-broker/linux-generic-bwrap-runtime.js +28 -1
- package/src/lib/process-execution-broker/linux-generic-bwrap.js +28 -4
- package/src/lib/process-execution-broker/platform-sandbox.js +92 -70
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +284 -19
- package/src/lib/process-execution-broker/workspace-transaction.js +4459 -0
- package/src/lib/project-instructions.js +165 -29
- package/src/lib/project-mcp-trust.js +67 -0
- package/src/lib/public-ide-capability-manifest.js +272 -0
- package/src/lib/public-ide-capability-validation.js +351 -0
- package/src/lib/secret-store.js +267 -43
- package/src/lib/secure-file-identity.js +433 -0
- package/src/lib/secure-fs.js +1089 -0
- package/src/lib/security-audit.js +58 -0
- package/src/lib/service-manager.js +34 -13
- package/src/lib/session-budget-runtime.js +1452 -0
- package/src/lib/session-host-snapshot.js +388 -0
- package/src/lib/session-message-provenance.js +429 -0
- package/src/lib/session-projection.js +804 -0
- package/src/lib/session-resource-budget.js +1495 -0
- package/src/lib/settings-hooks.cjs +138 -22
- package/src/lib/settings-loader.cjs +25 -2
- package/src/lib/side-effect-ledger-store.js +45 -23
- package/src/lib/skill-execution-identity.js +217 -0
- package/src/lib/skill-loader.js +299 -18
- package/src/lib/sub-agent-context.js +236 -28
- package/src/lib/sub-agent-registry.js +5 -0
- package/src/lib/subagent-contract.js +94 -0
- package/src/lib/todo-manager.js +185 -18
- package/src/lib/todo-persistence.js +417 -0
- package/src/lib/turn-binding-store.js +41 -23
- package/src/lib/turn-binding.js +160 -2
- package/src/lib/with-file-lock.js +165 -24
- package/src/lib/ws-chat-handler.js +21 -0
- package/src/program-base.js +18 -0
- package/src/repl/advisor-command.js +103 -0
- package/src/repl/agent-repl.js +2175 -146
- package/src/repl/btw-command.js +93 -10
- package/src/repl/clipboard-image.js +119 -0
- package/src/repl/config-summary.js +64 -108
- package/src/repl/prompt-editor.js +233 -0
- package/src/repl/prompt-interactions.js +344 -0
- package/src/repl/prompt-stash.js +354 -0
- package/src/repl/prompt-suggestions.js +312 -0
- package/src/repl/repl-keybindings.js +227 -0
- package/src/repl/session-recap.js +217 -0
- package/src/repl/slash-command-registry.js +22 -0
- package/src/repl/terminal-layout.js +88 -0
- package/src/runtime/agent-core.js +1630 -447
- package/src/runtime/coding-agent-contract-shared.cjs +13 -1
- package/src/runtime/coding-agent-managed-tool-policy.cjs +7 -2
- package/src/runtime/diagnostics.js +32 -13
- package/src/runtime/headless-runner.js +310 -118
- package/src/runtime/headless-stream.js +356 -95
- package/src/runtime/mcp-config.js +148 -14
- package/src/runtime/policies/agent-policy.js +14 -0
- package/src/runtime/status-diagnostics-lite.js +137 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-7iWGwZ-r.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BU2Yqq5H.js +0 -1
- package/src/assets/web-panel/assets/PdhVaultBrowser-69ca_WeR.js +0 -7
- package/src/assets/web-panel/assets/devWarning-B4dXIJqD.js +0 -1
- package/src/assets/web-panel/assets/index-C-C7DaG6.js +0 -1
- package/src/assets/web-panel/assets/index-DkfWzWtd.js +0 -1
|
@@ -12,9 +12,16 @@ import { executionBroker } from "../lib/process-execution-broker/index.js";
|
|
|
12
12
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
13
13
|
import { EventEmitter } from "events";
|
|
14
14
|
import { pathToFileURL } from "url";
|
|
15
|
+
import WebSocket from "ws";
|
|
15
16
|
import { safeJsonParse } from "../lib/safe-json.js";
|
|
16
17
|
import { EventRuntimeProducer } from "../lib/event-runtime-producer.js";
|
|
18
|
+
import { currentHostHooksV2WorkspaceBinding } from "../lib/hooks-v2-workspace-context.js";
|
|
17
19
|
import { resolvePluginWorkspaceAuthority } from "../lib/plugin-runtime/sandbox-policy.js";
|
|
20
|
+
import {
|
|
21
|
+
mergeMcpHeaders,
|
|
22
|
+
resolveMcpHeadersHelperContext,
|
|
23
|
+
runMcpHeadersHelper,
|
|
24
|
+
} from "../lib/mcp-headers-helper.js";
|
|
18
25
|
|
|
19
26
|
/**
|
|
20
27
|
* Injectable dependencies — overridable from tests.
|
|
@@ -23,6 +30,9 @@ import { resolvePluginWorkspaceAuthority } from "../lib/plugin-runtime/sandbox-p
|
|
|
23
30
|
export const _deps = {
|
|
24
31
|
spawn: executionBroker.spawn.bind(executionBroker),
|
|
25
32
|
fetch: (...args) => globalThis.fetch(...args),
|
|
33
|
+
WebSocket,
|
|
34
|
+
runMcpHeadersHelper,
|
|
35
|
+
resolveMcpHeadersHelperContext,
|
|
26
36
|
// Backoff sleep seam (tests override with a no-op so retries don't wait).
|
|
27
37
|
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
28
38
|
};
|
|
@@ -37,8 +47,24 @@ export const ServerState = {
|
|
|
37
47
|
ERROR: "error",
|
|
38
48
|
};
|
|
39
49
|
|
|
40
|
-
|
|
41
|
-
|
|
50
|
+
export const MCP_CONFIG_SCOPES = Object.freeze([
|
|
51
|
+
"local",
|
|
52
|
+
"project",
|
|
53
|
+
"user",
|
|
54
|
+
"managed",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
export function normalizeMcpConfigScope(scope = "user") {
|
|
58
|
+
const normalized = String(scope || "user")
|
|
59
|
+
.trim()
|
|
60
|
+
.toLowerCase();
|
|
61
|
+
if (!MCP_CONFIG_SCOPES.includes(normalized)) {
|
|
62
|
+
throw new Error(
|
|
63
|
+
`Invalid MCP scope "${scope}"; expected ${MCP_CONFIG_SCOPES.join(" | ")}`,
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
return normalized;
|
|
67
|
+
}
|
|
42
68
|
|
|
43
69
|
/**
|
|
44
70
|
* Default per-call timeout for HTTP MCP requests, mirroring the 30s stdio
|
|
@@ -47,6 +73,8 @@ const URL_TRANSPORTS = new Set(["http", "https", "sse", "ws", "wss"]);
|
|
|
47
73
|
* review) are exempt; override per server with `config.requestTimeoutMs`.
|
|
48
74
|
*/
|
|
49
75
|
const HTTP_REQUEST_TIMEOUT_MS = 30000;
|
|
76
|
+
const WEBSOCKET_CONNECT_TIMEOUT_MS = 10000;
|
|
77
|
+
const WEBSOCKET_MAX_PAYLOAD_BYTES = 16 * 1024 * 1024;
|
|
50
78
|
|
|
51
79
|
/**
|
|
52
80
|
* Default per-call timeout for stdio MCP requests — the same 30s default as the
|
|
@@ -88,7 +116,7 @@ export function inferTransport(config) {
|
|
|
88
116
|
) {
|
|
89
117
|
return proto;
|
|
90
118
|
}
|
|
91
|
-
} catch
|
|
119
|
+
} catch {
|
|
92
120
|
// fall through to stdio
|
|
93
121
|
}
|
|
94
122
|
}
|
|
@@ -171,6 +199,34 @@ export function isHttpTransport(transportKind) {
|
|
|
171
199
|
);
|
|
172
200
|
}
|
|
173
201
|
|
|
202
|
+
/** True for MCP's bidirectional JSON-RPC-over-WebSocket transports. */
|
|
203
|
+
export function isWebSocketTransport(transportKind) {
|
|
204
|
+
return transportKind === "ws" || transportKind === "wss";
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export function redactMcpUrl(value) {
|
|
208
|
+
try {
|
|
209
|
+
const parsed = new URL(String(value));
|
|
210
|
+
parsed.username = "";
|
|
211
|
+
parsed.password = "";
|
|
212
|
+
parsed.search = "";
|
|
213
|
+
parsed.hash = "";
|
|
214
|
+
return parsed.toString();
|
|
215
|
+
} catch {
|
|
216
|
+
return "<invalid-url>";
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function mcpTransportError(code, message, details = {}) {
|
|
221
|
+
const error = new Error(message);
|
|
222
|
+
error.code = code;
|
|
223
|
+
error.transport = details.transport || null;
|
|
224
|
+
error.url = details.url ? redactMcpUrl(details.url) : null;
|
|
225
|
+
if (details.status != null) error.status = details.status;
|
|
226
|
+
if (details.closeCode != null) error.closeCode = details.closeCode;
|
|
227
|
+
return error;
|
|
228
|
+
}
|
|
229
|
+
|
|
174
230
|
/**
|
|
175
231
|
* Heuristic: does this error look like the server went away (vs. the tool
|
|
176
232
|
* itself failing)? Used to gate reconnect-and-retry for servers that have a
|
|
@@ -183,11 +239,19 @@ export function isHttpTransport(transportKind) {
|
|
|
183
239
|
*/
|
|
184
240
|
export function isLikelyConnectionError(err) {
|
|
185
241
|
const msg = String((err && err.message) || err || "");
|
|
186
|
-
return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|network error|HTTP 40[134]\b|not connected|not found|not available/i.test(
|
|
242
|
+
return /fetch failed|ECONNREFUSED|ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|network error|WebSocket|HTTP 40[134]\b|not connected|not found|not available/i.test(
|
|
187
243
|
msg,
|
|
188
244
|
);
|
|
189
245
|
}
|
|
190
246
|
|
|
247
|
+
export function isMcpAuthenticationError(err) {
|
|
248
|
+
return (
|
|
249
|
+
err?.status === 401 ||
|
|
250
|
+
err?.status === 403 ||
|
|
251
|
+
/\bHTTP\s*40[13]\b/i.test(String(err?.message || err || ""))
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
|
|
191
255
|
/** Capability-discovery retry tuning (Claude-Code 2.1.191 "short backoff"). */
|
|
192
256
|
const MCP_DISCOVERY_RETRIES = 2; // up to 3 attempts total
|
|
193
257
|
const MCP_DISCOVERY_BACKOFF_MS = 250; // 250ms, 500ms
|
|
@@ -238,6 +302,8 @@ export class MCPClient extends EventEmitter {
|
|
|
238
302
|
this._reconnecting = new Map(); // name → in-flight reconnect promise
|
|
239
303
|
this._sessionId =
|
|
240
304
|
options && options.sessionId != null ? String(options.sessionId) : null;
|
|
305
|
+
this._workspaceBinding =
|
|
306
|
+
options?.workspaceBinding || currentHostHooksV2WorkspaceBinding();
|
|
241
307
|
// MCP roots capability (Claude-Code 2.1.203 parity): servers may ask the
|
|
242
308
|
// client for its workspace roots (`roots/list`, a server→client request).
|
|
243
309
|
// null = derive from process.cwd() at answer time.
|
|
@@ -327,7 +393,9 @@ export class MCPClient extends EventEmitter {
|
|
|
327
393
|
`mcp-elicitation:${this._elicitationKey(serverName, requestId)}`,
|
|
328
394
|
{ response: normalized },
|
|
329
395
|
);
|
|
330
|
-
} catch {
|
|
396
|
+
} catch {
|
|
397
|
+
// Runtime inbox acknowledgement is advisory.
|
|
398
|
+
}
|
|
331
399
|
return true;
|
|
332
400
|
}
|
|
333
401
|
|
|
@@ -441,7 +509,9 @@ export class MCPClient extends EventEmitter {
|
|
|
441
509
|
`mcp-elicitation:${key}`,
|
|
442
510
|
{ response },
|
|
443
511
|
);
|
|
444
|
-
} catch {
|
|
512
|
+
} catch {
|
|
513
|
+
// Runtime inbox acknowledgement is advisory.
|
|
514
|
+
}
|
|
445
515
|
return response;
|
|
446
516
|
}
|
|
447
517
|
} catch (error) {
|
|
@@ -464,7 +534,9 @@ export class MCPClient extends EventEmitter {
|
|
|
464
534
|
`mcp-elicitation:${key}`,
|
|
465
535
|
{ response: { action: "decline" } },
|
|
466
536
|
);
|
|
467
|
-
} catch {
|
|
537
|
+
} catch {
|
|
538
|
+
// Runtime inbox acknowledgement is advisory.
|
|
539
|
+
}
|
|
468
540
|
this._settleUrlElicitation(request, { action: "decline" });
|
|
469
541
|
this.emit("elicitation-deferred", {
|
|
470
542
|
...request,
|
|
@@ -485,7 +557,9 @@ export class MCPClient extends EventEmitter {
|
|
|
485
557
|
"elicitation timeout",
|
|
486
558
|
{ retryDelayMs: 0, maxAttempts: 1 },
|
|
487
559
|
);
|
|
488
|
-
} catch {
|
|
560
|
+
} catch {
|
|
561
|
+
// Runtime inbox timeout reporting is advisory.
|
|
562
|
+
}
|
|
489
563
|
resolve({ action: "cancel" });
|
|
490
564
|
}, this._elicitationTimeoutMs);
|
|
491
565
|
timeout.unref?.();
|
|
@@ -707,6 +781,50 @@ export class MCPClient extends EventEmitter {
|
|
|
707
781
|
else this._reconnectors.delete(name);
|
|
708
782
|
}
|
|
709
783
|
|
|
784
|
+
async _connectionHeaders(name, config, transportKind) {
|
|
785
|
+
const staticHeaders = { ...(config?.headers || {}) };
|
|
786
|
+
if (!config?.headersHelper) return staticHeaders;
|
|
787
|
+
if (
|
|
788
|
+
!isHttpTransport(transportKind) &&
|
|
789
|
+
!isWebSocketTransport(transportKind)
|
|
790
|
+
) {
|
|
791
|
+
const error = new Error(
|
|
792
|
+
`MCP headersHelper is only supported by HTTP, SSE, and WebSocket servers (server "${name}")`,
|
|
793
|
+
);
|
|
794
|
+
error.code = "CC_MCP_HEADERS_HELPER_TRANSPORT_INVALID";
|
|
795
|
+
throw error;
|
|
796
|
+
}
|
|
797
|
+
const context = _deps.resolveMcpHeadersHelperContext(
|
|
798
|
+
{
|
|
799
|
+
...config,
|
|
800
|
+
serverName: name,
|
|
801
|
+
},
|
|
802
|
+
this._workspaceBinding
|
|
803
|
+
? { currentWorkspaceBinding: () => this._workspaceBinding }
|
|
804
|
+
: undefined,
|
|
805
|
+
);
|
|
806
|
+
const dynamicHeaders = await _deps.runMcpHeadersHelper({
|
|
807
|
+
command: config.headersHelper,
|
|
808
|
+
cwd: context.cwd,
|
|
809
|
+
pluginRoot: context.pluginRoot,
|
|
810
|
+
execution: context.execution,
|
|
811
|
+
serverName: name,
|
|
812
|
+
serverUrl: config.url,
|
|
813
|
+
});
|
|
814
|
+
return mergeMcpHeaders(staticHeaders, dynamicHeaders);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async _refreshConnectionHeaders(name, entry) {
|
|
818
|
+
if (!entry?.config?.headersHelper) return entry?.httpHeaders || {};
|
|
819
|
+
const headers = await this._connectionHeaders(
|
|
820
|
+
name,
|
|
821
|
+
entry.config,
|
|
822
|
+
entry.transportKind,
|
|
823
|
+
);
|
|
824
|
+
entry.httpHeaders = headers;
|
|
825
|
+
return headers;
|
|
826
|
+
}
|
|
827
|
+
|
|
710
828
|
/**
|
|
711
829
|
* Connect to an MCP server. Routes to stdio or HTTP transport based on
|
|
712
830
|
* `config.transport` / `config.url` (see `inferTransport`).
|
|
@@ -714,25 +832,37 @@ export class MCPClient extends EventEmitter {
|
|
|
714
832
|
* @param {string} name - Server name
|
|
715
833
|
* @param {object} config - { command?, args?, env?, url?, transport? }
|
|
716
834
|
*/
|
|
717
|
-
async connect(name, config) {
|
|
835
|
+
async connect(name, config, _authRetryUsed = false) {
|
|
718
836
|
if (this.servers.has(name)) {
|
|
719
837
|
throw new Error(`Server "${name}" already connected`);
|
|
720
838
|
}
|
|
721
839
|
|
|
722
840
|
const transportKind = inferTransport(config);
|
|
841
|
+
const sourceConfig = config;
|
|
842
|
+
const connectionHeaders = await this._connectionHeaders(
|
|
843
|
+
name,
|
|
844
|
+
sourceConfig,
|
|
845
|
+
transportKind,
|
|
846
|
+
);
|
|
847
|
+
config = { ...sourceConfig, headers: connectionHeaders };
|
|
723
848
|
const entry = {
|
|
724
|
-
config
|
|
849
|
+
// Keep the source config (static headers + helper command) so every
|
|
850
|
+
// reconnect can execute the helper afresh instead of reusing credentials.
|
|
851
|
+
config: sourceConfig,
|
|
725
852
|
transportKind,
|
|
726
853
|
state: ServerState.CONNECTING,
|
|
727
854
|
process: null,
|
|
855
|
+
socket: null,
|
|
728
856
|
httpUrl: null,
|
|
729
|
-
httpHeaders:
|
|
857
|
+
httpHeaders: connectionHeaders,
|
|
730
858
|
httpSessionId: null,
|
|
731
859
|
protocolVersion: null,
|
|
732
860
|
_httpMessageStream: null,
|
|
733
861
|
tools: [],
|
|
734
862
|
resources: [],
|
|
863
|
+
resourceTemplates: [],
|
|
735
864
|
prompts: [],
|
|
865
|
+
resourceSubscriptions: new Set(),
|
|
736
866
|
_pending: new Map(),
|
|
737
867
|
_buffer: "",
|
|
738
868
|
// Per-connection streaming decoders so a multi-byte UTF-8 character (e.g.
|
|
@@ -743,6 +873,23 @@ export class MCPClient extends EventEmitter {
|
|
|
743
873
|
_stderrDecoder: new TextDecoder("utf-8"),
|
|
744
874
|
};
|
|
745
875
|
|
|
876
|
+
const rejectPending = (error) => {
|
|
877
|
+
const cause =
|
|
878
|
+
error instanceof Error
|
|
879
|
+
? error
|
|
880
|
+
: new Error(String(error || "MCP transport closed"));
|
|
881
|
+
for (const [, pending] of entry._pending) {
|
|
882
|
+
if (pending.timeout) clearTimeout(pending.timeout);
|
|
883
|
+
try {
|
|
884
|
+
pending.reject(cause);
|
|
885
|
+
} catch {
|
|
886
|
+
// already settled
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
entry._pending.clear();
|
|
890
|
+
};
|
|
891
|
+
entry._rejectPending = rejectPending;
|
|
892
|
+
|
|
746
893
|
this.servers.set(name, entry);
|
|
747
894
|
|
|
748
895
|
try {
|
|
@@ -751,7 +898,168 @@ export class MCPClient extends EventEmitter {
|
|
|
751
898
|
throw new Error(`HTTP transport requires a url (server "${name}")`);
|
|
752
899
|
}
|
|
753
900
|
entry.httpUrl = config.url;
|
|
754
|
-
entry.httpHeaders =
|
|
901
|
+
entry.httpHeaders = connectionHeaders;
|
|
902
|
+
} else if (isWebSocketTransport(transportKind)) {
|
|
903
|
+
if (!config.url) {
|
|
904
|
+
throw mcpTransportError(
|
|
905
|
+
"CC_MCP_WS_URL_REQUIRED",
|
|
906
|
+
`WebSocket transport requires a url (server "${name}")`,
|
|
907
|
+
{ transport: transportKind },
|
|
908
|
+
);
|
|
909
|
+
}
|
|
910
|
+
let endpoint;
|
|
911
|
+
try {
|
|
912
|
+
endpoint = new URL(config.url);
|
|
913
|
+
} catch {
|
|
914
|
+
throw mcpTransportError(
|
|
915
|
+
"CC_MCP_WS_INVALID_URL",
|
|
916
|
+
`Invalid WebSocket URL for server "${name}": ${redactMcpUrl(config.url)}`,
|
|
917
|
+
{ transport: transportKind, url: config.url },
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
if (endpoint.protocol !== `${transportKind}:`) {
|
|
921
|
+
throw mcpTransportError(
|
|
922
|
+
"CC_MCP_WS_SCHEME_MISMATCH",
|
|
923
|
+
`MCP transport "${transportKind}" requires a ${transportKind}:// URL (server "${name}")`,
|
|
924
|
+
{ transport: transportKind, url: config.url },
|
|
925
|
+
);
|
|
926
|
+
}
|
|
927
|
+
if (endpoint.username || endpoint.password) {
|
|
928
|
+
throw mcpTransportError(
|
|
929
|
+
"CC_MCP_WS_URL_CREDENTIALS_FORBIDDEN",
|
|
930
|
+
`WebSocket credentials must use headers or OAuth (server "${name}")`,
|
|
931
|
+
{ transport: transportKind, url: config.url },
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const connectTimeoutMs = Number.isFinite(config.connectTimeoutMs)
|
|
936
|
+
? Math.max(1, config.connectTimeoutMs)
|
|
937
|
+
: WEBSOCKET_CONNECT_TIMEOUT_MS;
|
|
938
|
+
const maxPayload = Number.isFinite(config.maxPayloadBytes)
|
|
939
|
+
? Math.max(1024, config.maxPayloadBytes)
|
|
940
|
+
: WEBSOCKET_MAX_PAYLOAD_BYTES;
|
|
941
|
+
const Socket = _deps.WebSocket;
|
|
942
|
+
const socket = new Socket(config.url, {
|
|
943
|
+
headers: { ...(config.headers || {}) },
|
|
944
|
+
handshakeTimeout: connectTimeoutMs,
|
|
945
|
+
maxPayload,
|
|
946
|
+
followRedirects: config.followRedirects === true,
|
|
947
|
+
...(transportKind === "wss" && config.rejectUnauthorized === false
|
|
948
|
+
? { rejectUnauthorized: false }
|
|
949
|
+
: {}),
|
|
950
|
+
});
|
|
951
|
+
entry.socket = socket;
|
|
952
|
+
|
|
953
|
+
await new Promise((resolve, reject) => {
|
|
954
|
+
let settled = false;
|
|
955
|
+
const finish = (fn, value) => {
|
|
956
|
+
if (settled) return;
|
|
957
|
+
settled = true;
|
|
958
|
+
socket.off("open", onOpen);
|
|
959
|
+
socket.off("error", onError);
|
|
960
|
+
socket.off("unexpected-response", onUnexpectedResponse);
|
|
961
|
+
fn(value);
|
|
962
|
+
};
|
|
963
|
+
const onOpen = () => finish(resolve);
|
|
964
|
+
const onError = (cause) =>
|
|
965
|
+
finish(
|
|
966
|
+
reject,
|
|
967
|
+
mcpTransportError(
|
|
968
|
+
"CC_MCP_WS_CONNECT_FAILED",
|
|
969
|
+
`WebSocket connection failed for server "${name}": ${cause?.message || cause}`,
|
|
970
|
+
{ transport: transportKind, url: config.url },
|
|
971
|
+
),
|
|
972
|
+
);
|
|
973
|
+
const onUnexpectedResponse = (_request, response) =>
|
|
974
|
+
finish(
|
|
975
|
+
reject,
|
|
976
|
+
mcpTransportError(
|
|
977
|
+
"CC_MCP_WS_HANDSHAKE_REJECTED",
|
|
978
|
+
`WebSocket handshake failed for server "${name}": HTTP ${response?.statusCode || "unknown"}`,
|
|
979
|
+
{
|
|
980
|
+
transport: transportKind,
|
|
981
|
+
url: config.url,
|
|
982
|
+
status: response?.statusCode,
|
|
983
|
+
},
|
|
984
|
+
),
|
|
985
|
+
);
|
|
986
|
+
socket.once("open", onOpen);
|
|
987
|
+
socket.once("error", onError);
|
|
988
|
+
socket.once("unexpected-response", onUnexpectedResponse);
|
|
989
|
+
});
|
|
990
|
+
|
|
991
|
+
socket.on("message", (data, isBinary) => {
|
|
992
|
+
if (isBinary) {
|
|
993
|
+
const error = mcpTransportError(
|
|
994
|
+
"CC_MCP_WS_BINARY_MESSAGE",
|
|
995
|
+
`MCP server "${name}" sent a binary WebSocket message; JSON text is required`,
|
|
996
|
+
{ transport: transportKind, url: config.url },
|
|
997
|
+
);
|
|
998
|
+
entry.state = ServerState.ERROR;
|
|
999
|
+
rejectPending(error);
|
|
1000
|
+
this.emit("server-error", {
|
|
1001
|
+
name,
|
|
1002
|
+
error: error.message,
|
|
1003
|
+
code: error.code,
|
|
1004
|
+
});
|
|
1005
|
+
socket.close(1003, "JSON text required");
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
try {
|
|
1009
|
+
const message = JSON.parse(String(data));
|
|
1010
|
+
if (
|
|
1011
|
+
!message ||
|
|
1012
|
+
Array.isArray(message) ||
|
|
1013
|
+
typeof message !== "object"
|
|
1014
|
+
) {
|
|
1015
|
+
throw new Error("expected one JSON-RPC object");
|
|
1016
|
+
}
|
|
1017
|
+
this._handleMessage(name, message);
|
|
1018
|
+
} catch (cause) {
|
|
1019
|
+
const error = mcpTransportError(
|
|
1020
|
+
"CC_MCP_WS_INVALID_MESSAGE",
|
|
1021
|
+
`Invalid WebSocket JSON-RPC message: ${cause?.message || cause}`,
|
|
1022
|
+
{ transport: transportKind, url: config.url },
|
|
1023
|
+
);
|
|
1024
|
+
entry.state = ServerState.ERROR;
|
|
1025
|
+
rejectPending(error);
|
|
1026
|
+
this.emit("server-error", {
|
|
1027
|
+
name,
|
|
1028
|
+
code: error.code,
|
|
1029
|
+
error: error.message,
|
|
1030
|
+
});
|
|
1031
|
+
socket.close(1007, "Invalid JSON-RPC message");
|
|
1032
|
+
}
|
|
1033
|
+
});
|
|
1034
|
+
socket.on("close", (code, reason) => {
|
|
1035
|
+
entry.state = ServerState.DISCONNECTED;
|
|
1036
|
+
const suffix = reason?.length ? `: ${String(reason)}` : "";
|
|
1037
|
+
const error = mcpTransportError(
|
|
1038
|
+
"CC_MCP_WS_CLOSED",
|
|
1039
|
+
`MCP WebSocket server "${name}" disconnected (code ${code})${suffix}`,
|
|
1040
|
+
{ transport: transportKind, url: config.url, closeCode: code },
|
|
1041
|
+
);
|
|
1042
|
+
rejectPending(error);
|
|
1043
|
+
this.emit("server-disconnected", {
|
|
1044
|
+
name,
|
|
1045
|
+
code,
|
|
1046
|
+
reason: String(reason || ""),
|
|
1047
|
+
});
|
|
1048
|
+
});
|
|
1049
|
+
socket.on("error", (cause) => {
|
|
1050
|
+
entry.state = ServerState.ERROR;
|
|
1051
|
+
const error = mcpTransportError(
|
|
1052
|
+
"CC_MCP_WS_ERROR",
|
|
1053
|
+
`MCP WebSocket server "${name}" error: ${cause?.message || cause}`,
|
|
1054
|
+
{ transport: transportKind, url: config.url },
|
|
1055
|
+
);
|
|
1056
|
+
rejectPending(error);
|
|
1057
|
+
this.emit("server-error", {
|
|
1058
|
+
name,
|
|
1059
|
+
error: error.message,
|
|
1060
|
+
code: error.code,
|
|
1061
|
+
});
|
|
1062
|
+
});
|
|
755
1063
|
} else if (transportKind === "stdio") {
|
|
756
1064
|
if (!config.command) {
|
|
757
1065
|
throw new Error(
|
|
@@ -950,6 +1258,18 @@ export class MCPClient extends EventEmitter {
|
|
|
950
1258
|
// Server may not support resources
|
|
951
1259
|
}
|
|
952
1260
|
|
|
1261
|
+
// Resource templates are optional even when resources are supported.
|
|
1262
|
+
try {
|
|
1263
|
+
const templatesResult = await this._sendRequest(
|
|
1264
|
+
name,
|
|
1265
|
+
"resources/templates/list",
|
|
1266
|
+
{},
|
|
1267
|
+
);
|
|
1268
|
+
entry.resourceTemplates = templatesResult?.resourceTemplates || [];
|
|
1269
|
+
} catch {
|
|
1270
|
+
// Server may not support resource templates.
|
|
1271
|
+
}
|
|
1272
|
+
|
|
953
1273
|
// Fetch available prompts (server-provided slash commands)
|
|
954
1274
|
try {
|
|
955
1275
|
const promptsResult = await this._sendRequest(name, "prompts/list", {});
|
|
@@ -975,6 +1295,7 @@ export class MCPClient extends EventEmitter {
|
|
|
975
1295
|
tools: entry.tools,
|
|
976
1296
|
toolsError: entry.toolsError,
|
|
977
1297
|
resources: entry.resources,
|
|
1298
|
+
resourceTemplates: entry.resourceTemplates,
|
|
978
1299
|
prompts: entry.prompts,
|
|
979
1300
|
serverInfo: entry.serverInfo,
|
|
980
1301
|
instructions: entry.instructions,
|
|
@@ -999,7 +1320,29 @@ export class MCPClient extends EventEmitter {
|
|
|
999
1320
|
// teardown is best-effort — the connect error is what matters
|
|
1000
1321
|
}
|
|
1001
1322
|
}
|
|
1323
|
+
if (entry.socket) {
|
|
1324
|
+
try {
|
|
1325
|
+
entry.socket.removeAllListeners();
|
|
1326
|
+
// ws emits an asynchronous error when terminate() races a rejected
|
|
1327
|
+
// opening handshake. Keep a sink installed after removing the
|
|
1328
|
+
// connection listeners so teardown cannot become an uncaught error.
|
|
1329
|
+
entry.socket.on?.("error", () => {});
|
|
1330
|
+
entry.socket.terminate?.();
|
|
1331
|
+
} catch {
|
|
1332
|
+
// teardown is best-effort
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1002
1335
|
this.servers.delete(name);
|
|
1336
|
+
if (
|
|
1337
|
+
!_authRetryUsed &&
|
|
1338
|
+
sourceConfig.headersHelper &&
|
|
1339
|
+
isMcpAuthenticationError(err)
|
|
1340
|
+
) {
|
|
1341
|
+
// A helper is intentionally uncached. One authentication rejection may
|
|
1342
|
+
// mean its short-lived token expired between generation and initialize;
|
|
1343
|
+
// execute it once more and retry the connection exactly once.
|
|
1344
|
+
return this.connect(name, sourceConfig, true);
|
|
1345
|
+
}
|
|
1003
1346
|
throw err;
|
|
1004
1347
|
}
|
|
1005
1348
|
}
|
|
@@ -1020,6 +1363,20 @@ export class MCPClient extends EventEmitter {
|
|
|
1020
1363
|
if (entry.process) {
|
|
1021
1364
|
entry.process.kill();
|
|
1022
1365
|
}
|
|
1366
|
+
if (entry.socket) {
|
|
1367
|
+
entry._rejectPending?.(
|
|
1368
|
+
mcpTransportError(
|
|
1369
|
+
"CC_MCP_WS_CLIENT_DISCONNECT",
|
|
1370
|
+
`MCP WebSocket server "${name}" disconnected by client`,
|
|
1371
|
+
{ transport: entry.transportKind, url: entry.config?.url },
|
|
1372
|
+
),
|
|
1373
|
+
);
|
|
1374
|
+
try {
|
|
1375
|
+
entry.socket.close(1000, "client disconnect");
|
|
1376
|
+
} catch {
|
|
1377
|
+
entry.socket.terminate?.();
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1023
1380
|
if (entry._httpMessageStream) {
|
|
1024
1381
|
entry._httpMessageStream.stopped = true;
|
|
1025
1382
|
entry._httpMessageStream.controller?.abort();
|
|
@@ -1038,7 +1395,7 @@ export class MCPClient extends EventEmitter {
|
|
|
1038
1395
|
entry.protocolVersion || this._protocolVersion,
|
|
1039
1396
|
},
|
|
1040
1397
|
});
|
|
1041
|
-
} catch
|
|
1398
|
+
} catch {
|
|
1042
1399
|
// ignore — disconnect is best-effort
|
|
1043
1400
|
}
|
|
1044
1401
|
}
|
|
@@ -1070,6 +1427,7 @@ export class MCPClient extends EventEmitter {
|
|
|
1070
1427
|
tools: entry.tools.length,
|
|
1071
1428
|
toolsError: entry.toolsError || null,
|
|
1072
1429
|
resources: entry.resources.length,
|
|
1430
|
+
resourceTemplates: (entry.resourceTemplates || []).length,
|
|
1073
1431
|
prompts: (entry.prompts || []).length,
|
|
1074
1432
|
serverInfo: entry.serverInfo || {},
|
|
1075
1433
|
});
|
|
@@ -1117,12 +1475,20 @@ export class MCPClient extends EventEmitter {
|
|
|
1117
1475
|
return this._callToolOnce(serverName, toolName, args);
|
|
1118
1476
|
}
|
|
1119
1477
|
if (
|
|
1120
|
-
!this._reconnectors.has(serverName)
|
|
1121
|
-
!
|
|
1478
|
+
!this._reconnectors.has(serverName) &&
|
|
1479
|
+
!this.servers.get(serverName)?.config?.headersHelper
|
|
1122
1480
|
) {
|
|
1123
1481
|
throw err;
|
|
1124
1482
|
}
|
|
1125
|
-
|
|
1483
|
+
if (!isLikelyConnectionError(err)) {
|
|
1484
|
+
throw err;
|
|
1485
|
+
}
|
|
1486
|
+
const reconnected = await this._tryReconnect(serverName, {
|
|
1487
|
+
// The high-level tool operation owns the one reconnect/retry budget.
|
|
1488
|
+
// Its reconnect initialize must not recursively mint another auth
|
|
1489
|
+
// retry when the refreshed credential is also rejected.
|
|
1490
|
+
connectAuthRetryUsed: true,
|
|
1491
|
+
});
|
|
1126
1492
|
if (!reconnected) throw err;
|
|
1127
1493
|
return await this._callToolOnce(serverName, toolName, args);
|
|
1128
1494
|
}
|
|
@@ -1194,19 +1560,21 @@ export class MCPClient extends EventEmitter {
|
|
|
1194
1560
|
* parallel — a double connect would throw "already connected").
|
|
1195
1561
|
* Resolves true on success, false on any failure (original error wins).
|
|
1196
1562
|
*/
|
|
1197
|
-
_tryReconnect(name) {
|
|
1563
|
+
_tryReconnect(name, options = {}) {
|
|
1198
1564
|
const inFlight = this._reconnecting.get(name);
|
|
1199
1565
|
if (inFlight) return inFlight;
|
|
1200
1566
|
const p = (async () => {
|
|
1201
1567
|
try {
|
|
1202
|
-
const
|
|
1568
|
+
const currentConfig = this.servers.get(name)?.config || null;
|
|
1569
|
+
const resolver = this._reconnectors.get(name);
|
|
1570
|
+
const fresh = resolver ? await resolver() : currentConfig;
|
|
1203
1571
|
if (!fresh) return false;
|
|
1204
1572
|
try {
|
|
1205
1573
|
await this.disconnect(name);
|
|
1206
1574
|
} catch {
|
|
1207
1575
|
// entry may already be gone — connect() below is what matters
|
|
1208
1576
|
}
|
|
1209
|
-
await this.connect(name, fresh);
|
|
1577
|
+
await this.connect(name, fresh, options.connectAuthRetryUsed === true);
|
|
1210
1578
|
this.emit("server-reconnected", { name, url: fresh.url || null });
|
|
1211
1579
|
return true;
|
|
1212
1580
|
} catch {
|
|
@@ -1252,6 +1620,73 @@ export class MCPClient extends EventEmitter {
|
|
|
1252
1620
|
return result;
|
|
1253
1621
|
}
|
|
1254
1622
|
|
|
1623
|
+
/** List optional URI templates advertised by one server or all servers. */
|
|
1624
|
+
listResourceTemplates(serverName) {
|
|
1625
|
+
if (serverName) {
|
|
1626
|
+
const entry = this.servers.get(serverName);
|
|
1627
|
+
if (!entry) throw new Error(`Server "${serverName}" not found`);
|
|
1628
|
+
return (entry.resourceTemplates || []).map((template) => ({
|
|
1629
|
+
...template,
|
|
1630
|
+
server: serverName,
|
|
1631
|
+
}));
|
|
1632
|
+
}
|
|
1633
|
+
const all = [];
|
|
1634
|
+
for (const [name, entry] of this.servers) {
|
|
1635
|
+
for (const template of entry.resourceTemplates || []) {
|
|
1636
|
+
all.push({ ...template, server: name });
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
return all;
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
/** Subscribe to change notifications for a concrete resource URI. */
|
|
1643
|
+
async subscribeResource(serverName, uri) {
|
|
1644
|
+
const entry = this.servers.get(serverName);
|
|
1645
|
+
if (!entry) throw new Error(`Server "${serverName}" not found`);
|
|
1646
|
+
await this._sendRequest(serverName, "resources/subscribe", { uri });
|
|
1647
|
+
entry.resourceSubscriptions ||= new Set();
|
|
1648
|
+
entry.resourceSubscriptions.add(String(uri));
|
|
1649
|
+
return true;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
/** Remove a previously established resource subscription. */
|
|
1653
|
+
async unsubscribeResource(serverName, uri) {
|
|
1654
|
+
const entry = this.servers.get(serverName);
|
|
1655
|
+
if (!entry) throw new Error(`Server "${serverName}" not found`);
|
|
1656
|
+
await this._sendRequest(serverName, "resources/unsubscribe", { uri });
|
|
1657
|
+
entry.resourceSubscriptions?.delete(String(uri));
|
|
1658
|
+
return true;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
/** Set the optional server-to-client logging verbosity. */
|
|
1662
|
+
async setLoggingLevel(serverName, level) {
|
|
1663
|
+
const normalized = String(level || "").toLowerCase();
|
|
1664
|
+
const levels = new Set([
|
|
1665
|
+
"debug",
|
|
1666
|
+
"info",
|
|
1667
|
+
"notice",
|
|
1668
|
+
"warning",
|
|
1669
|
+
"error",
|
|
1670
|
+
"critical",
|
|
1671
|
+
"alert",
|
|
1672
|
+
"emergency",
|
|
1673
|
+
]);
|
|
1674
|
+
if (!levels.has(normalized)) {
|
|
1675
|
+
throw new Error(`Invalid MCP logging level: ${level}`);
|
|
1676
|
+
}
|
|
1677
|
+
await this._sendRequest(serverName, "logging/setLevel", {
|
|
1678
|
+
level: normalized,
|
|
1679
|
+
});
|
|
1680
|
+
return normalized;
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1683
|
+
/** Ask a server for optional argument/ref completion candidates. */
|
|
1684
|
+
async complete(serverName, ref, argument, context = undefined) {
|
|
1685
|
+
const params = { ref, argument };
|
|
1686
|
+
if (context !== undefined) params.context = context;
|
|
1687
|
+
return this._sendRequest(serverName, "completion/complete", params);
|
|
1688
|
+
}
|
|
1689
|
+
|
|
1255
1690
|
/**
|
|
1256
1691
|
* List prompts from a specific server or all servers. Each prompt is
|
|
1257
1692
|
* annotated with its owning `server` (mirrors `listTools`).
|
|
@@ -1329,6 +1764,10 @@ export class MCPClient extends EventEmitter {
|
|
|
1329
1764
|
return this._sendHttpRequest(serverName, method, params);
|
|
1330
1765
|
}
|
|
1331
1766
|
|
|
1767
|
+
if (entry.socket) {
|
|
1768
|
+
return this._sendWebSocketRequest(serverName, method, params);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1332
1771
|
return new Promise((resolve, reject) => {
|
|
1333
1772
|
if (!entry.process) {
|
|
1334
1773
|
return reject(new Error("Server not available"));
|
|
@@ -1360,8 +1799,15 @@ export class MCPClient extends EventEmitter {
|
|
|
1360
1799
|
let timeout = null;
|
|
1361
1800
|
if (!longRunning && timeoutMs > 0) {
|
|
1362
1801
|
timeout = setTimeout(() => {
|
|
1363
|
-
entry._pending.delete(id);
|
|
1364
|
-
|
|
1802
|
+
if (!entry._pending.delete(id)) return;
|
|
1803
|
+
const timeoutError = new Error(`Request timeout: ${method}`);
|
|
1804
|
+
this._sendTimeoutCancellation(
|
|
1805
|
+
serverName,
|
|
1806
|
+
method,
|
|
1807
|
+
id,
|
|
1808
|
+
timeoutError.message,
|
|
1809
|
+
);
|
|
1810
|
+
reject(timeoutError);
|
|
1365
1811
|
}, timeoutMs);
|
|
1366
1812
|
entry._pending.get(id).timeout = timeout;
|
|
1367
1813
|
}
|
|
@@ -1376,6 +1822,73 @@ export class MCPClient extends EventEmitter {
|
|
|
1376
1822
|
});
|
|
1377
1823
|
}
|
|
1378
1824
|
|
|
1825
|
+
_sendWebSocketRequest(serverName, method, params) {
|
|
1826
|
+
const entry = this.servers.get(serverName);
|
|
1827
|
+
if (!entry?.socket || entry.socket.readyState !== 1) {
|
|
1828
|
+
return Promise.reject(
|
|
1829
|
+
mcpTransportError(
|
|
1830
|
+
"CC_MCP_WS_NOT_CONNECTED",
|
|
1831
|
+
`MCP WebSocket server "${serverName}" is not connected`,
|
|
1832
|
+
{ transport: entry?.transportKind, url: entry?.config?.url },
|
|
1833
|
+
),
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
return new Promise((resolve, reject) => {
|
|
1838
|
+
const id = this._nextId++;
|
|
1839
|
+
const message = JSON.stringify({ jsonrpc: "2.0", id, method, params });
|
|
1840
|
+
const longRunning = Boolean(entry.config?.longRunning);
|
|
1841
|
+
const timeoutMs = Number.isFinite(entry.config?.requestTimeoutMs)
|
|
1842
|
+
? entry.config.requestTimeoutMs
|
|
1843
|
+
: STDIO_REQUEST_TIMEOUT_MS;
|
|
1844
|
+
let timeout = null;
|
|
1845
|
+
if (!longRunning && timeoutMs > 0) {
|
|
1846
|
+
timeout = setTimeout(() => {
|
|
1847
|
+
if (!entry._pending.delete(id)) return;
|
|
1848
|
+
const timeoutError = mcpTransportError(
|
|
1849
|
+
"CC_MCP_WS_REQUEST_TIMEOUT",
|
|
1850
|
+
`Request timeout: ${method} (WebSocket, no response in ${timeoutMs}ms)`,
|
|
1851
|
+
{ transport: entry.transportKind, url: entry.config?.url },
|
|
1852
|
+
);
|
|
1853
|
+
this._sendTimeoutCancellation(
|
|
1854
|
+
serverName,
|
|
1855
|
+
method,
|
|
1856
|
+
id,
|
|
1857
|
+
timeoutError.message,
|
|
1858
|
+
);
|
|
1859
|
+
reject(timeoutError);
|
|
1860
|
+
}, timeoutMs);
|
|
1861
|
+
}
|
|
1862
|
+
entry._pending.set(id, { resolve, reject, timeout });
|
|
1863
|
+
entry.socket.send(message, (cause) => {
|
|
1864
|
+
if (!cause) return;
|
|
1865
|
+
if (timeout) clearTimeout(timeout);
|
|
1866
|
+
entry._pending.delete(id);
|
|
1867
|
+
reject(
|
|
1868
|
+
mcpTransportError(
|
|
1869
|
+
"CC_MCP_WS_SEND_FAILED",
|
|
1870
|
+
`WebSocket send failed for ${method}: ${cause.message}`,
|
|
1871
|
+
{ transport: entry.transportKind, url: entry.config?.url },
|
|
1872
|
+
),
|
|
1873
|
+
);
|
|
1874
|
+
});
|
|
1875
|
+
});
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
_sendTimeoutCancellation(serverName, method, requestId, reason) {
|
|
1879
|
+
// The initialize handshake establishes the protocol/session needed for
|
|
1880
|
+
// later notifications, so it cannot itself be cancelled safely.
|
|
1881
|
+
if (method === "initialize") return;
|
|
1882
|
+
try {
|
|
1883
|
+
this._sendNotification(serverName, "notifications/cancelled", {
|
|
1884
|
+
requestId,
|
|
1885
|
+
reason,
|
|
1886
|
+
});
|
|
1887
|
+
} catch {
|
|
1888
|
+
// Cancellation is best-effort and must never replace the timeout error.
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1379
1892
|
_sendNotification(serverName, method, params) {
|
|
1380
1893
|
const entry = this.servers.get(serverName);
|
|
1381
1894
|
if (!entry) return;
|
|
@@ -1386,6 +1899,16 @@ export class MCPClient extends EventEmitter {
|
|
|
1386
1899
|
return;
|
|
1387
1900
|
}
|
|
1388
1901
|
|
|
1902
|
+
if (entry.socket) {
|
|
1903
|
+
if (entry.socket.readyState === 1) {
|
|
1904
|
+
entry.socket.send(
|
|
1905
|
+
JSON.stringify({ jsonrpc: "2.0", method, params }),
|
|
1906
|
+
() => {},
|
|
1907
|
+
);
|
|
1908
|
+
}
|
|
1909
|
+
return;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1389
1912
|
if (!entry.process) return;
|
|
1390
1913
|
|
|
1391
1914
|
const message = JSON.stringify({
|
|
@@ -1420,6 +1943,7 @@ export class MCPClient extends EventEmitter {
|
|
|
1420
1943
|
controller: null,
|
|
1421
1944
|
lastEventId: null,
|
|
1422
1945
|
retryMs: 1000,
|
|
1946
|
+
authFailures: 0,
|
|
1423
1947
|
wake: null,
|
|
1424
1948
|
promise: null,
|
|
1425
1949
|
};
|
|
@@ -1431,19 +1955,22 @@ export class MCPClient extends EventEmitter {
|
|
|
1431
1955
|
typeof AbortController === "function"
|
|
1432
1956
|
? new AbortController()
|
|
1433
1957
|
: null;
|
|
1434
|
-
const headers = {
|
|
1435
|
-
Accept: "text/event-stream",
|
|
1436
|
-
...(entry.httpHeaders || {}),
|
|
1437
|
-
...(entry.httpSessionId
|
|
1438
|
-
? { "Mcp-Session-Id": entry.httpSessionId }
|
|
1439
|
-
: {}),
|
|
1440
|
-
"MCP-Protocol-Version":
|
|
1441
|
-
entry.protocolVersion || this._protocolVersion,
|
|
1442
|
-
...(stream.lastEventId
|
|
1443
|
-
? { "Last-Event-ID": stream.lastEventId }
|
|
1444
|
-
: {}),
|
|
1445
|
-
};
|
|
1446
1958
|
try {
|
|
1959
|
+
const resolvedHeaders = entry.config?.headersHelper
|
|
1960
|
+
? await this._refreshConnectionHeaders(serverName, entry)
|
|
1961
|
+
: entry.httpHeaders || {};
|
|
1962
|
+
const headers = {
|
|
1963
|
+
Accept: "text/event-stream",
|
|
1964
|
+
...resolvedHeaders,
|
|
1965
|
+
...(entry.httpSessionId
|
|
1966
|
+
? { "Mcp-Session-Id": entry.httpSessionId }
|
|
1967
|
+
: {}),
|
|
1968
|
+
"MCP-Protocol-Version":
|
|
1969
|
+
entry.protocolVersion || this._protocolVersion,
|
|
1970
|
+
...(stream.lastEventId
|
|
1971
|
+
? { "Last-Event-ID": stream.lastEventId }
|
|
1972
|
+
: {}),
|
|
1973
|
+
};
|
|
1447
1974
|
const response = await _deps.fetch(entry.httpUrl, {
|
|
1448
1975
|
method: "GET",
|
|
1449
1976
|
headers,
|
|
@@ -1452,11 +1979,24 @@ export class MCPClient extends EventEmitter {
|
|
|
1452
1979
|
: {}),
|
|
1453
1980
|
});
|
|
1454
1981
|
if (response.status === 405) return;
|
|
1982
|
+
if (response.status === 401 || response.status === 403) {
|
|
1983
|
+
if (entry.config?.headersHelper && stream.authFailures === 0) {
|
|
1984
|
+
stream.authFailures = 1;
|
|
1985
|
+
continue;
|
|
1986
|
+
}
|
|
1987
|
+
this.emit("server-stream-error", {
|
|
1988
|
+
name: serverName,
|
|
1989
|
+
code: "CC_MCP_AUTH_RETRY_EXHAUSTED",
|
|
1990
|
+
error: `MCP HTTP message stream authentication failed (HTTP ${response.status})`,
|
|
1991
|
+
});
|
|
1992
|
+
return;
|
|
1993
|
+
}
|
|
1455
1994
|
if (!response.ok) {
|
|
1456
1995
|
throw new Error(
|
|
1457
1996
|
`MCP HTTP message stream returned ${response.status}`,
|
|
1458
1997
|
);
|
|
1459
1998
|
}
|
|
1999
|
+
stream.authFailures = 0;
|
|
1460
2000
|
const contentType = response.headers?.get
|
|
1461
2001
|
? String(response.headers.get("content-type") || "").toLowerCase()
|
|
1462
2002
|
: "";
|
|
@@ -1475,6 +2015,9 @@ export class MCPClient extends EventEmitter {
|
|
|
1475
2015
|
name: serverName,
|
|
1476
2016
|
error: error?.message || String(error),
|
|
1477
2017
|
});
|
|
2018
|
+
if (String(error?.code || "").startsWith("CC_MCP_HEADERS_HELPER")) {
|
|
2019
|
+
return;
|
|
2020
|
+
}
|
|
1478
2021
|
} finally {
|
|
1479
2022
|
stream.controller = null;
|
|
1480
2023
|
}
|
|
@@ -1539,13 +2082,17 @@ export class MCPClient extends EventEmitter {
|
|
|
1539
2082
|
: HTTP_REQUEST_TIMEOUT_MS;
|
|
1540
2083
|
let controller = null;
|
|
1541
2084
|
let timer = null;
|
|
2085
|
+
const timeoutMessage = `Request timeout: ${method} (HTTP, no response in ${timeoutMs}ms)`;
|
|
1542
2086
|
if (
|
|
1543
2087
|
!longRunning &&
|
|
1544
2088
|
timeoutMs > 0 &&
|
|
1545
2089
|
typeof AbortController === "function"
|
|
1546
2090
|
) {
|
|
1547
2091
|
controller = new AbortController();
|
|
1548
|
-
timer = setTimeout(() =>
|
|
2092
|
+
timer = setTimeout(() => {
|
|
2093
|
+
controller.abort();
|
|
2094
|
+
this._sendTimeoutCancellation(serverName, method, id, timeoutMessage);
|
|
2095
|
+
}, timeoutMs);
|
|
1549
2096
|
}
|
|
1550
2097
|
|
|
1551
2098
|
try {
|
|
@@ -1567,18 +2114,37 @@ export class MCPClient extends EventEmitter {
|
|
|
1567
2114
|
}
|
|
1568
2115
|
|
|
1569
2116
|
if (!response.ok) {
|
|
1570
|
-
|
|
1571
|
-
|
|
2117
|
+
// Authentication bodies frequently echo token/debug material. When a
|
|
2118
|
+
// dynamic helper is configured, any error body may reflect its opaque
|
|
2119
|
+
// header values; structured HTTP status is sufficient and cannot leak
|
|
2120
|
+
// freshly generated credentials into logs or exceptions.
|
|
2121
|
+
const suppressBody =
|
|
2122
|
+
Boolean(entry.config?.headersHelper) ||
|
|
2123
|
+
response.status === 401 ||
|
|
2124
|
+
response.status === 403;
|
|
2125
|
+
const text = suppressBody
|
|
2126
|
+
? ""
|
|
2127
|
+
: typeof response.text === "function"
|
|
2128
|
+
? await response.text()
|
|
2129
|
+
: "";
|
|
1572
2130
|
const detail = text ? `: ${text.slice(0, 200)}` : "";
|
|
1573
2131
|
// 404 usually means a wrong/stale server URL — name it and point at the
|
|
1574
2132
|
// MCP config (Claude-Code 2.1.191: "HTTP 404 errors now show the URL and
|
|
1575
2133
|
// point to your MCP config") instead of a bare "HTTP 404".
|
|
1576
2134
|
if (response.status === 404) {
|
|
1577
2135
|
throw new Error(
|
|
1578
|
-
`HTTP 404${detail} — ${entry.httpUrl} returned Not Found; check this server's "url" in your MCP config`,
|
|
2136
|
+
`HTTP 404${detail} — ${redactMcpUrl(entry.httpUrl)} returned Not Found; check this server's "url" in your MCP config`,
|
|
1579
2137
|
);
|
|
1580
2138
|
}
|
|
1581
|
-
throw
|
|
2139
|
+
throw mcpTransportError(
|
|
2140
|
+
"CC_MCP_HTTP_STATUS",
|
|
2141
|
+
`HTTP ${response.status}${detail}`,
|
|
2142
|
+
{
|
|
2143
|
+
transport: entry.transportKind,
|
|
2144
|
+
url: entry.config?.url,
|
|
2145
|
+
status: response.status,
|
|
2146
|
+
},
|
|
2147
|
+
);
|
|
1582
2148
|
}
|
|
1583
2149
|
|
|
1584
2150
|
const contentType = response.headers?.get
|
|
@@ -1609,9 +2175,7 @@ export class MCPClient extends EventEmitter {
|
|
|
1609
2175
|
return envelope.result;
|
|
1610
2176
|
} catch (err) {
|
|
1611
2177
|
if (controller && controller.signal.aborted) {
|
|
1612
|
-
throw new Error(
|
|
1613
|
-
`Request timeout: ${method} (HTTP, no response in ${timeoutMs}ms)`,
|
|
1614
|
-
);
|
|
2178
|
+
throw new Error(timeoutMessage);
|
|
1615
2179
|
}
|
|
1616
2180
|
throw err;
|
|
1617
2181
|
} finally {
|
|
@@ -1641,7 +2205,7 @@ export class MCPClient extends EventEmitter {
|
|
|
1641
2205
|
body,
|
|
1642
2206
|
});
|
|
1643
2207
|
if (p && typeof p.catch === "function") p.catch(() => {});
|
|
1644
|
-
} catch
|
|
2208
|
+
} catch {
|
|
1645
2209
|
// ignore
|
|
1646
2210
|
}
|
|
1647
2211
|
}
|
|
@@ -1739,6 +2303,20 @@ export class MCPClient extends EventEmitter {
|
|
|
1739
2303
|
this._refreshServerList(serverName, "tools");
|
|
1740
2304
|
} else if (msg.method === "notifications/resources/list_changed") {
|
|
1741
2305
|
this._refreshServerList(serverName, "resources");
|
|
2306
|
+
this._refreshServerList(serverName, "resourceTemplates");
|
|
2307
|
+
} else if (msg.method === "notifications/resources/updated") {
|
|
2308
|
+
this.emit("resource-updated", {
|
|
2309
|
+
server: serverName,
|
|
2310
|
+
uri: msg.params?.uri || null,
|
|
2311
|
+
params: msg.params || {},
|
|
2312
|
+
});
|
|
2313
|
+
} else if (msg.method === "notifications/message") {
|
|
2314
|
+
this.emit("log-message", {
|
|
2315
|
+
server: serverName,
|
|
2316
|
+
level: msg.params?.level || "info",
|
|
2317
|
+
logger: msg.params?.logger || null,
|
|
2318
|
+
data: msg.params?.data,
|
|
2319
|
+
});
|
|
1742
2320
|
} else if (msg.method === "notifications/elicitation/complete") {
|
|
1743
2321
|
this._handleElicitationComplete(serverName, msg.params || {});
|
|
1744
2322
|
}
|
|
@@ -1770,10 +2348,19 @@ export class MCPClient extends EventEmitter {
|
|
|
1770
2348
|
do {
|
|
1771
2349
|
flags[`${kind}Dirty`] = false;
|
|
1772
2350
|
try {
|
|
1773
|
-
const method =
|
|
2351
|
+
const method =
|
|
2352
|
+
kind === "tools"
|
|
2353
|
+
? "tools/list"
|
|
2354
|
+
: kind === "resourceTemplates"
|
|
2355
|
+
? "resources/templates/list"
|
|
2356
|
+
: "resources/list";
|
|
1774
2357
|
const result = await this._sendRequest(serverName, method, {});
|
|
1775
2358
|
const list =
|
|
1776
|
-
(kind === "tools"
|
|
2359
|
+
(kind === "tools"
|
|
2360
|
+
? result?.tools
|
|
2361
|
+
: kind === "resourceTemplates"
|
|
2362
|
+
? result?.resourceTemplates
|
|
2363
|
+
: result?.resources) || [];
|
|
1777
2364
|
entry[kind] = list;
|
|
1778
2365
|
if (kind === "tools") entry.toolsError = null;
|
|
1779
2366
|
this.emit(`${kind}-changed`, {
|
|
@@ -1860,6 +2447,12 @@ export class MCPClient extends EventEmitter {
|
|
|
1860
2447
|
}
|
|
1861
2448
|
return;
|
|
1862
2449
|
}
|
|
2450
|
+
if (entry.socket) {
|
|
2451
|
+
if (entry.socket.readyState === 1) {
|
|
2452
|
+
entry.socket.send(JSON.stringify(envelope), () => {});
|
|
2453
|
+
}
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
1863
2456
|
if (!entry.process) return;
|
|
1864
2457
|
const message = JSON.stringify(envelope);
|
|
1865
2458
|
try {
|
|
@@ -2054,6 +2647,12 @@ export class MCPServerConfig {
|
|
|
2054
2647
|
auto_connect INTEGER DEFAULT 0,
|
|
2055
2648
|
url TEXT,
|
|
2056
2649
|
transport TEXT DEFAULT 'stdio',
|
|
2650
|
+
headers TEXT DEFAULT '{}',
|
|
2651
|
+
headers_helper TEXT,
|
|
2652
|
+
config_scope TEXT DEFAULT 'user',
|
|
2653
|
+
config_source TEXT,
|
|
2654
|
+
project_path TEXT,
|
|
2655
|
+
display_name TEXT,
|
|
2057
2656
|
created_at TEXT DEFAULT (datetime('now')),
|
|
2058
2657
|
updated_at TEXT DEFAULT (datetime('now'))
|
|
2059
2658
|
)
|
|
@@ -2081,7 +2680,29 @@ export class MCPServerConfig {
|
|
|
2081
2680
|
"ALTER TABLE mcp_servers ADD COLUMN transport TEXT DEFAULT 'stdio'",
|
|
2082
2681
|
);
|
|
2083
2682
|
}
|
|
2084
|
-
|
|
2683
|
+
if (!cols.has("headers")) {
|
|
2684
|
+
this.db.exec(
|
|
2685
|
+
"ALTER TABLE mcp_servers ADD COLUMN headers TEXT DEFAULT '{}'",
|
|
2686
|
+
);
|
|
2687
|
+
}
|
|
2688
|
+
if (!cols.has("headers_helper")) {
|
|
2689
|
+
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN headers_helper TEXT");
|
|
2690
|
+
}
|
|
2691
|
+
if (!cols.has("config_scope")) {
|
|
2692
|
+
this.db.exec(
|
|
2693
|
+
"ALTER TABLE mcp_servers ADD COLUMN config_scope TEXT DEFAULT 'user'",
|
|
2694
|
+
);
|
|
2695
|
+
}
|
|
2696
|
+
if (!cols.has("config_source")) {
|
|
2697
|
+
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN config_source TEXT");
|
|
2698
|
+
}
|
|
2699
|
+
if (!cols.has("project_path")) {
|
|
2700
|
+
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN project_path TEXT");
|
|
2701
|
+
}
|
|
2702
|
+
if (!cols.has("display_name")) {
|
|
2703
|
+
this.db.exec("ALTER TABLE mcp_servers ADD COLUMN display_name TEXT");
|
|
2704
|
+
}
|
|
2705
|
+
} catch {
|
|
2085
2706
|
// Best-effort; non-SQLite mocks silently skip.
|
|
2086
2707
|
}
|
|
2087
2708
|
}
|
|
@@ -2093,31 +2714,81 @@ export class MCPServerConfig {
|
|
|
2093
2714
|
if (!url && !config.command) {
|
|
2094
2715
|
throw new Error("MCP server config requires either command or url");
|
|
2095
2716
|
}
|
|
2717
|
+
const configScope = normalizeMcpConfigScope(config.configScope || "user");
|
|
2718
|
+
if (configScope === "managed" && config.allowManagedWrite !== true) {
|
|
2719
|
+
throw new Error(
|
|
2720
|
+
"Managed MCP configuration is read-only; provision it through managed settings",
|
|
2721
|
+
);
|
|
2722
|
+
}
|
|
2723
|
+
const projectPath =
|
|
2724
|
+
configScope === "local" || configScope === "project"
|
|
2725
|
+
? String(config.projectPath || process.cwd())
|
|
2726
|
+
: null;
|
|
2727
|
+
const configSource =
|
|
2728
|
+
config.configSource ||
|
|
2729
|
+
(configScope === "managed"
|
|
2730
|
+
? "managed-settings"
|
|
2731
|
+
: configScope === "user"
|
|
2732
|
+
? "user-database"
|
|
2733
|
+
: `${configScope}:${projectPath}`);
|
|
2734
|
+
const headersHelper =
|
|
2735
|
+
typeof config.headersHelper === "string" && config.headersHelper.trim()
|
|
2736
|
+
? config.headersHelper
|
|
2737
|
+
: null;
|
|
2738
|
+
// The legacy table uses `name` as its primary key. Encode workspace-bound
|
|
2739
|
+
// identities in that key so `foo` can coexist at local/project/user scope
|
|
2740
|
+
// while retaining the old key for user rows and backwards compatibility.
|
|
2741
|
+
const storageName =
|
|
2742
|
+
configScope === "user"
|
|
2743
|
+
? name
|
|
2744
|
+
: `${configScope}:${Buffer.from(projectPath || "managed", "utf8").toString("base64url")}:${name}`;
|
|
2096
2745
|
this.db
|
|
2097
2746
|
.prepare(
|
|
2098
|
-
"INSERT OR REPLACE INTO mcp_servers (name, command, args, env, auto_connect, url, transport, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
2747
|
+
"INSERT OR REPLACE INTO mcp_servers (name, command, args, env, auto_connect, url, transport, headers, headers_helper, config_scope, config_source, project_path, display_name, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))",
|
|
2099
2748
|
)
|
|
2100
2749
|
.run(
|
|
2101
|
-
|
|
2750
|
+
storageName,
|
|
2102
2751
|
config.command || null,
|
|
2103
2752
|
JSON.stringify(config.args || []),
|
|
2104
2753
|
JSON.stringify(config.env || {}),
|
|
2105
2754
|
config.autoConnect ? 1 : 0,
|
|
2106
2755
|
url,
|
|
2107
2756
|
transport,
|
|
2757
|
+
JSON.stringify(config.headers || {}),
|
|
2758
|
+
headersHelper,
|
|
2759
|
+
configScope,
|
|
2760
|
+
configSource,
|
|
2761
|
+
projectPath,
|
|
2762
|
+
name,
|
|
2108
2763
|
);
|
|
2109
2764
|
}
|
|
2110
2765
|
|
|
2111
|
-
remove(name) {
|
|
2766
|
+
remove(name, options = {}) {
|
|
2767
|
+
const current = this.get(name, options);
|
|
2768
|
+
if (!current) return false;
|
|
2769
|
+
if (
|
|
2770
|
+
options.scope &&
|
|
2771
|
+
current.configScope !== normalizeMcpConfigScope(options.scope)
|
|
2772
|
+
) {
|
|
2773
|
+
return false;
|
|
2774
|
+
}
|
|
2775
|
+
if (
|
|
2776
|
+
current.configScope === "managed" &&
|
|
2777
|
+
options.allowManagedWrite !== true
|
|
2778
|
+
) {
|
|
2779
|
+
throw new Error(
|
|
2780
|
+
"Managed MCP configuration is read-only; change the managed settings source",
|
|
2781
|
+
);
|
|
2782
|
+
}
|
|
2112
2783
|
const result = this.db
|
|
2113
2784
|
.prepare("DELETE FROM mcp_servers WHERE name = ?")
|
|
2114
|
-
.run(name);
|
|
2785
|
+
.run(current._storageName || name);
|
|
2115
2786
|
return result.changes > 0;
|
|
2116
2787
|
}
|
|
2117
2788
|
|
|
2118
2789
|
_rowToConfig(row) {
|
|
2119
|
-
|
|
2120
|
-
name: row.name,
|
|
2790
|
+
const config = {
|
|
2791
|
+
name: row.display_name || row.name,
|
|
2121
2792
|
command: row.command || null,
|
|
2122
2793
|
// safeJsonParse, not bare JSON.parse: list()/getAutoConnect() map every
|
|
2123
2794
|
// row through here, so one corrupt cell must not take down the whole
|
|
@@ -2128,28 +2799,98 @@ export class MCPServerConfig {
|
|
|
2128
2799
|
url: row.url || null,
|
|
2129
2800
|
transport:
|
|
2130
2801
|
row.transport || (row.url ? inferTransport({ url: row.url }) : "stdio"),
|
|
2802
|
+
headers: safeJsonParse(row.headers, {}),
|
|
2803
|
+
...(typeof row.headers_helper === "string" && row.headers_helper.trim()
|
|
2804
|
+
? { headersHelper: row.headers_helper }
|
|
2805
|
+
: {}),
|
|
2806
|
+
configScope: normalizeMcpConfigScope(row.config_scope || "user"),
|
|
2807
|
+
configSource:
|
|
2808
|
+
row.config_source ||
|
|
2809
|
+
(row.config_scope === "managed"
|
|
2810
|
+
? "managed-settings"
|
|
2811
|
+
: "legacy-database"),
|
|
2812
|
+
projectPath: row.project_path || null,
|
|
2131
2813
|
};
|
|
2814
|
+
Object.defineProperty(config, "_storageName", {
|
|
2815
|
+
value: row.name,
|
|
2816
|
+
enumerable: false,
|
|
2817
|
+
});
|
|
2818
|
+
return config;
|
|
2132
2819
|
}
|
|
2133
2820
|
|
|
2134
|
-
get(name) {
|
|
2135
|
-
|
|
2136
|
-
.prepare("SELECT * FROM mcp_servers WHERE name = ?")
|
|
2137
|
-
.get(name);
|
|
2138
|
-
if (!row) return null;
|
|
2139
|
-
return this._rowToConfig(row);
|
|
2821
|
+
get(name, options = {}) {
|
|
2822
|
+
return this.list(options).find((config) => config.name === name) || null;
|
|
2140
2823
|
}
|
|
2141
2824
|
|
|
2142
|
-
list() {
|
|
2825
|
+
list(options = {}) {
|
|
2143
2826
|
const rows = this.db
|
|
2144
2827
|
.prepare("SELECT * FROM mcp_servers ORDER BY name")
|
|
2145
2828
|
.all();
|
|
2146
|
-
|
|
2829
|
+
const visible = rows
|
|
2830
|
+
.map((row) => this._rowToConfig(row))
|
|
2831
|
+
.filter((config) => this._isVisible(config, options));
|
|
2832
|
+
return options.allScopes === true || options.scope
|
|
2833
|
+
? visible
|
|
2834
|
+
: this._effectiveByName(visible);
|
|
2147
2835
|
}
|
|
2148
2836
|
|
|
2149
|
-
getAutoConnect() {
|
|
2837
|
+
getAutoConnect(options = {}) {
|
|
2150
2838
|
const rows = this.db
|
|
2151
2839
|
.prepare("SELECT * FROM mcp_servers WHERE auto_connect = ? ORDER BY name")
|
|
2152
2840
|
.all(1);
|
|
2153
|
-
|
|
2841
|
+
const visible = rows
|
|
2842
|
+
.map((row) => this._rowToConfig(row))
|
|
2843
|
+
.filter((config) => this._isVisible(config, options));
|
|
2844
|
+
return options.allScopes === true || options.scope
|
|
2845
|
+
? visible
|
|
2846
|
+
: this._effectiveByName(visible);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2849
|
+
_effectiveByName(configs) {
|
|
2850
|
+
const priority = { managed: 4, local: 3, project: 2, user: 1 };
|
|
2851
|
+
const selected = new Map();
|
|
2852
|
+
for (const config of configs) {
|
|
2853
|
+
const current = selected.get(config.name);
|
|
2854
|
+
if (
|
|
2855
|
+
!current ||
|
|
2856
|
+
(priority[config.configScope] || 0) >
|
|
2857
|
+
(priority[current.configScope] || 0)
|
|
2858
|
+
) {
|
|
2859
|
+
selected.set(config.name, config);
|
|
2860
|
+
}
|
|
2861
|
+
}
|
|
2862
|
+
return [...selected.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2865
|
+
_isVisible(config, options = {}) {
|
|
2866
|
+
if (
|
|
2867
|
+
options.scope &&
|
|
2868
|
+
config.configScope !== normalizeMcpConfigScope(options.scope)
|
|
2869
|
+
) {
|
|
2870
|
+
return false;
|
|
2871
|
+
}
|
|
2872
|
+
if (options.allScopes === true) return true;
|
|
2873
|
+
if (config.configScope !== "local" && config.configScope !== "project") {
|
|
2874
|
+
return true;
|
|
2875
|
+
}
|
|
2876
|
+
if (!config.projectPath) return false;
|
|
2877
|
+
const cwd = String(options.cwd || process.cwd());
|
|
2878
|
+
// Avoid importing path solely for an authority comparison: URL
|
|
2879
|
+
// normalization gives stable case-insensitive file URLs on Windows and
|
|
2880
|
+
// resolves dot segments on every supported platform.
|
|
2881
|
+
try {
|
|
2882
|
+
const normalizeAuthority = (value) => {
|
|
2883
|
+
const href = pathToFileURL(value).href;
|
|
2884
|
+
return process.platform === "win32" ? href.toLowerCase() : href;
|
|
2885
|
+
};
|
|
2886
|
+
const expected = normalizeAuthority(config.projectPath);
|
|
2887
|
+
const actual = normalizeAuthority(cwd);
|
|
2888
|
+
return (
|
|
2889
|
+
actual === expected ||
|
|
2890
|
+
actual.startsWith(`${expected.replace(/\/$/, "")}/`)
|
|
2891
|
+
);
|
|
2892
|
+
} catch {
|
|
2893
|
+
return false;
|
|
2894
|
+
}
|
|
2154
2895
|
}
|
|
2155
2896
|
}
|