chainlesschain 0.162.198 → 0.162.200
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/bin/chainlesschain.js +14 -6
- package/package.json +4 -1
- package/scripts/command-lifecycle-report.mjs +94 -0
- package/src/assets/web-panel/assets/{AIOps-CSN-D2xb.js → AIOps-DcQ0bWKy.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CBSMlGzv.js → ActionButton-GecwfFPt.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-3yr3Nt2t.js → Analytics-CD6iSt9v.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-1b-o3lMH.js → AppLayout-CDEtuEYf.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-Dr8rJi17.js → Artifacts-DS3jAjeW.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-2V689HC2.js → Audit-CcSJA048.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DIxkNrD_.js → BackgroundAgents-DRo9Morb.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Pg6BcwrE.js → Backup-Vw5MrWft.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CLUpqyfH.js → BaseInput-DNHIu823.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-qVS8rD4G.js → Chat-DvAsO8-b.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-ti49X12Q.js → ChatBubbleRenderer-9aDuObay.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Bs9AaEay.js → Checkbox-CKCa7tvn.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dvc1mq8q.js → Codegen-CIFvZdaT.js} +1 -1
- package/src/assets/web-panel/assets/{Col-C4_biO7A.js → Col-C79yz7bK.js} +1 -1
- package/src/assets/web-panel/assets/{Community-al25XQUV.js → Community-DVvZskLJ.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-D-ZDB8M1.js → Compact-oaew61hV.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-6lIfDz1W.js → Compliance-DqA7wmRx.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DdI48XMJ.js → Cowork-B-qGadxa.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DF5HZd2g.js → Cron-CUlCl4sS.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DuxeSvt3.js → Crosschain-CNOlr9DU.js} +1 -1
- package/src/assets/web-panel/assets/{DID-B9yHkUTd.js → DID-DJqFCT_3.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Z5EuI3IM.js → Dashboard-CznGpphf.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-vuRfO3dY.js → Dropdown-_cJw218E.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-QFLCc1uL.js → EmailListRenderer-Dr-1BZ9C.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-BZ7EXkFJ.js → FamilyGuardDashboard-Dxf4xtSk.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DycIEg2s.js → Federation-DOiG3i_1.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Ba0H_7yG.js → FormItemContext-BzbMcFB9.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer--JgSTK-C.js → GenericCardRenderer-VbBrYttP.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DEvTbldE.js → Git-CLG8E6ED.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJIljy18.js → Governance-LGJQfk_q.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CVyW3G3q.js → Inference-DlQKx9lF.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-v8-borge.js → KnowledgeGraph-DU8vEouV.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BCdS7gGo.js → Logs-FZIQOdln.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BJNWIrbw.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-AN6tGA2B.js → Marketplace-C_xd-zZI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DGViXASN.js → McpTools-q-YktphS.js} +5 -5
- package/src/assets/web-panel/assets/{Memory-Bpvmx9X4.js → Memory-Cq9OhDin.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CTP-DOTC.js → MobileBridge-B03_R98T.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-C8qfktxn.js → MobileProjects-DZrk-qmF.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-ndYr0M1s.js → Mtc-BidCKW28.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Ci0-YK7W.js → MtcAudit-r-iaMVM7.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BehSTFW7.js → Multisig-CgTUFvff.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-B91_YBLv.js → NLProgramming-B7NvzfAV.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BQlJ0-gq.js → Notes-80CWNqJI.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-CDgYMjYK.js → NotificationSettings-0H6xdiY3.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BJkSlKeH.js → OrderTableRenderer-CKP4AFWy.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B-b3cWCp.js → Organization-B8sxCV_h.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CILUL4nL.js → Overflow-CW9JBBhQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BTCFBkF0.js → P2P-bdSQV7oZ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BAR6AUx5.js → PdhVaultBrowser-DQad42nW.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-RRf6HbWJ.js → Permissions-CueexxTR.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BGTMiICa.js → PersonalDataHub-fqxflhR8.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-945qY9YF.js → Pipeline-KFwwnIMC.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DPZwI6lJ.js → Privacy-ClnD9buY.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BPH2NRKc.js → ProjectInit-CZEz6sm3.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BZCRNCvb.js → ProjectSettings-B6NzpmOI.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BTI__tBt.js → Projects-DYyuwPCs.js} +1 -1
- package/src/assets/web-panel/assets/Providers-C3tz_jdh.js +1 -0
- package/src/assets/web-panel/assets/{QrScannerModal-N6rbqt1r.js → QrScannerModal-BzuOBbqB.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-DrwjPS7M.js → QuickAsk-CmNO9qSj.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cog9Wd99.js → Recommend-VJNj3_NF.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DC9pynjM.js → RemoteSession-CrbdbSBF.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-D4th4gKS.js → Reputation-DTyMd45j.js} +1 -1
- package/src/assets/web-panel/assets/{Row-ogiWR6v9.js → Row-DHrQ-Icz.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-uYlk-2Oj.js → RssFeed-DqWxY9c5.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DYzzwvtM.js → Search-ctF1Ks3J.js} +1 -1
- package/src/assets/web-panel/assets/{Security-Da7YOyX6.js → Security-BrQz8uo5.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CLI1VM1X.js → Services-D4scVFar.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DK77dzDC.js → Skeleton-oUScaOjv.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-C_z7Lmhl.js → Skills-CL-y0eLt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D6413Q8L.js → Sla-DQExhwXj.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C07ydbj2.js → SpeechSettings-iNp_I7kN.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DGOm2W22.js → SyncSettings-CTDlMo0A.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Cr_PVyLM.js → Tasks-4kb8l3sc.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BhL2mZ0t.js → Templates-BXcL9oXW.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CPk5oyUr.js → Tenant-DLE8eS0D.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-ClhqUsqV.js → Terminal-DGpUZPPK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BCC7MV5a.js → TimelineRenderer-C1pWXGPC.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-DD1Jt4oO.js → Tokens-BYfbfqhZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BCydFvN2.js → Trigger-K9YfLOCw.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DDHT1noa.js → Trust-Ctkq77Hy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CWPZC7oI.js → UkeySign-C6_CSe9J.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-9Ea0nJR0.js → VideoEditing-Be3rpOWs.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BjxhLxTf.js → Wallet-D0UIplxk.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-C6uCye67.js → WebAuthn-CF9bM9I3.js} +3 -3
- package/src/assets/web-panel/assets/{WorkflowEditor-d2g4reYo.js → WorkflowEditor-D0H1XVPY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-frTc6ScX.js → chat-Cgzzbe0o.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B5K6lG3v.js → colors-BwNxEVS0.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Cdn46r8D.js → compact-item-D60Z4Hhs.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DU1jqVQi.js → createContext-MFk58XB0.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BJiw369i.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DdNH-aW_.js → hasIn-CRj2SvGO.js} +1 -1
- package/src/assets/web-panel/assets/{index-DM5FmPZU.js → index--s45_1cb.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4l82Me1.js → index--tquQ2ZG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQorP1Ng.js → index-1T1rhokn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CHmkkdfT.js → index-B-M82_rx.js} +1 -1
- package/src/assets/web-panel/assets/{index-CGiwfwB9.js → index-BE_el1Zs.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwlIxAmX.js → index-BhRkR7IO.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cj6P9Nst.js → index-Bo0y96-K.js} +1 -1
- package/src/assets/web-panel/assets/{index-BoFak7le.js → index-CJgu5JUK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dca_i7H8.js → index-CLWwdebO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVLL-mq_.js → index-CNAK5QG_.js} +1 -1
- package/src/assets/web-panel/assets/{index-LaivuXyF.js → index-CQjJjPRD.js} +1 -1
- package/src/assets/web-panel/assets/{index-t_OBzIP4.js → index-CTDbAfae.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcGwGIaR.js → index-CVI_9sRK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D23xGYjM.js → index-CY14oMQK.js} +1 -1
- package/src/assets/web-panel/assets/{index-VDM2KKo9.js → index-CbiTJkZf.js} +1 -1
- package/src/assets/web-panel/assets/index-CiMouosn.js +1 -0
- package/src/assets/web-panel/assets/{index-32ajXmTb.js → index-CqWpxMqx.js} +1 -1
- package/src/assets/web-panel/assets/{index-PjAS0sIZ.js → index-CvB8lYUR.js} +1 -1
- package/src/assets/web-panel/assets/{index-7ehytj1K.js → index-D51XwtCY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DtBCCkC4.js → index-D6HvK_1O.js} +1 -1
- package/src/assets/web-panel/assets/{index-Be4OtL-N.js → index-D9jVIwLJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUoNAmE8.js → index-DA4tE98u.js} +1 -1
- package/src/assets/web-panel/assets/{index-cYaWV4Mq.js → index-DBwOtXw0.js} +1 -1
- package/src/assets/web-panel/assets/{index-MM4Zz2OE.js → index-DKdNzTIb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIVyTmZ3.js → index-DRySdmvN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtRPi4YC.js → index-DWzyTev7.js} +1 -1
- package/src/assets/web-panel/assets/index-DXcKlwy7.js +1 -0
- package/src/assets/web-panel/assets/{index-kVO_MDm-.js → index-DYjs7jAP.js} +1 -1
- package/src/assets/web-panel/assets/{index-D4miNkWv.js → index-DbROzMBp.js} +1 -1
- package/src/assets/web-panel/assets/{index-yZRuZpwK.js → index-DhFtk5PR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXKhhx9V.js → index-Di2AbMLg.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC4eGkar.js → index-DvmocQsM.js} +1 -1
- package/src/assets/web-panel/assets/{index-NmEXVjzt.js → index-Dw-o0zEF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRzBBdQ0.js → index-DysPoP38.js} +1 -1
- package/src/assets/web-panel/assets/{index-HjCvXHf5.js → index-LX9pJxm_.js} +1 -1
- package/src/assets/web-panel/assets/{index-gg3QPC3i.js → index-OI_fXWx0.js} +3 -3
- package/src/assets/web-panel/assets/{index-mgU3Q-lN.js → index-eDTOX8Xj.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1qIC4GR.js → index-gWKDsTzF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DftZuyA7.js → index-q_GDUVAA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-DwLTRNdo.js → initDefaultProps-C61kZqEd.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BchZDXnh.js → motion-DZnT5U1L.js} +1 -1
- package/src/assets/web-panel/assets/{move-e8M-zZFl.js → move-L8wzwbel.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Ci40fQb3.js → mtc-parser-DLdYSejc.js} +1 -1
- package/src/assets/web-panel/assets/{omit-C7110_f2.js → omit-D53cbS59.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-skteMgnM.js → pickAttrs-Bbc5HPtu.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-MGZ-lh2B.js → placementArrow-BkvmO860.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-N4Sd7uy-.js → responsiveObserve-u2y1ryS1.js} +1 -1
- package/src/assets/web-panel/assets/{slide-C7HDGRaa.js → slide-BBqj7svU.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BhRexlgy.js → statusUtils-GN0mqu2d.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-ChEoQkN2.js → styleChecker-BvwPKoBT.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CDbG1s2Y.js → useFlexGapSupport-CCt8tnfY.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CUIx0tcJ.js → useFs-DXAxl2SV.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-bkO7L7ec.js → usePersonalDataHub-DihlR2dj.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B_ZvABIG.js → vnode-D7BOX2gi.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-D5shjpS8.js → zoom-CVbO-nL7.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/mcp.js +119 -0
- package/src/commands/session-show.js +8 -10
- package/src/commands/session.js +60 -52
- package/src/data/changelog.json +27 -1
- package/src/gateways/ws/session-protocol.js +14 -4
- package/src/gateways/ws/ws-agent-handler.js +31 -0
- package/src/harness/jsonl-session-store.js +1147 -130
- package/src/harness/mcp-client.js +44 -11
- package/src/harness/session-index.js +80 -80
- package/src/harness/session-list-index.js +147 -2
- package/src/lib/background-interaction-journal.js +2 -2
- package/src/lib/command-lifecycle-report.js +516 -0
- package/src/lib/mcp-stdio-environment.js +48 -0
- package/src/lib/mcp-stdio-executable-identity.js +552 -17
- package/src/lib/mcp-stdio-package-materialization.js +1424 -0
- package/src/lib/packer/native-update-state.js +979 -1
- package/src/lib/packer/pack-update-applier.js +1050 -125
- package/src/lib/packer/pkg-config-generator.js +1 -0
- package/src/lib/paths.js +53 -0
- package/src/lib/process-execution-broker/index.js +180 -37
- package/src/lib/process-execution-broker/platform-sandbox.js +291 -24
- package/src/lib/recent-session.js +17 -17
- package/src/lib/secure-file-identity.js +47 -0
- package/src/lib/secure-fs.js +21 -2
- package/src/lib/session-anti-rollback-anchor.js +766 -0
- package/src/lib/session-host-lease.js +492 -0
- package/src/lib/session-persistence-failure.js +109 -0
- package/src/lib/session-picker.js +26 -3
- package/src/lib/session-search.js +25 -11
- package/src/lib/side-effect-ledger-store.js +2 -2
- package/src/lib/skill-execution-authority.js +13 -9
- package/src/lib/turn-binding-store.js +2 -2
- package/src/repl/agent-repl.js +309 -39
- package/src/runtime/agent-core.js +88 -5
- package/src/runtime/cleanup-deadline.js +143 -0
- package/src/runtime/headless-runner.js +357 -139
- package/src/runtime/headless-stream.js +207 -28
- package/src/runtime/output-backpressure.js +53 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-CU-JB2Ns.js +0 -1
- package/src/assets/web-panel/assets/Providers-DDCf_nIw.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CP-jEUyw.js +0 -1
- package/src/assets/web-panel/assets/index-CzrCHrFy.js +0 -1
- package/src/assets/web-panel/assets/index-OJsGskZw.js +0 -1
|
@@ -0,0 +1,1424 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fixed-version, content-addressed npm materialization for MCP stdio servers.
|
|
3
|
+
*
|
|
4
|
+
* JavaScript package launchers are resolvers and downloaders, not executable
|
|
5
|
+
* identities. An explicitly approved materialization normalizes supported
|
|
6
|
+
* npx/npm/pnpm/yarn/bunx/corepack source invocations to one exact npm package
|
|
7
|
+
* version, installs it with lifecycle scripts disabled, verifies every
|
|
8
|
+
* transitive lock entry has registry integrity, snapshots the exact installed
|
|
9
|
+
* bytes, and bundles the reachable JavaScript closure into one guarded CJS
|
|
10
|
+
* capsule. Runtime resolution never invokes the dynamic launcher or the live
|
|
11
|
+
* node_modules entrypoint: it re-verifies both the provenance tree and capsule,
|
|
12
|
+
* then returns the current Node runtime plus the pinned capsule.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import crypto from "node:crypto";
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import { builtinModules, createRequire } from "node:module";
|
|
18
|
+
import path from "node:path";
|
|
19
|
+
import { getCacheDir, getMachineSecurityAnchorDir } from "./paths.js";
|
|
20
|
+
import {
|
|
21
|
+
mutateSecurityStore,
|
|
22
|
+
readSecurityStore,
|
|
23
|
+
writeSecurityStore,
|
|
24
|
+
} from "./durable-security-store.js";
|
|
25
|
+
import { sanitizeMcpStdioHostEnvironment } from "./mcp-stdio-environment.js";
|
|
26
|
+
|
|
27
|
+
export const MCP_STDIO_PACKAGE_MATERIALIZATION_REQUIRED_CODE =
|
|
28
|
+
"CC_MCP_STDIO_PACKAGE_MATERIALIZATION_REQUIRED";
|
|
29
|
+
export const MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE =
|
|
30
|
+
"CC_MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID";
|
|
31
|
+
export const MCP_STDIO_PACKAGE_MATERIALIZATION_FAILED_CODE =
|
|
32
|
+
"CC_MCP_STDIO_PACKAGE_MATERIALIZATION_FAILED";
|
|
33
|
+
export const MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE =
|
|
34
|
+
"CC_MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED";
|
|
35
|
+
|
|
36
|
+
const MATERIALIZATION_SCHEMA =
|
|
37
|
+
"chainlesschain.mcp-stdio-package-materialization/v2";
|
|
38
|
+
const MATERIALIZATION_VERSION = 2;
|
|
39
|
+
const CAPSULE_SCHEMA = "chainlesschain.mcp-stdio-node-capsule/v1";
|
|
40
|
+
const CAPSULE_RELATIVE_PATH = "capsule/server.cjs";
|
|
41
|
+
const CAPSULE_BUILDER = "esbuild";
|
|
42
|
+
const CAPSULE_BUILDER_VERSION = "0.28.1";
|
|
43
|
+
const CAPSULE_BUILDER_BINARIES = Object.freeze({
|
|
44
|
+
"darwin-arm64": Object.freeze({
|
|
45
|
+
packageName: "@esbuild/darwin-arm64",
|
|
46
|
+
relativePath: "bin/esbuild",
|
|
47
|
+
sha256: "e2dc9a52440a2a34f09434a2f4843cb1e30f84e40dcf238976ec61ef8cd7f36a",
|
|
48
|
+
}),
|
|
49
|
+
"darwin-x64": Object.freeze({
|
|
50
|
+
packageName: "@esbuild/darwin-x64",
|
|
51
|
+
relativePath: "bin/esbuild",
|
|
52
|
+
sha256: "dd53ccf32f9b5b3ab30d41388ef1fc8f81c44ca57ee7a32a7364a1753308d009",
|
|
53
|
+
}),
|
|
54
|
+
"linux-arm64": Object.freeze({
|
|
55
|
+
packageName: "@esbuild/linux-arm64",
|
|
56
|
+
relativePath: "bin/esbuild",
|
|
57
|
+
sha256: "51e829ba36f36be6d9aea6e329ddc4f9350302339b16aaca96a3cb97f64a8ebb",
|
|
58
|
+
}),
|
|
59
|
+
"linux-ia32": Object.freeze({
|
|
60
|
+
packageName: "@esbuild/linux-ia32",
|
|
61
|
+
relativePath: "bin/esbuild",
|
|
62
|
+
sha256:
|
|
63
|
+
"9cd7515a75d6f96b0aa055861cf987888b4765c890501b6274f4bdff4061a5e0d9fd",
|
|
64
|
+
}),
|
|
65
|
+
"linux-x64": Object.freeze({
|
|
66
|
+
packageName: "@esbuild/linux-x64",
|
|
67
|
+
relativePath: "bin/esbuild",
|
|
68
|
+
sha256: "0c6588b092a2c291a72bab90659f3c9e0e25e0fe59c9ac12b4dae4d945e5548c",
|
|
69
|
+
}),
|
|
70
|
+
"win32-arm64": Object.freeze({
|
|
71
|
+
packageName: "@esbuild/win32-arm64",
|
|
72
|
+
relativePath: "esbuild.exe",
|
|
73
|
+
sha256: "bfb8798ab678f1ce4a723739f4a3eabab3244d7a04eeb12be2eb9f58095c13ef",
|
|
74
|
+
}),
|
|
75
|
+
"win32-ia32": Object.freeze({
|
|
76
|
+
packageName: "@esbuild/win32-ia32",
|
|
77
|
+
relativePath: "esbuild.exe",
|
|
78
|
+
sha256: "8fa99b6e0945830fce8d7e208fdb21763aa4aea875751f4a0eec7f6e262af1dd",
|
|
79
|
+
}),
|
|
80
|
+
"win32-x64": Object.freeze({
|
|
81
|
+
packageName: "@esbuild/win32-x64",
|
|
82
|
+
relativePath: "esbuild.exe",
|
|
83
|
+
sha256: "ec02ee9b14ab332416fedd10614dfb80eed5304d94f67745067c011934a8c3c3",
|
|
84
|
+
}),
|
|
85
|
+
});
|
|
86
|
+
const INDEX_LABEL = "MCP stdio package materialization index";
|
|
87
|
+
const MANIFEST_LABEL = "MCP stdio package materialization manifest";
|
|
88
|
+
const MAX_FILES = 50_000;
|
|
89
|
+
const MAX_TOTAL_BYTES = 1024 * 1024 * 1024;
|
|
90
|
+
const MAX_FILE_BYTES = 256 * 1024 * 1024;
|
|
91
|
+
const MAX_DEPTH = 64;
|
|
92
|
+
const HASH_CHUNK_BYTES = 1024 * 1024;
|
|
93
|
+
const EXACT_VERSION =
|
|
94
|
+
/^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
95
|
+
const PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/i;
|
|
96
|
+
|
|
97
|
+
export const _deps = {
|
|
98
|
+
fs,
|
|
99
|
+
processBrokerRunSync: null,
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const require = createRequire(import.meta.url);
|
|
103
|
+
const NODE_BUILTINS = new Set(
|
|
104
|
+
builtinModules.flatMap((name) => [
|
|
105
|
+
name,
|
|
106
|
+
name.startsWith("node:") ? name : `node:${name}`,
|
|
107
|
+
]),
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
function materializationError(code, message, details = {}) {
|
|
111
|
+
const error = new Error(message);
|
|
112
|
+
error.name = "McpStdioPackageMaterializationError";
|
|
113
|
+
error.code = code;
|
|
114
|
+
Object.assign(error, details);
|
|
115
|
+
return error;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function sha256(value) {
|
|
119
|
+
return crypto.createHash("sha256").update(value).digest("hex");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function canonicalJson(value) {
|
|
123
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(",")}]`;
|
|
124
|
+
if (value && typeof value === "object") {
|
|
125
|
+
return `{${Object.keys(value)
|
|
126
|
+
.sort()
|
|
127
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson(value[key])}`)
|
|
128
|
+
.join(",")}}`;
|
|
129
|
+
}
|
|
130
|
+
return JSON.stringify(value);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function executableBasename(value) {
|
|
134
|
+
return path
|
|
135
|
+
.basename(String(value || ""))
|
|
136
|
+
.replace(/\.(?:exe|com|cmd|bat)$/i, "")
|
|
137
|
+
.toLowerCase();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export function getMcpStdioPackageMaterializationRoot(options = {}) {
|
|
141
|
+
return path.resolve(
|
|
142
|
+
options.root ||
|
|
143
|
+
process.env.CC_MCP_PACKAGE_MATERIALIZATION_ROOT ||
|
|
144
|
+
path.join(getCacheDir(), "mcp-stdio-package-materializations-v2"),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function getMcpStdioPackageMaterializationIndexPath(options = {}) {
|
|
149
|
+
return path.resolve(
|
|
150
|
+
options.indexPath ||
|
|
151
|
+
process.env.CC_MCP_PACKAGE_MATERIALIZATION_INDEX ||
|
|
152
|
+
path.join(
|
|
153
|
+
getMachineSecurityAnchorDir(),
|
|
154
|
+
"mcp-stdio-package-materializations-v2.json",
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function parseExactNpmPackageSpec(value) {
|
|
160
|
+
if (typeof value !== "string" || !value || value.includes("\0")) {
|
|
161
|
+
throw materializationError(
|
|
162
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
163
|
+
"MCP package materialization requires an exact npm package@version",
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
const separator = value.startsWith("@")
|
|
167
|
+
? value.indexOf("@", value.indexOf("/") + 1)
|
|
168
|
+
: value.lastIndexOf("@");
|
|
169
|
+
if (separator <= 0) {
|
|
170
|
+
throw materializationError(
|
|
171
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
172
|
+
`MCP npm package spec must use an exact version: ${value}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
const name = value.slice(0, separator);
|
|
176
|
+
const version = value.slice(separator + 1);
|
|
177
|
+
if (!PACKAGE_NAME.test(name) || !EXACT_VERSION.test(version)) {
|
|
178
|
+
throw materializationError(
|
|
179
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
180
|
+
`MCP npm package spec must use a registry name and exact version: ${value}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
return Object.freeze({ name, version, spec: `${name}@${version}` });
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function skipYesFlag(args, start) {
|
|
187
|
+
let index = start;
|
|
188
|
+
while (args[index] === "-y" || args[index] === "--yes") index += 1;
|
|
189
|
+
return index;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function packageArgumentIndex(config) {
|
|
193
|
+
const command = executableBasename(config?.command);
|
|
194
|
+
const args = Array.isArray(config?.args) ? [...config.args] : [];
|
|
195
|
+
let index = 0;
|
|
196
|
+
let launcher = command;
|
|
197
|
+
|
|
198
|
+
if (["npx", "bunx", "pnpx"].includes(command)) {
|
|
199
|
+
index = skipYesFlag(args, 0);
|
|
200
|
+
} else if (command === "npm") {
|
|
201
|
+
if (args[index] !== "exec") return null;
|
|
202
|
+
launcher = "npm-exec";
|
|
203
|
+
index = skipYesFlag(args, index + 1);
|
|
204
|
+
if (args[index] === "--") index += 1;
|
|
205
|
+
} else if (command === "pnpm") {
|
|
206
|
+
if (args[index] !== "dlx") return null;
|
|
207
|
+
launcher = "pnpm-dlx";
|
|
208
|
+
index = skipYesFlag(args, index + 1);
|
|
209
|
+
} else if (command === "yarn" || command === "yarnpkg") {
|
|
210
|
+
if (args[index] !== "dlx") return null;
|
|
211
|
+
launcher = `${command}-dlx`;
|
|
212
|
+
index = skipYesFlag(args, index + 1);
|
|
213
|
+
} else if (command === "corepack") {
|
|
214
|
+
const manager = executableBasename(args[index]);
|
|
215
|
+
if (!["pnpm", "yarn", "yarnpkg"].includes(manager)) return null;
|
|
216
|
+
if (args[index + 1] !== "dlx") return null;
|
|
217
|
+
launcher = `corepack-${manager}-dlx`;
|
|
218
|
+
index = skipYesFlag(args, index + 2);
|
|
219
|
+
} else {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return Object.freeze({ args, index, launcher });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export function parseNpmPackageLauncherInvocation(config, packageSpec) {
|
|
227
|
+
const exact = parseExactNpmPackageSpec(packageSpec);
|
|
228
|
+
const parsed = packageArgumentIndex(config);
|
|
229
|
+
if (!parsed) {
|
|
230
|
+
throw materializationError(
|
|
231
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
232
|
+
`MCP fixed npm materialization does not support source launcher ${String(config?.command)}`,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
if (parsed.args[parsed.index] !== exact.spec) {
|
|
236
|
+
throw materializationError(
|
|
237
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
238
|
+
`MCP package launcher must name the same exact package spec ${exact.spec} without unbound resolver flags`,
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
return Object.freeze({
|
|
242
|
+
...exact,
|
|
243
|
+
launcher: parsed.launcher,
|
|
244
|
+
passthroughArgs: Object.freeze(parsed.args.slice(parsed.index + 1)),
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Compatibility export for callers introduced with the original npx-only
|
|
249
|
+
// materializer. Its semantics intentionally expand to all normalized npm
|
|
250
|
+
// package launchers while retaining the stable API name.
|
|
251
|
+
export const parseNpxMaterializationInvocation =
|
|
252
|
+
parseNpmPackageLauncherInvocation;
|
|
253
|
+
|
|
254
|
+
function resolveNpmCli(options = {}) {
|
|
255
|
+
const candidates = [
|
|
256
|
+
options.npmCli,
|
|
257
|
+
process.env.npm_execpath,
|
|
258
|
+
path.join(
|
|
259
|
+
path.dirname(process.execPath),
|
|
260
|
+
"node_modules",
|
|
261
|
+
"npm",
|
|
262
|
+
"bin",
|
|
263
|
+
"npm-cli.js",
|
|
264
|
+
),
|
|
265
|
+
path.resolve(
|
|
266
|
+
path.dirname(process.execPath),
|
|
267
|
+
"..",
|
|
268
|
+
"lib",
|
|
269
|
+
"node_modules",
|
|
270
|
+
"npm",
|
|
271
|
+
"bin",
|
|
272
|
+
"npm-cli.js",
|
|
273
|
+
),
|
|
274
|
+
path.resolve(
|
|
275
|
+
path.dirname(process.execPath),
|
|
276
|
+
"..",
|
|
277
|
+
"node_modules",
|
|
278
|
+
"npm",
|
|
279
|
+
"bin",
|
|
280
|
+
"npm-cli.js",
|
|
281
|
+
),
|
|
282
|
+
].filter(Boolean);
|
|
283
|
+
for (const candidate of candidates) {
|
|
284
|
+
try {
|
|
285
|
+
const resolved = path.resolve(candidate);
|
|
286
|
+
const stat = _deps.fs.statSync(resolved);
|
|
287
|
+
if (stat.isFile()) return resolved;
|
|
288
|
+
} catch {
|
|
289
|
+
// Try the next explicit npm CLI location.
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
throw materializationError(
|
|
293
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_FAILED_CODE,
|
|
294
|
+
"A local npm-cli.js could not be resolved for fixed package materialization",
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function defaultInstallRunner({
|
|
299
|
+
directory,
|
|
300
|
+
packageSpec,
|
|
301
|
+
npmCli,
|
|
302
|
+
env,
|
|
303
|
+
processBrokerRunSync: injectedProcessBrokerRunSync,
|
|
304
|
+
}) {
|
|
305
|
+
const runThroughProcessBroker =
|
|
306
|
+
injectedProcessBrokerRunSync || _deps.processBrokerRunSync;
|
|
307
|
+
if (typeof runThroughProcessBroker !== "function") {
|
|
308
|
+
throw materializationError(
|
|
309
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_FAILED_CODE,
|
|
310
|
+
"MCP package materialization requires a host-owned Process Broker runner",
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
const result = runThroughProcessBroker(
|
|
314
|
+
process.execPath,
|
|
315
|
+
[
|
|
316
|
+
npmCli,
|
|
317
|
+
"install",
|
|
318
|
+
"--ignore-scripts",
|
|
319
|
+
"--no-audit",
|
|
320
|
+
"--no-fund",
|
|
321
|
+
"--omit=dev",
|
|
322
|
+
"--save-exact",
|
|
323
|
+
"--package-lock=true",
|
|
324
|
+
"--install-strategy=nested",
|
|
325
|
+
packageSpec,
|
|
326
|
+
],
|
|
327
|
+
{
|
|
328
|
+
cwd: directory,
|
|
329
|
+
env: sanitizeMcpStdioHostEnvironment(env),
|
|
330
|
+
encoding: "utf8",
|
|
331
|
+
shell: false,
|
|
332
|
+
windowsHide: true,
|
|
333
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
334
|
+
},
|
|
335
|
+
);
|
|
336
|
+
if (result.error || result.status !== 0) {
|
|
337
|
+
const detail = String(
|
|
338
|
+
result.stderr || result.error?.message || "npm failed",
|
|
339
|
+
)
|
|
340
|
+
.trim()
|
|
341
|
+
.slice(0, 2000);
|
|
342
|
+
throw materializationError(
|
|
343
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_FAILED_CODE,
|
|
344
|
+
`npm fixed package materialization failed: ${detail}`,
|
|
345
|
+
{ cause: result.error, exitCode: result.status },
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function packageDirectory(treeRoot, packageName) {
|
|
351
|
+
return path.join(treeRoot, "node_modules", ...packageName.split("/"));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
function readJson(file, label) {
|
|
355
|
+
try {
|
|
356
|
+
const value = JSON.parse(_deps.fs.readFileSync(file, "utf8"));
|
|
357
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
358
|
+
throw new TypeError("top-level value must be an object");
|
|
359
|
+
}
|
|
360
|
+
return value;
|
|
361
|
+
} catch (cause) {
|
|
362
|
+
throw materializationError(
|
|
363
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
364
|
+
`${label} is unavailable or invalid: ${cause.message}`,
|
|
365
|
+
{ cause, file },
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
function validateNpmLock(treeRoot, exact) {
|
|
371
|
+
const lockPath = path.join(treeRoot, "package-lock.json");
|
|
372
|
+
const lock = readJson(lockPath, "npm package-lock.json");
|
|
373
|
+
if (!Number.isInteger(lock.lockfileVersion) || lock.lockfileVersion < 2) {
|
|
374
|
+
throw materializationError(
|
|
375
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
376
|
+
"npm materialization requires package-lock v2 or newer",
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const packages = lock.packages;
|
|
380
|
+
if (!packages || typeof packages !== "object" || Array.isArray(packages)) {
|
|
381
|
+
throw materializationError(
|
|
382
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
383
|
+
"npm materialization lock has no packages map",
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const rootVersion = packages[""]?.dependencies?.[exact.name];
|
|
387
|
+
if (rootVersion !== exact.version) {
|
|
388
|
+
throw materializationError(
|
|
389
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
390
|
+
"npm materialization lock does not bind the requested exact root version",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
let packageCount = 0;
|
|
394
|
+
for (const [location, entry] of Object.entries(packages)) {
|
|
395
|
+
if (location === "") continue;
|
|
396
|
+
packageCount += 1;
|
|
397
|
+
if (
|
|
398
|
+
!entry ||
|
|
399
|
+
typeof entry !== "object" ||
|
|
400
|
+
entry.link === true ||
|
|
401
|
+
typeof entry.version !== "string" ||
|
|
402
|
+
!EXACT_VERSION.test(entry.version) ||
|
|
403
|
+
typeof entry.resolved !== "string" ||
|
|
404
|
+
!/^https:\/\//i.test(entry.resolved) ||
|
|
405
|
+
typeof entry.integrity !== "string" ||
|
|
406
|
+
!/^sha512-[A-Za-z0-9+/]+={0,2}$/.test(entry.integrity)
|
|
407
|
+
) {
|
|
408
|
+
throw materializationError(
|
|
409
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
410
|
+
`npm transitive lock entry is not registry-integrity pinned: ${location}`,
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
if (packageCount === 0) {
|
|
415
|
+
throw materializationError(
|
|
416
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
417
|
+
"npm materialization lock contains no installed package",
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
return Object.freeze({
|
|
421
|
+
lockPath,
|
|
422
|
+
lockSha256: sha256(_deps.fs.readFileSync(lockPath)),
|
|
423
|
+
packageCount,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
function resolvePackageEntrypoint(treeRoot, exact, requestedBin) {
|
|
428
|
+
const root = packageDirectory(treeRoot, exact.name);
|
|
429
|
+
const packageJson = readJson(
|
|
430
|
+
path.join(root, "package.json"),
|
|
431
|
+
"npm package.json",
|
|
432
|
+
);
|
|
433
|
+
if (
|
|
434
|
+
packageJson.name !== exact.name ||
|
|
435
|
+
packageJson.version !== exact.version
|
|
436
|
+
) {
|
|
437
|
+
throw materializationError(
|
|
438
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
439
|
+
"installed npm package identity does not match the requested exact spec",
|
|
440
|
+
);
|
|
441
|
+
}
|
|
442
|
+
let binName;
|
|
443
|
+
let binPath;
|
|
444
|
+
if (typeof packageJson.bin === "string") {
|
|
445
|
+
binName = packageJson.name.replace(/^@[^/]+\//, "");
|
|
446
|
+
binPath = packageJson.bin;
|
|
447
|
+
if (requestedBin && requestedBin !== binName) {
|
|
448
|
+
throw materializationError(
|
|
449
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
450
|
+
`requested npm bin ${requestedBin} is not exported by ${exact.spec}`,
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
} else if (
|
|
454
|
+
packageJson.bin &&
|
|
455
|
+
typeof packageJson.bin === "object" &&
|
|
456
|
+
!Array.isArray(packageJson.bin)
|
|
457
|
+
) {
|
|
458
|
+
const names = Object.keys(packageJson.bin).sort();
|
|
459
|
+
binName = requestedBin || (names.length === 1 ? names[0] : null);
|
|
460
|
+
if (!binName || typeof packageJson.bin[binName] !== "string") {
|
|
461
|
+
throw materializationError(
|
|
462
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
463
|
+
`npm package ${exact.spec} exports multiple bins; select one explicitly`,
|
|
464
|
+
);
|
|
465
|
+
}
|
|
466
|
+
binPath = packageJson.bin[binName];
|
|
467
|
+
} else {
|
|
468
|
+
throw materializationError(
|
|
469
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
470
|
+
`npm package ${exact.spec} does not export an executable bin`,
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
const entrypoint = path.resolve(root, binPath);
|
|
474
|
+
const relative = path.relative(treeRoot, entrypoint);
|
|
475
|
+
if (
|
|
476
|
+
relative === "" ||
|
|
477
|
+
relative === ".." ||
|
|
478
|
+
relative.startsWith(`..${path.sep}`) ||
|
|
479
|
+
path.isAbsolute(relative)
|
|
480
|
+
) {
|
|
481
|
+
throw materializationError(
|
|
482
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
483
|
+
"npm package bin resolves outside the materialized tree",
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
const stat = _deps.fs.lstatSync(entrypoint);
|
|
487
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
488
|
+
throw materializationError(
|
|
489
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
490
|
+
"npm package bin must be one regular non-symlink file",
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
const headerBuffer = Buffer.allocUnsafe(
|
|
494
|
+
Math.max(1, Math.min(stat.size, 256)),
|
|
495
|
+
);
|
|
496
|
+
const headerDescriptor = _deps.fs.openSync(entrypoint, "r");
|
|
497
|
+
let headerBytes;
|
|
498
|
+
try {
|
|
499
|
+
headerBytes = _deps.fs.readSync(
|
|
500
|
+
headerDescriptor,
|
|
501
|
+
headerBuffer,
|
|
502
|
+
0,
|
|
503
|
+
headerBuffer.length,
|
|
504
|
+
0,
|
|
505
|
+
);
|
|
506
|
+
} finally {
|
|
507
|
+
_deps.fs.closeSync(headerDescriptor);
|
|
508
|
+
}
|
|
509
|
+
const header = headerBuffer.subarray(0, headerBytes).toString("utf8");
|
|
510
|
+
if (
|
|
511
|
+
![".js", ".cjs", ".mjs"].includes(path.extname(entrypoint).toLowerCase()) &&
|
|
512
|
+
!/^#![^\r\n]*\bnode(?:\.exe)?(?:\s|$)/i.test(header)
|
|
513
|
+
) {
|
|
514
|
+
throw materializationError(
|
|
515
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
516
|
+
"npm package bin must be a direct Node entrypoint; native or secondary-runtime bins require a separate materializer",
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return Object.freeze({
|
|
520
|
+
binName,
|
|
521
|
+
entrypoint,
|
|
522
|
+
entrypointRelative: relative.split(path.sep).join("/"),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function hashFile(file, expectedBytes) {
|
|
527
|
+
let descriptor;
|
|
528
|
+
try {
|
|
529
|
+
descriptor = _deps.fs.openSync(
|
|
530
|
+
file,
|
|
531
|
+
Number(_deps.fs.constants.O_RDONLY) |
|
|
532
|
+
Number(_deps.fs.constants.O_NOFOLLOW || 0) |
|
|
533
|
+
Number(_deps.fs.constants.O_NONBLOCK || 0),
|
|
534
|
+
);
|
|
535
|
+
const before = _deps.fs.fstatSync(descriptor, { bigint: true });
|
|
536
|
+
if (!before.isFile() || before.size !== BigInt(expectedBytes)) {
|
|
537
|
+
throw new Error("file size or type changed before hashing");
|
|
538
|
+
}
|
|
539
|
+
const digest = crypto.createHash("sha256");
|
|
540
|
+
const chunk = Buffer.allocUnsafe(
|
|
541
|
+
Math.max(1, Math.min(expectedBytes, HASH_CHUNK_BYTES)),
|
|
542
|
+
);
|
|
543
|
+
let offset = 0;
|
|
544
|
+
while (offset < expectedBytes) {
|
|
545
|
+
const count = _deps.fs.readSync(
|
|
546
|
+
descriptor,
|
|
547
|
+
chunk,
|
|
548
|
+
0,
|
|
549
|
+
Math.min(chunk.length, expectedBytes - offset),
|
|
550
|
+
offset,
|
|
551
|
+
);
|
|
552
|
+
if (count <= 0) throw new Error("file ended before its observed size");
|
|
553
|
+
digest.update(chunk.subarray(0, count));
|
|
554
|
+
offset += count;
|
|
555
|
+
}
|
|
556
|
+
const after = _deps.fs.fstatSync(descriptor, { bigint: true });
|
|
557
|
+
if (
|
|
558
|
+
after.dev !== before.dev ||
|
|
559
|
+
after.ino !== before.ino ||
|
|
560
|
+
after.size !== before.size ||
|
|
561
|
+
after.mtimeNs !== before.mtimeNs ||
|
|
562
|
+
after.ctimeNs !== before.ctimeNs
|
|
563
|
+
) {
|
|
564
|
+
throw new Error("file changed while it was being hashed");
|
|
565
|
+
}
|
|
566
|
+
return digest.digest("hex");
|
|
567
|
+
} finally {
|
|
568
|
+
if (descriptor !== undefined) _deps.fs.closeSync(descriptor);
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
function collectTree(root) {
|
|
573
|
+
const files = [];
|
|
574
|
+
let totalBytes = 0;
|
|
575
|
+
const visit = (directory, depth) => {
|
|
576
|
+
if (depth > MAX_DEPTH) {
|
|
577
|
+
throw new Error("materialized dependency tree exceeds the depth limit");
|
|
578
|
+
}
|
|
579
|
+
const entries = _deps.fs
|
|
580
|
+
.readdirSync(directory, { withFileTypes: true })
|
|
581
|
+
.sort((left, right) => left.name.localeCompare(right.name));
|
|
582
|
+
for (const entry of entries) {
|
|
583
|
+
if (entry.name === ".bin" && entry.isDirectory()) continue;
|
|
584
|
+
const absolute = path.join(directory, entry.name);
|
|
585
|
+
const relative = path.relative(root, absolute).split(path.sep).join("/");
|
|
586
|
+
if (entry.isSymbolicLink()) {
|
|
587
|
+
throw new Error(
|
|
588
|
+
`materialized dependency tree contains a symlink: ${relative}`,
|
|
589
|
+
);
|
|
590
|
+
}
|
|
591
|
+
if (entry.isDirectory()) {
|
|
592
|
+
visit(absolute, depth + 1);
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (!entry.isFile()) {
|
|
596
|
+
throw new Error(
|
|
597
|
+
`materialized dependency tree contains a special file: ${relative}`,
|
|
598
|
+
);
|
|
599
|
+
}
|
|
600
|
+
const stat = _deps.fs.statSync(absolute, { bigint: true });
|
|
601
|
+
if (stat.size < 0n || stat.size > BigInt(MAX_FILE_BYTES)) {
|
|
602
|
+
throw new Error(
|
|
603
|
+
`materialized file exceeds the size limit: ${relative}`,
|
|
604
|
+
);
|
|
605
|
+
}
|
|
606
|
+
totalBytes += Number(stat.size);
|
|
607
|
+
if (files.length >= MAX_FILES || totalBytes > MAX_TOTAL_BYTES) {
|
|
608
|
+
throw new Error(
|
|
609
|
+
"materialized dependency tree exceeds its aggregate budget",
|
|
610
|
+
);
|
|
611
|
+
}
|
|
612
|
+
files.push({
|
|
613
|
+
path: relative,
|
|
614
|
+
bytes: Number(stat.size),
|
|
615
|
+
mode: Number(stat.mode),
|
|
616
|
+
sha256: hashFile(absolute, Number(stat.size)),
|
|
617
|
+
});
|
|
618
|
+
}
|
|
619
|
+
};
|
|
620
|
+
visit(root, 0);
|
|
621
|
+
return Object.freeze({
|
|
622
|
+
files: Object.freeze(files.map((entry) => Object.freeze(entry))),
|
|
623
|
+
fileCount: files.length,
|
|
624
|
+
totalBytes,
|
|
625
|
+
closureDigest: sha256(canonicalJson(files)),
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
function resolveContainedPath(root, relative, label) {
|
|
630
|
+
if (
|
|
631
|
+
typeof relative !== "string" ||
|
|
632
|
+
!relative ||
|
|
633
|
+
relative.includes("\0") ||
|
|
634
|
+
relative.split("/").some((part) => !part || part === "." || part === "..")
|
|
635
|
+
) {
|
|
636
|
+
throw new Error(`${label} has an invalid relative path`);
|
|
637
|
+
}
|
|
638
|
+
const resolved = path.resolve(root, ...relative.split("/"));
|
|
639
|
+
const observed = path.relative(path.resolve(root), resolved);
|
|
640
|
+
if (
|
|
641
|
+
observed === "" ||
|
|
642
|
+
observed === ".." ||
|
|
643
|
+
observed.startsWith(`..${path.sep}`) ||
|
|
644
|
+
path.isAbsolute(observed)
|
|
645
|
+
) {
|
|
646
|
+
throw new Error(`${label} escapes its root`);
|
|
647
|
+
}
|
|
648
|
+
return resolved;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
function copyAttestedFile(sourceRoot, snapshotRoot, record) {
|
|
652
|
+
const source = resolveContainedPath(
|
|
653
|
+
sourceRoot,
|
|
654
|
+
record.path,
|
|
655
|
+
"MCP capsule source",
|
|
656
|
+
);
|
|
657
|
+
const destination = resolveContainedPath(
|
|
658
|
+
snapshotRoot,
|
|
659
|
+
record.path,
|
|
660
|
+
"MCP capsule snapshot",
|
|
661
|
+
);
|
|
662
|
+
_deps.fs.mkdirSync(path.dirname(destination), {
|
|
663
|
+
recursive: true,
|
|
664
|
+
mode: 0o700,
|
|
665
|
+
});
|
|
666
|
+
let sourceDescriptor;
|
|
667
|
+
let destinationDescriptor;
|
|
668
|
+
try {
|
|
669
|
+
sourceDescriptor = _deps.fs.openSync(
|
|
670
|
+
source,
|
|
671
|
+
Number(_deps.fs.constants.O_RDONLY) |
|
|
672
|
+
Number(_deps.fs.constants.O_NOFOLLOW || 0) |
|
|
673
|
+
Number(_deps.fs.constants.O_NONBLOCK || 0),
|
|
674
|
+
);
|
|
675
|
+
const before = _deps.fs.fstatSync(sourceDescriptor, { bigint: true });
|
|
676
|
+
if (!before.isFile() || before.size !== BigInt(record.bytes)) {
|
|
677
|
+
throw new Error(`MCP capsule source changed before copy: ${record.path}`);
|
|
678
|
+
}
|
|
679
|
+
destinationDescriptor = _deps.fs.openSync(
|
|
680
|
+
destination,
|
|
681
|
+
Number(_deps.fs.constants.O_WRONLY) |
|
|
682
|
+
Number(_deps.fs.constants.O_CREAT) |
|
|
683
|
+
Number(_deps.fs.constants.O_EXCL) |
|
|
684
|
+
Number(_deps.fs.constants.O_NOFOLLOW || 0),
|
|
685
|
+
0o600,
|
|
686
|
+
);
|
|
687
|
+
const digest = crypto.createHash("sha256");
|
|
688
|
+
const chunk = Buffer.allocUnsafe(
|
|
689
|
+
Math.max(1, Math.min(record.bytes, HASH_CHUNK_BYTES)),
|
|
690
|
+
);
|
|
691
|
+
let offset = 0;
|
|
692
|
+
while (offset < record.bytes) {
|
|
693
|
+
const count = _deps.fs.readSync(
|
|
694
|
+
sourceDescriptor,
|
|
695
|
+
chunk,
|
|
696
|
+
0,
|
|
697
|
+
Math.min(chunk.length, record.bytes - offset),
|
|
698
|
+
offset,
|
|
699
|
+
);
|
|
700
|
+
if (count <= 0) {
|
|
701
|
+
throw new Error(`MCP capsule source ended during copy: ${record.path}`);
|
|
702
|
+
}
|
|
703
|
+
digest.update(chunk.subarray(0, count));
|
|
704
|
+
let written = 0;
|
|
705
|
+
while (written < count) {
|
|
706
|
+
const writeCount = _deps.fs.writeSync(
|
|
707
|
+
destinationDescriptor,
|
|
708
|
+
chunk,
|
|
709
|
+
written,
|
|
710
|
+
count - written,
|
|
711
|
+
offset + written,
|
|
712
|
+
);
|
|
713
|
+
if (writeCount <= 0) {
|
|
714
|
+
throw new Error(
|
|
715
|
+
`MCP capsule snapshot ended during copy: ${record.path}`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
written += writeCount;
|
|
719
|
+
}
|
|
720
|
+
offset += count;
|
|
721
|
+
}
|
|
722
|
+
const after = _deps.fs.fstatSync(sourceDescriptor, { bigint: true });
|
|
723
|
+
if (
|
|
724
|
+
after.dev !== before.dev ||
|
|
725
|
+
after.ino !== before.ino ||
|
|
726
|
+
after.size !== before.size ||
|
|
727
|
+
after.mtimeNs !== before.mtimeNs ||
|
|
728
|
+
after.ctimeNs !== before.ctimeNs ||
|
|
729
|
+
digest.digest("hex") !== record.sha256
|
|
730
|
+
) {
|
|
731
|
+
throw new Error(`MCP capsule source changed during copy: ${record.path}`);
|
|
732
|
+
}
|
|
733
|
+
_deps.fs.fchmodSync(destinationDescriptor, record.mode & 0o777);
|
|
734
|
+
_deps.fs.fsyncSync(destinationDescriptor);
|
|
735
|
+
} finally {
|
|
736
|
+
if (destinationDescriptor !== undefined) {
|
|
737
|
+
_deps.fs.closeSync(destinationDescriptor);
|
|
738
|
+
}
|
|
739
|
+
if (sourceDescriptor !== undefined) _deps.fs.closeSync(sourceDescriptor);
|
|
740
|
+
}
|
|
741
|
+
if (hashFile(destination, record.bytes) !== record.sha256) {
|
|
742
|
+
throw new Error(`MCP capsule snapshot copy is invalid: ${record.path}`);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
function createAttestedSnapshot(treeRoot, closure, staging) {
|
|
747
|
+
const snapshotRoot = path.join(
|
|
748
|
+
staging,
|
|
749
|
+
`.capsule-source-${crypto.randomUUID()}`,
|
|
750
|
+
);
|
|
751
|
+
_deps.fs.mkdirSync(snapshotRoot, { recursive: false, mode: 0o700 });
|
|
752
|
+
for (const record of closure.files) {
|
|
753
|
+
copyAttestedFile(treeRoot, snapshotRoot, record);
|
|
754
|
+
}
|
|
755
|
+
const observed = collectTree(snapshotRoot);
|
|
756
|
+
if (
|
|
757
|
+
observed.fileCount !== closure.fileCount ||
|
|
758
|
+
observed.totalBytes !== closure.totalBytes ||
|
|
759
|
+
observed.closureDigest !== closure.closureDigest ||
|
|
760
|
+
canonicalJson(observed.files) !== canonicalJson(closure.files)
|
|
761
|
+
) {
|
|
762
|
+
throw new Error(
|
|
763
|
+
"MCP capsule source snapshot does not match its attestation",
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
return snapshotRoot;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
function resolveCapsuleBuilderBinary() {
|
|
770
|
+
const platform = `${process.platform}-${process.arch}`;
|
|
771
|
+
const expected = CAPSULE_BUILDER_BINARIES[platform];
|
|
772
|
+
if (!expected) {
|
|
773
|
+
throw new Error(`MCP capsule builder does not support ${platform}`);
|
|
774
|
+
}
|
|
775
|
+
const packageJsonPath = require.resolve(
|
|
776
|
+
`${expected.packageName}/package.json`,
|
|
777
|
+
);
|
|
778
|
+
const packageJson = JSON.parse(
|
|
779
|
+
_deps.fs.readFileSync(packageJsonPath, "utf8"),
|
|
780
|
+
);
|
|
781
|
+
if (
|
|
782
|
+
packageJson.name !== expected.packageName ||
|
|
783
|
+
packageJson.version !== CAPSULE_BUILDER_VERSION
|
|
784
|
+
) {
|
|
785
|
+
throw new Error(
|
|
786
|
+
`MCP capsule builder must be ${expected.packageName}@${CAPSULE_BUILDER_VERSION}`,
|
|
787
|
+
);
|
|
788
|
+
}
|
|
789
|
+
const candidate = path.join(
|
|
790
|
+
path.dirname(packageJsonPath),
|
|
791
|
+
...expected.relativePath.split("/"),
|
|
792
|
+
);
|
|
793
|
+
const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
|
|
794
|
+
const binary = realpath(candidate);
|
|
795
|
+
const stat = _deps.fs.lstatSync(binary);
|
|
796
|
+
if (
|
|
797
|
+
stat.isSymbolicLink() ||
|
|
798
|
+
!stat.isFile() ||
|
|
799
|
+
stat.size <= 0 ||
|
|
800
|
+
stat.size > MAX_FILE_BYTES ||
|
|
801
|
+
hashFile(binary, stat.size) !== expected.sha256
|
|
802
|
+
) {
|
|
803
|
+
throw new Error("MCP capsule builder binary identity is invalid");
|
|
804
|
+
}
|
|
805
|
+
return Object.freeze({
|
|
806
|
+
binary,
|
|
807
|
+
platform,
|
|
808
|
+
sha256: expected.sha256,
|
|
809
|
+
});
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function capsuleRuntimeGuard() {
|
|
813
|
+
return `;(() => {
|
|
814
|
+
const Module = require("node:module");
|
|
815
|
+
const allowed = new Set(Module.builtinModules.flatMap((name) => [name, name.startsWith("node:") ? name : "node:" + name]));
|
|
816
|
+
const originalLoad = Module._load;
|
|
817
|
+
const blocked = (kind, request) => {
|
|
818
|
+
const error = new Error("MCP stdio capsule blocked " + kind + (request === undefined ? "" : ": " + String(request)));
|
|
819
|
+
error.code = kind === "native module loading" ? "CC_MCP_STDIO_NATIVE_MODULE_BLOCKED" : "CC_MCP_STDIO_EXTERNAL_MODULE_BLOCKED";
|
|
820
|
+
throw error;
|
|
821
|
+
};
|
|
822
|
+
Object.defineProperty(Module, "_load", {
|
|
823
|
+
configurable: false,
|
|
824
|
+
enumerable: false,
|
|
825
|
+
writable: false,
|
|
826
|
+
value: function(request) {
|
|
827
|
+
if (typeof request !== "string" || !allowed.has(request)) blocked("external module loading", request);
|
|
828
|
+
return Reflect.apply(originalLoad, this, arguments);
|
|
829
|
+
},
|
|
830
|
+
});
|
|
831
|
+
Object.defineProperty(process, "dlopen", {
|
|
832
|
+
configurable: false,
|
|
833
|
+
enumerable: false,
|
|
834
|
+
writable: false,
|
|
835
|
+
value: function() { blocked("native module loading"); },
|
|
836
|
+
});
|
|
837
|
+
})();`;
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
function buildCapsule({
|
|
841
|
+
treeRoot,
|
|
842
|
+
entrypointRelative,
|
|
843
|
+
closure,
|
|
844
|
+
staging,
|
|
845
|
+
processBrokerRunSync,
|
|
846
|
+
env,
|
|
847
|
+
}) {
|
|
848
|
+
const snapshotRoot = createAttestedSnapshot(treeRoot, closure, staging);
|
|
849
|
+
const capsuleRoot = path.join(staging, "capsule");
|
|
850
|
+
const capsulePath = path.join(staging, ...CAPSULE_RELATIVE_PATH.split("/"));
|
|
851
|
+
const metafilePath = path.join(
|
|
852
|
+
staging,
|
|
853
|
+
`.capsule-meta-${crypto.randomUUID()}.json`,
|
|
854
|
+
);
|
|
855
|
+
const realpath = _deps.fs.realpathSync.native || _deps.fs.realpathSync;
|
|
856
|
+
const canonicalSnapshotRoot = realpath(snapshotRoot);
|
|
857
|
+
_deps.fs.mkdirSync(capsuleRoot, { recursive: false, mode: 0o700 });
|
|
858
|
+
try {
|
|
859
|
+
const snapshotEntrypoint = resolveContainedPath(
|
|
860
|
+
snapshotRoot,
|
|
861
|
+
entrypointRelative,
|
|
862
|
+
"MCP capsule entrypoint",
|
|
863
|
+
);
|
|
864
|
+
const canonicalEntrypointRelative = path
|
|
865
|
+
.relative(canonicalSnapshotRoot, realpath(snapshotEntrypoint))
|
|
866
|
+
.split(path.sep)
|
|
867
|
+
.join("/");
|
|
868
|
+
if (canonicalEntrypointRelative !== entrypointRelative) {
|
|
869
|
+
throw new Error("MCP capsule entrypoint changed through a path alias");
|
|
870
|
+
}
|
|
871
|
+
const builderEntrypoint = `.${path.sep}${entrypointRelative
|
|
872
|
+
.split("/")
|
|
873
|
+
.join(path.sep)}`;
|
|
874
|
+
const builder = resolveCapsuleBuilderBinary();
|
|
875
|
+
const runThroughProcessBroker =
|
|
876
|
+
processBrokerRunSync || _deps.processBrokerRunSync;
|
|
877
|
+
if (typeof runThroughProcessBroker !== "function") {
|
|
878
|
+
throw new Error(
|
|
879
|
+
"MCP capsule construction requires a host-owned Process Broker runner",
|
|
880
|
+
);
|
|
881
|
+
}
|
|
882
|
+
const result = runThroughProcessBroker(
|
|
883
|
+
builder.binary,
|
|
884
|
+
[
|
|
885
|
+
builderEntrypoint,
|
|
886
|
+
"--bundle",
|
|
887
|
+
"--charset=utf8",
|
|
888
|
+
"--format=cjs",
|
|
889
|
+
"--legal-comments=none",
|
|
890
|
+
"--log-level=warning",
|
|
891
|
+
`--metafile=${metafilePath}`,
|
|
892
|
+
`--outfile=${capsulePath}`,
|
|
893
|
+
"--packages=bundle",
|
|
894
|
+
"--platform=node",
|
|
895
|
+
"--preserve-symlinks",
|
|
896
|
+
"--supported:dynamic-import=false",
|
|
897
|
+
"--target=node22",
|
|
898
|
+
"--tree-shaking=false",
|
|
899
|
+
`--banner:js=${capsuleRuntimeGuard()}`,
|
|
900
|
+
],
|
|
901
|
+
{
|
|
902
|
+
cwd: canonicalSnapshotRoot,
|
|
903
|
+
env: sanitizeMcpStdioHostEnvironment(env),
|
|
904
|
+
encoding: "utf8",
|
|
905
|
+
shell: false,
|
|
906
|
+
windowsHide: true,
|
|
907
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
908
|
+
},
|
|
909
|
+
);
|
|
910
|
+
const stderr = String(result?.stderr || "").trim();
|
|
911
|
+
if (result?.error || result?.status !== 0 || stderr) {
|
|
912
|
+
const detail = String(
|
|
913
|
+
result?.error?.message || stderr || "esbuild failed",
|
|
914
|
+
)
|
|
915
|
+
.trim()
|
|
916
|
+
.slice(0, 2000);
|
|
917
|
+
throw new Error(`MCP capsule build failed closed: ${detail}`);
|
|
918
|
+
}
|
|
919
|
+
const metafile = JSON.parse(_deps.fs.readFileSync(metafilePath, "utf8"));
|
|
920
|
+
if (
|
|
921
|
+
!metafile ||
|
|
922
|
+
typeof metafile !== "object" ||
|
|
923
|
+
!metafile.inputs ||
|
|
924
|
+
typeof metafile.inputs !== "object" ||
|
|
925
|
+
!metafile.outputs ||
|
|
926
|
+
typeof metafile.outputs !== "object"
|
|
927
|
+
) {
|
|
928
|
+
throw new Error("MCP capsule build metadata is invalid");
|
|
929
|
+
}
|
|
930
|
+
const sourceByPath = new Map(
|
|
931
|
+
closure.files.map((record) => [record.path, record]),
|
|
932
|
+
);
|
|
933
|
+
const inputs = Object.keys(metafile.inputs)
|
|
934
|
+
.map((input) => input.split(path.sep).join("/"))
|
|
935
|
+
.sort()
|
|
936
|
+
.map((input) => {
|
|
937
|
+
const absolute = realpath(
|
|
938
|
+
path.resolve(snapshotRoot, ...input.split("/")),
|
|
939
|
+
);
|
|
940
|
+
const relative = path
|
|
941
|
+
.relative(canonicalSnapshotRoot, absolute)
|
|
942
|
+
.split(path.sep)
|
|
943
|
+
.join("/");
|
|
944
|
+
const record = sourceByPath.get(relative);
|
|
945
|
+
if (!record) {
|
|
946
|
+
throw new Error(
|
|
947
|
+
`MCP capsule build used an unattested input: ${input}`,
|
|
948
|
+
);
|
|
949
|
+
}
|
|
950
|
+
return {
|
|
951
|
+
path: relative,
|
|
952
|
+
bytes: record.bytes,
|
|
953
|
+
sha256: record.sha256,
|
|
954
|
+
};
|
|
955
|
+
});
|
|
956
|
+
if (new Set(inputs.map((input) => input.path)).size !== inputs.length) {
|
|
957
|
+
throw new Error("MCP capsule build reported duplicate input aliases");
|
|
958
|
+
}
|
|
959
|
+
if (
|
|
960
|
+
inputs.length === 0 ||
|
|
961
|
+
!inputs.some((input) => input.path === entrypointRelative)
|
|
962
|
+
) {
|
|
963
|
+
throw new Error("MCP capsule build did not bind its approved entrypoint");
|
|
964
|
+
}
|
|
965
|
+
const externalBuiltins = [];
|
|
966
|
+
for (const output of Object.values(metafile.outputs)) {
|
|
967
|
+
for (const imported of output.imports || []) {
|
|
968
|
+
if (!imported.external || !NODE_BUILTINS.has(imported.path)) {
|
|
969
|
+
throw new Error(
|
|
970
|
+
`MCP capsule build retained an external dependency: ${imported.path}`,
|
|
971
|
+
);
|
|
972
|
+
}
|
|
973
|
+
externalBuiltins.push(imported.path);
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
const postBuildClosure = collectTree(treeRoot);
|
|
977
|
+
if (
|
|
978
|
+
postBuildClosure.fileCount !== closure.fileCount ||
|
|
979
|
+
postBuildClosure.totalBytes !== closure.totalBytes ||
|
|
980
|
+
postBuildClosure.closureDigest !== closure.closureDigest ||
|
|
981
|
+
canonicalJson(postBuildClosure.files) !== canonicalJson(closure.files)
|
|
982
|
+
) {
|
|
983
|
+
throw new Error("MCP package dependency closure changed during bundling");
|
|
984
|
+
}
|
|
985
|
+
const capsuleClosure = collectTree(capsuleRoot);
|
|
986
|
+
if (
|
|
987
|
+
capsuleClosure.fileCount !== 1 ||
|
|
988
|
+
capsuleClosure.files[0]?.path !== path.basename(capsulePath)
|
|
989
|
+
) {
|
|
990
|
+
throw new Error("MCP capsule output is not one regular file");
|
|
991
|
+
}
|
|
992
|
+
return Object.freeze({
|
|
993
|
+
schema: CAPSULE_SCHEMA,
|
|
994
|
+
relativePath: CAPSULE_RELATIVE_PATH,
|
|
995
|
+
sha256: capsuleClosure.files[0].sha256,
|
|
996
|
+
bytes: capsuleClosure.files[0].bytes,
|
|
997
|
+
closureDigest: capsuleClosure.closureDigest,
|
|
998
|
+
builder: CAPSULE_BUILDER,
|
|
999
|
+
builderVersion: CAPSULE_BUILDER_VERSION,
|
|
1000
|
+
builderPlatform: builder.platform,
|
|
1001
|
+
builderBinarySha256: builder.sha256,
|
|
1002
|
+
nodeTarget: "node22",
|
|
1003
|
+
inputCount: inputs.length,
|
|
1004
|
+
inputDigest: sha256(canonicalJson(inputs)),
|
|
1005
|
+
externalBuiltins: Object.freeze([...new Set(externalBuiltins)].sort()),
|
|
1006
|
+
});
|
|
1007
|
+
} finally {
|
|
1008
|
+
if (_deps.fs.existsSync(metafilePath)) _deps.fs.rmSync(metafilePath);
|
|
1009
|
+
_deps.fs.rmSync(snapshotRoot, { recursive: true, force: true });
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
function assertSafeGeneration(root, generation) {
|
|
1014
|
+
if (typeof generation !== "string" || !/^[a-f0-9]{64}$/.test(generation)) {
|
|
1015
|
+
throw new TypeError("MCP materialization generation is invalid");
|
|
1016
|
+
}
|
|
1017
|
+
const directory = path.resolve(root, generation);
|
|
1018
|
+
const relative = path.relative(path.resolve(root), directory);
|
|
1019
|
+
if (relative !== generation || path.isAbsolute(relative)) {
|
|
1020
|
+
throw new TypeError("MCP materialization generation escapes its root");
|
|
1021
|
+
}
|
|
1022
|
+
return directory;
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function validateManifest(manifest) {
|
|
1026
|
+
if (
|
|
1027
|
+
manifest?.schema !== MATERIALIZATION_SCHEMA ||
|
|
1028
|
+
manifest.version !== MATERIALIZATION_VERSION ||
|
|
1029
|
+
typeof manifest.sourceFingerprint !== "string" ||
|
|
1030
|
+
!/^[a-f0-9]{64}$/.test(manifest.sourceFingerprint) ||
|
|
1031
|
+
typeof manifest.generation !== "string" ||
|
|
1032
|
+
!/^[a-f0-9]{64}$/.test(manifest.generation) ||
|
|
1033
|
+
typeof manifest.package?.name !== "string" ||
|
|
1034
|
+
typeof manifest.package?.version !== "string" ||
|
|
1035
|
+
!EXACT_VERSION.test(manifest.package.version) ||
|
|
1036
|
+
typeof manifest.lockSha256 !== "string" ||
|
|
1037
|
+
!/^[a-f0-9]{64}$/.test(manifest.lockSha256) ||
|
|
1038
|
+
typeof manifest.entrypointRelative !== "string" ||
|
|
1039
|
+
manifest.capsule?.schema !== CAPSULE_SCHEMA ||
|
|
1040
|
+
manifest.capsule?.relativePath !== CAPSULE_RELATIVE_PATH ||
|
|
1041
|
+
typeof manifest.capsule?.sha256 !== "string" ||
|
|
1042
|
+
!/^[a-f0-9]{64}$/.test(manifest.capsule.sha256) ||
|
|
1043
|
+
!Number.isSafeInteger(manifest.capsule?.bytes) ||
|
|
1044
|
+
manifest.capsule.bytes <= 0 ||
|
|
1045
|
+
manifest.capsule.bytes > MAX_FILE_BYTES ||
|
|
1046
|
+
typeof manifest.capsule?.closureDigest !== "string" ||
|
|
1047
|
+
!/^[a-f0-9]{64}$/.test(manifest.capsule.closureDigest) ||
|
|
1048
|
+
manifest.capsule?.builder !== CAPSULE_BUILDER ||
|
|
1049
|
+
manifest.capsule?.builderVersion !== CAPSULE_BUILDER_VERSION ||
|
|
1050
|
+
typeof manifest.capsule?.builderPlatform !== "string" ||
|
|
1051
|
+
!CAPSULE_BUILDER_BINARIES[manifest.capsule.builderPlatform] ||
|
|
1052
|
+
manifest.capsule?.builderBinarySha256 !==
|
|
1053
|
+
CAPSULE_BUILDER_BINARIES[manifest.capsule.builderPlatform]?.sha256 ||
|
|
1054
|
+
manifest.capsule?.nodeTarget !== "node22" ||
|
|
1055
|
+
!Number.isSafeInteger(manifest.capsule?.inputCount) ||
|
|
1056
|
+
manifest.capsule.inputCount <= 0 ||
|
|
1057
|
+
typeof manifest.capsule?.inputDigest !== "string" ||
|
|
1058
|
+
!/^[a-f0-9]{64}$/.test(manifest.capsule.inputDigest) ||
|
|
1059
|
+
!Array.isArray(manifest.capsule?.externalBuiltins) ||
|
|
1060
|
+
manifest.capsule.externalBuiltins.some(
|
|
1061
|
+
(name) => typeof name !== "string" || !NODE_BUILTINS.has(name),
|
|
1062
|
+
) ||
|
|
1063
|
+
!Array.isArray(manifest.files) ||
|
|
1064
|
+
!Number.isSafeInteger(manifest.fileCount) ||
|
|
1065
|
+
!Number.isSafeInteger(manifest.totalBytes) ||
|
|
1066
|
+
typeof manifest.closureDigest !== "string" ||
|
|
1067
|
+
!/^[a-f0-9]{64}$/.test(manifest.closureDigest)
|
|
1068
|
+
) {
|
|
1069
|
+
throw materializationError(
|
|
1070
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
1071
|
+
"MCP package materialization manifest is invalid",
|
|
1072
|
+
);
|
|
1073
|
+
}
|
|
1074
|
+
if (
|
|
1075
|
+
manifest.fileCount !== manifest.files.length ||
|
|
1076
|
+
manifest.files.length > MAX_FILES ||
|
|
1077
|
+
manifest.totalBytes > MAX_TOTAL_BYTES ||
|
|
1078
|
+
manifest.capsule.inputCount > manifest.fileCount ||
|
|
1079
|
+
canonicalJson(manifest.capsule.externalBuiltins) !==
|
|
1080
|
+
canonicalJson([...new Set(manifest.capsule.externalBuiltins)].sort()) ||
|
|
1081
|
+
sha256(canonicalJson(manifest.files)) !== manifest.closureDigest
|
|
1082
|
+
) {
|
|
1083
|
+
throw materializationError(
|
|
1084
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
1085
|
+
"MCP package materialization manifest closure is invalid",
|
|
1086
|
+
);
|
|
1087
|
+
}
|
|
1088
|
+
return manifest;
|
|
1089
|
+
}
|
|
1090
|
+
|
|
1091
|
+
function verifyPublishedGeneration(root, record, expectedFingerprint) {
|
|
1092
|
+
const generationRoot = assertSafeGeneration(root, record.generation);
|
|
1093
|
+
const manifestPath = path.join(generationRoot, "manifest.json");
|
|
1094
|
+
const manifest = validateManifest(readJson(manifestPath, MANIFEST_LABEL));
|
|
1095
|
+
if (
|
|
1096
|
+
manifest.generation !== record.generation ||
|
|
1097
|
+
manifest.sourceFingerprint !== expectedFingerprint ||
|
|
1098
|
+
sha256(canonicalJson(manifest)) !== record.manifestDigest
|
|
1099
|
+
) {
|
|
1100
|
+
throw materializationError(
|
|
1101
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1102
|
+
"MCP package materialization authority or manifest changed",
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
const treeRoot = path.join(generationRoot, "tree");
|
|
1106
|
+
let observed;
|
|
1107
|
+
try {
|
|
1108
|
+
observed = collectTree(treeRoot);
|
|
1109
|
+
} catch (cause) {
|
|
1110
|
+
throw materializationError(
|
|
1111
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1112
|
+
`MCP package materialization could not be re-attested: ${cause.message}`,
|
|
1113
|
+
{ cause },
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
if (
|
|
1117
|
+
observed.fileCount !== manifest.fileCount ||
|
|
1118
|
+
observed.totalBytes !== manifest.totalBytes ||
|
|
1119
|
+
observed.closureDigest !== manifest.closureDigest ||
|
|
1120
|
+
canonicalJson(observed.files) !== canonicalJson(manifest.files)
|
|
1121
|
+
) {
|
|
1122
|
+
throw materializationError(
|
|
1123
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1124
|
+
"MCP package materialization dependency closure changed",
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
let sourceEntrypoint;
|
|
1128
|
+
let entrypoint;
|
|
1129
|
+
try {
|
|
1130
|
+
sourceEntrypoint = resolveContainedPath(
|
|
1131
|
+
treeRoot,
|
|
1132
|
+
manifest.entrypointRelative,
|
|
1133
|
+
"MCP package materialization source entrypoint",
|
|
1134
|
+
);
|
|
1135
|
+
entrypoint = resolveContainedPath(
|
|
1136
|
+
generationRoot,
|
|
1137
|
+
manifest.capsule.relativePath,
|
|
1138
|
+
"MCP package materialization capsule",
|
|
1139
|
+
);
|
|
1140
|
+
} catch (cause) {
|
|
1141
|
+
throw materializationError(
|
|
1142
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
1143
|
+
cause.message,
|
|
1144
|
+
{ cause },
|
|
1145
|
+
);
|
|
1146
|
+
}
|
|
1147
|
+
if (
|
|
1148
|
+
!manifest.files.some(
|
|
1149
|
+
(file) =>
|
|
1150
|
+
file.path === manifest.entrypointRelative &&
|
|
1151
|
+
resolveContainedPath(
|
|
1152
|
+
treeRoot,
|
|
1153
|
+
file.path,
|
|
1154
|
+
"MCP package materialization source file",
|
|
1155
|
+
) === sourceEntrypoint,
|
|
1156
|
+
)
|
|
1157
|
+
) {
|
|
1158
|
+
throw materializationError(
|
|
1159
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
1160
|
+
"MCP package materialization source entrypoint is outside its closure",
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
const capsuleRoot = path.dirname(entrypoint);
|
|
1164
|
+
let capsuleClosure;
|
|
1165
|
+
try {
|
|
1166
|
+
capsuleClosure = collectTree(capsuleRoot);
|
|
1167
|
+
} catch (cause) {
|
|
1168
|
+
throw materializationError(
|
|
1169
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1170
|
+
`MCP package capsule could not be re-attested: ${cause.message}`,
|
|
1171
|
+
{ cause },
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (
|
|
1175
|
+
capsuleClosure.fileCount !== 1 ||
|
|
1176
|
+
capsuleClosure.totalBytes !== manifest.capsule.bytes ||
|
|
1177
|
+
capsuleClosure.closureDigest !== manifest.capsule.closureDigest ||
|
|
1178
|
+
capsuleClosure.files[0]?.path !== path.basename(entrypoint) ||
|
|
1179
|
+
capsuleClosure.files[0]?.sha256 !== manifest.capsule.sha256
|
|
1180
|
+
) {
|
|
1181
|
+
throw materializationError(
|
|
1182
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1183
|
+
"MCP package capsule changed",
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
return Object.freeze({
|
|
1187
|
+
generationRoot,
|
|
1188
|
+
treeRoot,
|
|
1189
|
+
capsuleRoot,
|
|
1190
|
+
entrypoint,
|
|
1191
|
+
manifest: Object.freeze(manifest),
|
|
1192
|
+
identity: Object.freeze({
|
|
1193
|
+
schema: MATERIALIZATION_SCHEMA,
|
|
1194
|
+
sourceFingerprint: manifest.sourceFingerprint,
|
|
1195
|
+
generation: manifest.generation,
|
|
1196
|
+
package: Object.freeze({ ...manifest.package }),
|
|
1197
|
+
binName: manifest.binName,
|
|
1198
|
+
lockSha256: manifest.lockSha256,
|
|
1199
|
+
packageCount: manifest.packageCount,
|
|
1200
|
+
entrypointRelative: manifest.entrypointRelative,
|
|
1201
|
+
capsule: Object.freeze({
|
|
1202
|
+
...manifest.capsule,
|
|
1203
|
+
externalBuiltins: Object.freeze([...manifest.capsule.externalBuiltins]),
|
|
1204
|
+
}),
|
|
1205
|
+
closureDigest: manifest.closureDigest,
|
|
1206
|
+
fileCount: manifest.fileCount,
|
|
1207
|
+
totalBytes: manifest.totalBytes,
|
|
1208
|
+
}),
|
|
1209
|
+
});
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
export function materializeMcpStdioNpmPackage({
|
|
1213
|
+
approvalRecord,
|
|
1214
|
+
config,
|
|
1215
|
+
packageSpec,
|
|
1216
|
+
binName,
|
|
1217
|
+
root: explicitRoot,
|
|
1218
|
+
indexPath: explicitIndexPath,
|
|
1219
|
+
npmCli: explicitNpmCli,
|
|
1220
|
+
installRunner = defaultInstallRunner,
|
|
1221
|
+
processBrokerRunSync,
|
|
1222
|
+
env = process.env,
|
|
1223
|
+
now = Date.now(),
|
|
1224
|
+
}) {
|
|
1225
|
+
if (
|
|
1226
|
+
!approvalRecord ||
|
|
1227
|
+
typeof approvalRecord.fingerprint !== "string" ||
|
|
1228
|
+
!/^[a-f0-9]{64}$/.test(approvalRecord.fingerprint)
|
|
1229
|
+
) {
|
|
1230
|
+
throw materializationError(
|
|
1231
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_INVALID_CODE,
|
|
1232
|
+
"MCP package materialization requires a valid execution approval",
|
|
1233
|
+
);
|
|
1234
|
+
}
|
|
1235
|
+
const source = parseNpmPackageLauncherInvocation(config, packageSpec);
|
|
1236
|
+
const root = getMcpStdioPackageMaterializationRoot({ root: explicitRoot });
|
|
1237
|
+
const indexPath = getMcpStdioPackageMaterializationIndexPath({
|
|
1238
|
+
indexPath: explicitIndexPath,
|
|
1239
|
+
});
|
|
1240
|
+
_deps.fs.mkdirSync(root, { recursive: true, mode: 0o700 });
|
|
1241
|
+
const staging = path.join(root, `.staging-${crypto.randomUUID()}`);
|
|
1242
|
+
const treeRoot = path.join(staging, "tree");
|
|
1243
|
+
let published = false;
|
|
1244
|
+
try {
|
|
1245
|
+
_deps.fs.mkdirSync(treeRoot, { recursive: true, mode: 0o700 });
|
|
1246
|
+
writeSecurityStore(
|
|
1247
|
+
path.join(treeRoot, "package.json"),
|
|
1248
|
+
"MCP npm materialization root package",
|
|
1249
|
+
{
|
|
1250
|
+
name: "chainlesschain-mcp-materialization",
|
|
1251
|
+
version: "1.0.0",
|
|
1252
|
+
private: true,
|
|
1253
|
+
dependencies: { [source.name]: source.version },
|
|
1254
|
+
},
|
|
1255
|
+
);
|
|
1256
|
+
installRunner({
|
|
1257
|
+
directory: treeRoot,
|
|
1258
|
+
packageSpec: source.spec,
|
|
1259
|
+
npmCli: resolveNpmCli({ npmCli: explicitNpmCli }),
|
|
1260
|
+
env,
|
|
1261
|
+
processBrokerRunSync,
|
|
1262
|
+
});
|
|
1263
|
+
const lock = validateNpmLock(treeRoot, source);
|
|
1264
|
+
const entry = resolvePackageEntrypoint(treeRoot, source, binName);
|
|
1265
|
+
const closure = collectTree(treeRoot);
|
|
1266
|
+
const capsule = buildCapsule({
|
|
1267
|
+
treeRoot,
|
|
1268
|
+
entrypointRelative: entry.entrypointRelative,
|
|
1269
|
+
closure,
|
|
1270
|
+
staging,
|
|
1271
|
+
processBrokerRunSync,
|
|
1272
|
+
env,
|
|
1273
|
+
});
|
|
1274
|
+
const generation = sha256(
|
|
1275
|
+
canonicalJson({
|
|
1276
|
+
sourceFingerprint: approvalRecord.fingerprint,
|
|
1277
|
+
package: source.spec,
|
|
1278
|
+
binName: entry.binName,
|
|
1279
|
+
passthroughArgs: source.passthroughArgs,
|
|
1280
|
+
lockSha256: lock.lockSha256,
|
|
1281
|
+
closureDigest: closure.closureDigest,
|
|
1282
|
+
capsule,
|
|
1283
|
+
}),
|
|
1284
|
+
);
|
|
1285
|
+
const manifest = {
|
|
1286
|
+
schema: MATERIALIZATION_SCHEMA,
|
|
1287
|
+
version: MATERIALIZATION_VERSION,
|
|
1288
|
+
generation,
|
|
1289
|
+
sourceFingerprint: approvalRecord.fingerprint,
|
|
1290
|
+
package: { name: source.name, version: source.version },
|
|
1291
|
+
binName: entry.binName,
|
|
1292
|
+
passthroughArgs: [...source.passthroughArgs],
|
|
1293
|
+
lockSha256: lock.lockSha256,
|
|
1294
|
+
packageCount: lock.packageCount,
|
|
1295
|
+
entrypointRelative: entry.entrypointRelative,
|
|
1296
|
+
capsule,
|
|
1297
|
+
closureDigest: closure.closureDigest,
|
|
1298
|
+
fileCount: closure.fileCount,
|
|
1299
|
+
totalBytes: closure.totalBytes,
|
|
1300
|
+
files: closure.files,
|
|
1301
|
+
};
|
|
1302
|
+
const manifestDigest = sha256(canonicalJson(manifest));
|
|
1303
|
+
writeSecurityStore(
|
|
1304
|
+
path.join(staging, "manifest.json"),
|
|
1305
|
+
MANIFEST_LABEL,
|
|
1306
|
+
manifest,
|
|
1307
|
+
);
|
|
1308
|
+
const generationRoot = assertSafeGeneration(root, generation);
|
|
1309
|
+
try {
|
|
1310
|
+
_deps.fs.renameSync(staging, generationRoot);
|
|
1311
|
+
published = true;
|
|
1312
|
+
} catch (cause) {
|
|
1313
|
+
// Windows reports EPERM rather than EEXIST when the destination
|
|
1314
|
+
// generation already exists. Never classify by errno alone: only an
|
|
1315
|
+
// actually present destination may enter the exact verification path.
|
|
1316
|
+
if (!_deps.fs.existsSync(generationRoot)) throw cause;
|
|
1317
|
+
}
|
|
1318
|
+
const record = {
|
|
1319
|
+
generation,
|
|
1320
|
+
manifestDigest,
|
|
1321
|
+
packageSpec: source.spec,
|
|
1322
|
+
materializedAt: new Date(now).toISOString(),
|
|
1323
|
+
};
|
|
1324
|
+
const verified = verifyPublishedGeneration(
|
|
1325
|
+
root,
|
|
1326
|
+
record,
|
|
1327
|
+
approvalRecord.fingerprint,
|
|
1328
|
+
);
|
|
1329
|
+
mutateSecurityStore(indexPath, INDEX_LABEL, (index) => {
|
|
1330
|
+
index[approvalRecord.fingerprint] = record;
|
|
1331
|
+
});
|
|
1332
|
+
return Object.freeze({
|
|
1333
|
+
status: "materialized",
|
|
1334
|
+
generation,
|
|
1335
|
+
manifestDigest,
|
|
1336
|
+
root: verified.generationRoot,
|
|
1337
|
+
identity: verified.identity,
|
|
1338
|
+
});
|
|
1339
|
+
} catch (cause) {
|
|
1340
|
+
if (cause?.code?.startsWith?.("CC_MCP_STDIO_PACKAGE_")) throw cause;
|
|
1341
|
+
throw materializationError(
|
|
1342
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_FAILED_CODE,
|
|
1343
|
+
`MCP fixed package materialization failed: ${cause.message}`,
|
|
1344
|
+
{ cause },
|
|
1345
|
+
);
|
|
1346
|
+
} finally {
|
|
1347
|
+
if (!published && _deps.fs.existsSync(staging)) {
|
|
1348
|
+
_deps.fs.rmSync(staging, { recursive: true, force: true });
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
export function resolveMcpStdioPackageMaterialization({
|
|
1354
|
+
approvalRecord,
|
|
1355
|
+
root: explicitRoot,
|
|
1356
|
+
indexPath: explicitIndexPath,
|
|
1357
|
+
}) {
|
|
1358
|
+
const fingerprint = approvalRecord?.fingerprint;
|
|
1359
|
+
if (typeof fingerprint !== "string" || !/^[a-f0-9]{64}$/.test(fingerprint)) {
|
|
1360
|
+
throw materializationError(
|
|
1361
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_REQUIRED_CODE,
|
|
1362
|
+
"MCP dynamic launcher requires a valid fixed package materialization approval",
|
|
1363
|
+
);
|
|
1364
|
+
}
|
|
1365
|
+
const indexPath = getMcpStdioPackageMaterializationIndexPath({
|
|
1366
|
+
indexPath: explicitIndexPath,
|
|
1367
|
+
});
|
|
1368
|
+
const index = readSecurityStore(indexPath, INDEX_LABEL);
|
|
1369
|
+
const record = index[fingerprint];
|
|
1370
|
+
if (!record) {
|
|
1371
|
+
throw materializationError(
|
|
1372
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_REQUIRED_CODE,
|
|
1373
|
+
"MCP dynamic launcher has no fixed package generation; run 'cc mcp materialize-package <name>'",
|
|
1374
|
+
);
|
|
1375
|
+
}
|
|
1376
|
+
const root = getMcpStdioPackageMaterializationRoot({ root: explicitRoot });
|
|
1377
|
+
const verified = verifyPublishedGeneration(root, record, fingerprint);
|
|
1378
|
+
return Object.freeze({
|
|
1379
|
+
command: process.execPath,
|
|
1380
|
+
args: Object.freeze([
|
|
1381
|
+
verified.entrypoint,
|
|
1382
|
+
...(verified.manifest.passthroughArgs || []),
|
|
1383
|
+
]),
|
|
1384
|
+
identity: verified.identity,
|
|
1385
|
+
generationRoot: verified.generationRoot,
|
|
1386
|
+
treeRoot: verified.treeRoot,
|
|
1387
|
+
capsuleRoot: verified.capsuleRoot,
|
|
1388
|
+
manifestDigest: record.manifestDigest,
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
export function reattestMcpStdioPackageMaterialization(materialization) {
|
|
1393
|
+
if (
|
|
1394
|
+
!materialization ||
|
|
1395
|
+
typeof materialization !== "object" ||
|
|
1396
|
+
typeof materialization.generationRoot !== "string" ||
|
|
1397
|
+
typeof materialization.manifestDigest !== "string" ||
|
|
1398
|
+
typeof materialization.identity?.generation !== "string"
|
|
1399
|
+
) {
|
|
1400
|
+
throw materializationError(
|
|
1401
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1402
|
+
"MCP package materialization launch authority is invalid",
|
|
1403
|
+
);
|
|
1404
|
+
}
|
|
1405
|
+
const root = path.dirname(materialization.generationRoot);
|
|
1406
|
+
const record = {
|
|
1407
|
+
generation: materialization.identity.generation,
|
|
1408
|
+
manifestDigest: materialization.manifestDigest,
|
|
1409
|
+
};
|
|
1410
|
+
const verified = verifyPublishedGeneration(
|
|
1411
|
+
root,
|
|
1412
|
+
record,
|
|
1413
|
+
materialization.identity.sourceFingerprint,
|
|
1414
|
+
);
|
|
1415
|
+
if (
|
|
1416
|
+
canonicalJson(verified.identity) !== canonicalJson(materialization.identity)
|
|
1417
|
+
) {
|
|
1418
|
+
throw materializationError(
|
|
1419
|
+
MCP_STDIO_PACKAGE_MATERIALIZATION_CHANGED_CODE,
|
|
1420
|
+
"MCP package materialization identity changed before spawn",
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
return verified;
|
|
1424
|
+
}
|