@fangorn-network/westmarch 2026.10.29 → 2026.11.4-dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (360) hide show
  1. package/README.md +5 -5
  2. package/docs/app-to-agent.md +61 -625
  3. package/package.json +12 -17
  4. package/site/dist/assets/__vite-browser-external-D2U8RZpp.js +1 -0
  5. package/site/dist/assets/embed.worker-CmS57Zti.js +1 -0
  6. package/site/dist/assets/index-Djs8f08m.js +8 -0
  7. package/site/dist/assets/rolldown-runtime-COiEfhsA.js +1 -0
  8. package/site/dist/assets/{transformers.web-BTyOEJ0C.js → transformers.web-CwR_IYQV.js} +1 -1
  9. package/site/dist/index.html +3 -4
  10. package/site/worker.js +115 -338
  11. package/src/agent/agent-card.js +33 -45
  12. package/src/agent/mcp.js +168 -371
  13. package/src/agent/tools.js +2 -1
  14. package/src/agent/view-tools.js +4 -56
  15. package/src/agent/x402.js +182 -369
  16. package/src/core/roles.js +6 -6
  17. package/src/core/shard.js +4 -4
  18. package/src/discover/apps.js +67 -81
  19. package/src/discover/directory.js +2 -2
  20. package/src/market/reactions.js +21 -15
  21. package/src/publish/devnet.js +69 -18
  22. package/src/publish/init.js +1 -1
  23. package/src/publish/ship.js +74 -275
  24. package/src/publish/view.js +52 -53
  25. package/template/README.md +9 -11
  26. package/template/package.json +1 -1
  27. package/plugin/scripts/fx.mjs +0 -266
  28. package/site/dist/assets/AccountNotFoundScreen-C4njXc69-DyVH4CEz.js +0 -1
  29. package/site/dist/assets/AddFundsSelectionScreen-RAoCn7ej-Dz1--SS9.js +0 -31
  30. package/site/dist/assets/Address-H8kD1d9O-BdFZmRwI.js +0 -17
  31. package/site/dist/assets/AffirmativeConsentScreen-BG0MJxku-CxaCAisL.js +0 -6
  32. package/site/dist/assets/AllowlistRejectionScreen-Cae6e1Od-JGk7vndN.js +0 -1
  33. package/site/dist/assets/ApiController-CTLbMCcH.js +0 -5
  34. package/site/dist/assets/ArrowsRightLeftIcon-B8g9adpz.js +0 -1
  35. package/site/dist/assets/AuthenticateWithWalletScreen-CU6hIcFT-CXD9_OP8.js +0 -1
  36. package/site/dist/assets/AwaitingEvmToSolBridgingScreen-D0e_sZMS-BtJCglvU.js +0 -1
  37. package/site/dist/assets/AwaitingExternalEthereumTransferScreen-ur5HL4R7-BgC2TsqH.js +0 -1
  38. package/site/dist/assets/AwaitingPasswordlessCodeScreen-DRHz76mM-B9OzLeEO.js +0 -103
  39. package/site/dist/assets/AwaitingSolToEvmBridgingScreen-CdLRrGD2-C7r5snUL.js +0 -1
  40. package/site/dist/assets/BridgeNetworkSelectionView-CFGjB2nO-BTV0NRio.js +0 -68
  41. package/site/dist/assets/CaptchaScreen-DbpvkIGx-7rSZIuM-.js +0 -1
  42. package/site/dist/assets/CheckCircleIcon-B1l17ioc.js +0 -1
  43. package/site/dist/assets/CheckCircleIcon-BGSI8nKf.js +0 -1
  44. package/site/dist/assets/Checkbox-D1EDeo41-BUfq0w99.js +0 -52
  45. package/site/dist/assets/ChevronDownIcon-B6uJvPHu.js +0 -1
  46. package/site/dist/assets/Chip-CZKIKt9K-r9Nk__0-.js +0 -26
  47. package/site/dist/assets/CoinbaseOnrampStatusScreen-BbyCJDXL-DWWNHxmR.js +0 -16
  48. package/site/dist/assets/ConnectEmailForm-B-VRE0MN-CRQwS0Xu.js +0 -3
  49. package/site/dist/assets/ConnectLedgerScreen-DWHABGot-CZcCt31O.js +0 -10
  50. package/site/dist/assets/ConnectOnlyLandingScreen-CZYx9Bmp-C5IOf-SQ.js +0 -1
  51. package/site/dist/assets/ConnectOnlyStatusScreen-DCRzjxHx-cwg74F3A.js +0 -1
  52. package/site/dist/assets/ConnectOrCreateScreen-CUUYUO07-D28oeK6b.js +0 -1
  53. package/site/dist/assets/ConnectPhoneForm-Dek5xn4C-EEdkKeTp.js +0 -95
  54. package/site/dist/assets/ConnectWalletView-DqI-PTvX-C2mEzukj.js +0 -74
  55. package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-CH_7r8iV.js +0 -7
  56. package/site/dist/assets/ConnectionStatusScreen-fUfROm9m-Z33kis0B.js +0 -1
  57. package/site/dist/assets/ConnectorUtil-DH4NePk5.js +0 -21
  58. package/site/dist/assets/ConstantsUtil-DmLsRHAl.js +0 -198
  59. package/site/dist/assets/CopyToClipboard-i_OQSBJr-_G88JCVR.js +0 -39
  60. package/site/dist/assets/CopyableText-CQapvaMr-CGGfc_zM.js +0 -28
  61. package/site/dist/assets/CrossAppAuthScreen-CLw7ymZq-C9Okoc0V.js +0 -1
  62. package/site/dist/assets/CustomLandingScreenView-Cm_MBSyX-D2l6hdSl.js +0 -22
  63. package/site/dist/assets/DelegatedActionsConsentScreen-DYF2U4y5-Dda3KKqS.js +0 -1
  64. package/site/dist/assets/DelegatedActionsRevokeScreen-bPp6Uwyh-DG8kJPt1.js +0 -1
  65. package/site/dist/assets/DepositAddressAddressStepView-4vdkb55L-CcOUfn6f.js +0 -187
  66. package/site/dist/assets/DepositAddressScreen-DBLRs7aI-DeituDNh.js +0 -8
  67. package/site/dist/assets/DepositAddressV3Screen-CDqIEMQw-FDg6K14-.js +0 -1
  68. package/site/dist/assets/EmailInputForm-Cqo1mda8-BmbxvPHC.js +0 -124
  69. package/site/dist/assets/EmbeddedWalletConnectingScreen-D4T7DUzs-B05CQ9rx.js +0 -1
  70. package/site/dist/assets/EmbeddedWalletCreatedScreen-D7WhW5ve-Cxd1b9XH.js +0 -1
  71. package/site/dist/assets/EmbeddedWalletKeyExportScreen-CqV4NYQ3-CrJy0ViX.js +0 -23
  72. package/site/dist/assets/EmbeddedWalletOnAccountCreateScreen-BoKb3Q2S-Bakaa3zu.js +0 -1
  73. package/site/dist/assets/EmbeddedWalletPasswordUpdateScreen-D3rMlqS7-j78vKrcj.js +0 -1
  74. package/site/dist/assets/EmbeddedWalletPasswordUpdateSplashScreen-BPFkWJeE-D3dr7W9_.js +0 -1
  75. package/site/dist/assets/EnrollTotp-Dibew4dp-BXbYV8nI.js +0 -66
  76. package/site/dist/assets/EnvelopeIcon-DjPofnRI.js +0 -1
  77. package/site/dist/assets/ErrorBanner-BcpGRt0h-BhH6_V-9.js +0 -21
  78. package/site/dist/assets/ErrorMessage-D8VaAP5m-Bs7uX2a9.js +0 -8
  79. package/site/dist/assets/ErrorScreen-DipdTWzE-BgPRgnOb.js +0 -4
  80. package/site/dist/assets/ErrorScreen-DipdTWzE-CvxXC6pz.js +0 -1
  81. package/site/dist/assets/ExclamationCircleIcon-BE-WGrut.js +0 -1
  82. package/site/dist/assets/ExclamationTriangleIcon-Ca1Xlc_f.js +0 -1
  83. package/site/dist/assets/FarcasterConnectStatusScreen-51tjUxqq-De2JvZdV.js +0 -75
  84. package/site/dist/assets/FarcasterSignerStatusScreen-6x-Uh95G-bLp3hXoO.js +0 -28
  85. package/site/dist/assets/FiatOnrampScreen-BAhYeiTI-Jr6D6JiZ.js +0 -491
  86. package/site/dist/assets/FingerPrintIcon-DOCbS93y.js +0 -1
  87. package/site/dist/assets/FundSolWalletWithExternalSolanaWallet-WtLwYaOl-A_G5kfa1.js +0 -1
  88. package/site/dist/assets/FundWalletMethodHeader-BWNkokCT-LPv35wkD.js +0 -1
  89. package/site/dist/assets/FundWithBankDepositScreen-DajkHxBD-0hfGrfvf.js +0 -58
  90. package/site/dist/assets/FundingEditAmountScreen-CpvpiO4M-Dz0s_NWR.js +0 -1
  91. package/site/dist/assets/FundingMethodSelectionScreen-Db7vhRlE-ZGm-3-Io.js +0 -1
  92. package/site/dist/assets/GlobeAltIcon-BBRl2MMn.js +0 -1
  93. package/site/dist/assets/GooglePay-B53WnudL-BGiVpB8y.js +0 -1
  94. package/site/dist/assets/HCaptchaWrapper-CUi9LJsp-CHdfIeD_.js +0 -4
  95. package/site/dist/assets/InAppBrowserLoginNotPossible-BMR9br1T-Cgoiow3m.js +0 -1
  96. package/site/dist/assets/InfoBanner-Cb3p1z12-_YqQmRiu.js +0 -22
  97. package/site/dist/assets/InjectedWalletIcon-DLcYOGDj-tuIrKQHH.js +0 -1
  98. package/site/dist/assets/InstallWalletScreen-DTA8dwLv-BMU_wa61.js +0 -1
  99. package/site/dist/assets/JsonTree-BHzNC-ic-DcVr0YF0.js +0 -34
  100. package/site/dist/assets/LabelXs-oqZNqbm_-CNprGVQM.js +0 -6
  101. package/site/dist/assets/LandingScreen-CGWUoSUG-fH6rsQen.js +0 -1
  102. package/site/dist/assets/Layouts-BMRfo5hw-e-sepi6F.js +0 -77
  103. package/site/dist/assets/Link-BdDilT2T-BdwlRJch.js +0 -40
  104. package/site/dist/assets/LinkConflictScreen-C3ZEPsgl-BXKN2Qm5.js +0 -77
  105. package/site/dist/assets/LinkEmailScreen-dKo9frwc-Cyn7Q8p1.js +0 -1
  106. package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-CWiqnwLM.js +0 -117
  107. package/site/dist/assets/LinkPasskeyScreen-zbrl_AI_-YmDevJzf.js +0 -1
  108. package/site/dist/assets/LinkPhoneScreen-oIUpfj0w-h7shW-Ay.js +0 -1
  109. package/site/dist/assets/Loader-UAZqxsnw-DJEVblm1.js +0 -62
  110. package/site/dist/assets/LoadingSkeleton-BMsgO5PV-D2atjsP5.js +0 -17
  111. package/site/dist/assets/LockClosedIcon-9EFzWK1C.js +0 -1
  112. package/site/dist/assets/LoginFailedScreen-CN0dJV6U-By3Si77a.js +0 -1
  113. package/site/dist/assets/ManualTransferScreen-CHqcuqbs-BAHPY8rs.js +0 -1
  114. package/site/dist/assets/MfaAuthEnrollmentFlowScreen-Dj7gPTRC-CLjffFG1.js +0 -1
  115. package/site/dist/assets/MfaAuthVerifyFlowScreen-DbFpyWMy-BmQg1Y61.js +0 -1
  116. package/site/dist/assets/MfaEnrollmentFlowScreen-DhLnGgnr-DmBtTtlw.js +0 -1
  117. package/site/dist/assets/MfaVerifyFlowScreen-C_XPjMRP-4a683Bmf.js +0 -1
  118. package/site/dist/assets/ModalFooter-BCuEK_nz-C_71qsaz.js +0 -403
  119. package/site/dist/assets/MoonpayStatusScreen-CzTUtokc-DEHHcEf8.js +0 -16
  120. package/site/dist/assets/OAuthStatusScreen-ufbOa-da-BjAb_WsY.js +0 -1
  121. package/site/dist/assets/OpenLink-CUpJ1mOr-BLWFsPOW.js +0 -39
  122. package/site/dist/assets/PasskeySelectSignupOrLogin-xh2ScC_l-2PJTJxX-.js +0 -1
  123. package/site/dist/assets/PasskeyStatusScreen-Bla8MaOk-DCEg761l.js +0 -6
  124. package/site/dist/assets/PasswordRecoveryScreen-awGXZkQk-8nVjNc8k.js +0 -20
  125. package/site/dist/assets/PhArrowCircleDown-D97G-BOA.js +0 -14
  126. package/site/dist/assets/PhArrowClockwise-DVgyFuez.js +0 -14
  127. package/site/dist/assets/PhArrowDown-87b1BizM.js +0 -14
  128. package/site/dist/assets/PhArrowLeft-Da0MfnbK.js +0 -14
  129. package/site/dist/assets/PhArrowRight-AdrIL4DW.js +0 -14
  130. package/site/dist/assets/PhArrowSquareOut-Bk2CMG3I.js +0 -14
  131. package/site/dist/assets/PhArrowUp-4sgyKPGx.js +0 -14
  132. package/site/dist/assets/PhArrowUpRight-BNghD2zq.js +0 -14
  133. package/site/dist/assets/PhArrowsClockwise-BXRn1GEl.js +0 -14
  134. package/site/dist/assets/PhArrowsDownUp-DM09Jo_3.js +0 -14
  135. package/site/dist/assets/PhArrowsLeftRight-MuKBLUoF.js +0 -14
  136. package/site/dist/assets/PhBank-fHbgCqqZ.js +0 -14
  137. package/site/dist/assets/PhBrowser-2-9m-AkM.js +0 -14
  138. package/site/dist/assets/PhCaretDown-BDNabFuL.js +0 -14
  139. package/site/dist/assets/PhCaretLeft-CI460R-A.js +0 -14
  140. package/site/dist/assets/PhCaretRight-D7ZS3Ls8.js +0 -14
  141. package/site/dist/assets/PhCaretUp-C-iIfyvO.js +0 -14
  142. package/site/dist/assets/PhCheck-DxQXn57r.js +0 -14
  143. package/site/dist/assets/PhCircleHalf-MJZeRBGF.js +0 -14
  144. package/site/dist/assets/PhClock-x3MS3Mwi.js +0 -14
  145. package/site/dist/assets/PhCompass-hYn3HhGN.js +0 -14
  146. package/site/dist/assets/PhCopy-CWNYcABI.js +0 -14
  147. package/site/dist/assets/PhCreditCard-Fy4pzwtt.js +0 -14
  148. package/site/dist/assets/PhCurrencyDollar-BgiaenCi.js +0 -14
  149. package/site/dist/assets/PhDesktop-CxrzCSv5.js +0 -14
  150. package/site/dist/assets/PhDeviceMobile-CsyA5Goi.js +0 -14
  151. package/site/dist/assets/PhDotsThree-D-k1BiYg.js +0 -14
  152. package/site/dist/assets/PhEnvelope-D8kzeNox.js +0 -14
  153. package/site/dist/assets/PhFunnelSimple-CfQs5DAW.js +0 -14
  154. package/site/dist/assets/PhGlobe-DvmSc9_A.js +0 -14
  155. package/site/dist/assets/PhIdentificationCard-CfkZm-5K.js +0 -14
  156. package/site/dist/assets/PhImage-Cf0J2ZfJ.js +0 -14
  157. package/site/dist/assets/PhInfo-Bwi51-2X.js +0 -14
  158. package/site/dist/assets/PhLightbulb-C4FZV7Es.js +0 -14
  159. package/site/dist/assets/PhMagnifyingGlass-C9u8jSCJ.js +0 -14
  160. package/site/dist/assets/PhPaperPlaneRight-zxQVvPLX.js +0 -14
  161. package/site/dist/assets/PhPlus-C4UoW-2n.js +0 -14
  162. package/site/dist/assets/PhPower-CFyWDmgD.js +0 -14
  163. package/site/dist/assets/PhPuzzlePiece-BjoNVfST.js +0 -14
  164. package/site/dist/assets/PhQrCode-CP3tFauY.js +0 -14
  165. package/site/dist/assets/PhQuestion-wOWJBd3G.js +0 -14
  166. package/site/dist/assets/PhQuestionMark-CV0k9C0Y.js +0 -14
  167. package/site/dist/assets/PhSealCheck-vIKzREuN.js +0 -14
  168. package/site/dist/assets/PhSignOut-CxELV-i0.js +0 -14
  169. package/site/dist/assets/PhSpinner-CGDmbzUd.js +0 -14
  170. package/site/dist/assets/PhTrash-C9PD0SAt.js +0 -14
  171. package/site/dist/assets/PhUser-0sWNXQeZ.js +0 -14
  172. package/site/dist/assets/PhVault-6JTskUcC.js +0 -14
  173. package/site/dist/assets/PhWarning-CoKs3gz6.js +0 -14
  174. package/site/dist/assets/PhWarningCircle-BXHw1fCt.js +0 -14
  175. package/site/dist/assets/PhX-DX0SFIqC.js +0 -14
  176. package/site/dist/assets/PhoneIcon-CZ47md80.js +0 -1
  177. package/site/dist/assets/PinInput-DbZ0b1i1-BrTXu3Km.js +0 -247
  178. package/site/dist/assets/PrivyPluginContext-DA-yS5vO-CY60yubk.js +0 -1
  179. package/site/dist/assets/QrCode-DbnZwlkH-CcMrWV5D.js +0 -22
  180. package/site/dist/assets/RecoveryOAuthStatusScreen-CsgtTI2x-DgeDnHOK.js +0 -1
  181. package/site/dist/assets/RecoveryPasswordCreateScreen-Be-qJ-LU-CvJJwLhK.js +0 -1
  182. package/site/dist/assets/RecoverySelectionScreen-D1iGtpmi-CADtGtSV.js +0 -7
  183. package/site/dist/assets/SIWXUtil-Cpiv91-M.js +0 -1
  184. package/site/dist/assets/Screen-D30meWsY-vEusJ7KX.js +0 -175
  185. package/site/dist/assets/ScreenHeader-CHmc4-Lu-BvGTLg53.js +0 -31
  186. package/site/dist/assets/ScreenLayout-OwaQQcxH-cfOTmyl0.js +0 -1
  187. package/site/dist/assets/SelectSourceAsset-CWFog6gm-DxYklZYs.js +0 -140
  188. package/site/dist/assets/SetAutomaticRecoveryScreen-BO_OEqAZ-oeqeo05x.js +0 -14
  189. package/site/dist/assets/SetWalletPasswordForm-DwCTVtAh-BxFee9UA.js +0 -11
  190. package/site/dist/assets/ShieldCheckIcon-eFjsosCA.js +0 -1
  191. package/site/dist/assets/SignRequestScreen-BHKv32KR-z8ixfIma.js +0 -26
  192. package/site/dist/assets/SolanaWalletConnector-C7Lqk-Wz-Bc_lvdiF.js +0 -1
  193. package/site/dist/assets/StackedContainer-B2vaEl56-Do5dXB8-.js +0 -24
  194. package/site/dist/assets/StandardSignAndSendTransactionScreen-CfhIXVtd-DudetZAc.js +0 -20
  195. package/site/dist/assets/Subtitle-CV-2yKE4-DV0o1av3.js +0 -13
  196. package/site/dist/assets/TelegramAuthScreen-UL9zLfO8-Bo3sONvf.js +0 -1
  197. package/site/dist/assets/Title-BnzYV3Is-C3qOPnO_.js +0 -7
  198. package/site/dist/assets/TodoList-DnyULl18-BvxvubbF.js +0 -36
  199. package/site/dist/assets/TransactionDetails-e9nesl30-WWflkOIi.js +0 -109
  200. package/site/dist/assets/TransactionErrorView-BDBNqA_f-CotYKcgf.js +0 -178
  201. package/site/dist/assets/TransferFromWalletScreen-Qvn1Yuno-sVFlWqa8.js +0 -31
  202. package/site/dist/assets/TransferOrBridgeLoadingScreen-Dl1I_iIz-CV2-mY19.js +0 -6
  203. package/site/dist/assets/TurnstileWrapper-B7sNddys-oPbuMTQt.js +0 -1
  204. package/site/dist/assets/UpdateEmailScreen-DVOhPV0H-D_mjSpAy.js +0 -1
  205. package/site/dist/assets/UpdatePhoneScreen-CXRswTNZ-6RLG0ahv.js +0 -1
  206. package/site/dist/assets/UserLimitReachedScreen-5we2DHn8-CaVN0bti.js +0 -1
  207. package/site/dist/assets/Value-DTgR824E-Bp6WGZZm.js +0 -27
  208. package/site/dist/assets/WalletCards-DH1rqayz-D3cOTb9x.js +0 -1
  209. package/site/dist/assets/WalletIcon-DOIZhb2l.js +0 -1
  210. package/site/dist/assets/WalletInfoCard-sq_2wgFg-bOqfl5MA.js +0 -29
  211. package/site/dist/assets/WalletInterstitialScreen-Sh7LfsAh-DWaVkMCA.js +0 -1
  212. package/site/dist/assets/WalletLink-DK5LB5Ha-DjUZvqs-.js +0 -20
  213. package/site/dist/assets/WalletOverflowButton-bfCnp-9T-B6P7DQXA.js +0 -1
  214. package/site/dist/assets/WarningBanner-ZZqCEtZK-Bc2YKZb7.js +0 -21
  215. package/site/dist/assets/__vite-browser-external-NciY4Vu1.js +0 -1
  216. package/site/dist/assets/_esm-BZP3cXTv.js +0 -1
  217. package/site/dist/assets/_esm-ZmzBJpEs.js +0 -6
  218. package/site/dist/assets/accounts-D4AXF17G.js +0 -1
  219. package/site/dist/assets/accounts-IkdFx9nq.js +0 -1
  220. package/site/dist/assets/analytics-mkkvFRju-BRBcadka.js +0 -1
  221. package/site/dist/assets/baseSepolia-Bn1j1Ysw.js +0 -1
  222. package/site/dist/assets/basic-Cz1YiCSk.js +0 -1575
  223. package/site/dist/assets/capitalizeFirstLetter-DmLYqXsO-DtOXx-2n.js +0 -1
  224. package/site/dist/assets/ccip-B1J3oTZx.js +0 -7
  225. package/site/dist/assets/ccip-Bd7261zy.js +0 -1
  226. package/site/dist/assets/ccip-D6EHKpjI.js +0 -1
  227. package/site/dist/assets/chain-scefkYIH.js +0 -5
  228. package/site/dist/assets/check-DHnllI8N.js +0 -1
  229. package/site/dist/assets/chevron-down-1y-r4bGO.js +0 -1
  230. package/site/dist/assets/circle-alert-OwxamXUX.js +0 -1
  231. package/site/dist/assets/circle-check-big-3sZGVC10.js +0 -1
  232. package/site/dist/assets/circle-x-DQjNUV9J.js +0 -1
  233. package/site/dist/assets/client-B-BpRtwY.js +0 -1
  234. package/site/dist/assets/client-BfIR1FvH.js +0 -9
  235. package/site/dist/assets/coinbase-DCPC2L_R-YEuIn3jh.js +0 -3
  236. package/site/dist/assets/copy-DMOX_yn7.js +0 -1
  237. package/site/dist/assets/core-GIEmxmAa.js +0 -12
  238. package/site/dist/assets/createLucideIcon-BsERoFTA.js +0 -1
  239. package/site/dist/assets/defineChain-B-SQDLTG.js +0 -1
  240. package/site/dist/assets/dijkstra-CY95afk4.js +0 -1
  241. package/site/dist/assets/dist-CoS6E-3x.js +0 -1
  242. package/site/dist/assets/dist-Cp6IXeJd.js +0 -1
  243. package/site/dist/assets/dist-CrNxmdZj.js +0 -1
  244. package/site/dist/assets/dist-DYGtf4F0.js +0 -16
  245. package/site/dist/assets/embed.worker-Cnl4VGr3.js +0 -1
  246. package/site/dist/assets/errors-BQ3N-RVt-CZPAWCgC.js +0 -1
  247. package/site/dist/assets/esm-C3DslVJx.js +0 -1
  248. package/site/dist/assets/esm-Ci1ogKuK.js +0 -1
  249. package/site/dist/assets/esm-Co94ENUV.js +0 -37
  250. package/site/dist/assets/esm-bb8SANZh.js +0 -1
  251. package/site/dist/assets/esm-n2lWo7kz.js +0 -1
  252. package/site/dist/assets/ethers-zTjbodk5-Cs648CnD.js +0 -1
  253. package/site/dist/assets/eventemitter3-Dzc_0Uq2.js +0 -1
  254. package/site/dist/assets/external-link-BBPG3rG7.js +0 -1
  255. package/site/dist/assets/farcaster-DPlSjvF5-CzBCmgyL.js +0 -1
  256. package/site/dist/assets/features-CuiHbgEJ.js +0 -2
  257. package/site/dist/assets/fingerprint-pattern-CjLtyIxB.js +0 -1
  258. package/site/dist/assets/floating-ui.react-DmaCfayr.js +0 -1
  259. package/site/dist/assets/floating-ui.react-dom-CaoMF8mP.js +0 -1
  260. package/site/dist/assets/formatErc20TokenAmount-BuPk9xcy-BVqOKQif.js +0 -1
  261. package/site/dist/assets/formatter-CVbp4SlJ.js +0 -1
  262. package/site/dist/assets/get-entropy-details-for-user-dHRH5Gpi-BNiOFXbK.js +0 -1
  263. package/site/dist/assets/getChainName-DjpPdUSc-DUyKDrnp.js +0 -1
  264. package/site/dist/assets/getErc20Balance-jXNpFf0N-q4bV8kQq.js +0 -1
  265. package/site/dist/assets/getErc20TokenInfo-BNTrzLMP-DcnTFrXN.js +0 -1
  266. package/site/dist/assets/getFormattedUsdFromLamports-De3U9GlO-Gx4_wxON.js +0 -1
  267. package/site/dist/assets/getUsdcMintAddress-BVgm9GRo-r2UnDeEy.js +0 -1
  268. package/site/dist/assets/guards-BIW-GiMJ-D3tWTQAR.js +0 -1
  269. package/site/dist/assets/hooks.module-BYZGfwDl.js +0 -1
  270. package/site/dist/assets/hourglass-D_GfQdJr.js +0 -1
  271. package/site/dist/assets/id-UqEwL2BI-CTlvRMA5.js +0 -1
  272. package/site/dist/assets/index-BdEw5wyl-kLbhqLbv.js +0 -1
  273. package/site/dist/assets/index-C2h3EneX.js +0 -8
  274. package/site/dist/assets/index-CWARkn2w-DxuckIFn.js +0 -61
  275. package/site/dist/assets/index-DvMddJcd-CNBb4VUn.js +0 -12
  276. package/site/dist/assets/index-YiCw0Y_x-Czl0tWfF.js +0 -436
  277. package/site/dist/assets/index.browser-B-i-Lj3J.js +0 -1
  278. package/site/dist/assets/index.browser-BMmUcykv.js +0 -1
  279. package/site/dist/assets/index.es-Crd0TyUS.js +0 -2
  280. package/site/dist/assets/index.es-DQbup1WG.js +0 -3
  281. package/site/dist/assets/isPaymentRequestAvailable-Bq1cemEn-Cjf3l05I.js +0 -1
  282. package/site/dist/assets/jsx-runtime-CndCZlyt.js +0 -1
  283. package/site/dist/assets/landmark-BZPqMFEf.js +0 -1
  284. package/site/dist/assets/lib-DGtzi2DK.js +0 -1
  285. package/site/dist/assets/localBatchGatewayRequest-B71J8sQ9.js +0 -29
  286. package/site/dist/assets/lock-DyN_c-ud.js +0 -1
  287. package/site/dist/assets/logger-CsXm3oIv-B0UYpNFI.js +0 -1
  288. package/site/dist/assets/mail-V_mcPb64.js +0 -1
  289. package/site/dist/assets/modal-context-CwmSeFcv-DPWNJrfK.js +0 -1
  290. package/site/dist/assets/native-BKj27wfc.js +0 -1
  291. package/site/dist/assets/ofetch.CWycOUEr-Duby3iHT.js +0 -1
  292. package/site/dist/assets/parseAbi-Csg-8t59.js +0 -2
  293. package/site/dist/assets/parseAbiParameters-BWbIMYe4.js +0 -1
  294. package/site/dist/assets/parseEther-CHS0GNkM.js +0 -1
  295. package/site/dist/assets/pkce-DXF0yKjP-7_VY-d83.js +0 -1
  296. package/site/dist/assets/preload-helper-CRzLMJPS.js +0 -1
  297. package/site/dist/assets/privy-context-81KmsBw2-DiR6-Yq8.js +0 -2
  298. package/site/dist/assets/property-DuZ9DLP4.js +0 -3
  299. package/site/dist/assets/react-BfYA1q0G.js +0 -1
  300. package/site/dist/assets/react-CK2HRI1t.js +0 -1
  301. package/site/dist/assets/react-DxGeY8B8.js +0 -1
  302. package/site/dist/assets/react-auth-sHnipzbS.js +0 -1
  303. package/site/dist/assets/react-dom-BI2Koe4o.js +0 -1
  304. package/site/dist/assets/reservoir-x-nGuZkT-BguVFH3D.js +0 -1
  305. package/site/dist/assets/rolldown-runtime-Bjp52BOI.js +0 -1
  306. package/site/dist/assets/safe-url-D7SRPu33-CkMedGro.js +0 -1
  307. package/site/dist/assets/secp256k1-BeYL8aZl.js +0 -1
  308. package/site/dist/assets/secp256k1-Bh4MSkhz.js +0 -1
  309. package/site/dist/assets/secp256k1-Ci4cqHl0.js +0 -1
  310. package/site/dist/assets/secp256k1-DsxXqbXv.js +0 -1
  311. package/site/dist/assets/secp256k1-N9RUmafO.js +0 -1
  312. package/site/dist/assets/serializeAuthorizationList-DGMik9Wx.js +0 -1
  313. package/site/dist/assets/serializeSignature-CWKpxoab.js +0 -2
  314. package/site/dist/assets/sha2-C6s7zZmb.js +0 -1
  315. package/site/dist/assets/shared-DRZUmnL5-BewE5wK8.js +0 -193
  316. package/site/dist/assets/shared-FM0rljBt-CxLACri_.js +0 -21
  317. package/site/dist/assets/shouldProceedtoEmbeddedWalletCreationFlow-B3vyavrh-BkAKsRNi.js +0 -1
  318. package/site/dist/assets/simulateTransaction-CfuOrae4-PjA_tGjX.js +0 -1
  319. package/site/dist/assets/smartphone-3p8nFSTE.js +0 -1
  320. package/site/dist/assets/src-BBjkfi-Q.js +0 -1
  321. package/site/dist/assets/storage-Ufl800Ne-1qCR2JMO.js +0 -8
  322. package/site/dist/assets/stringify-DqUj9VzO.js +0 -20
  323. package/site/dist/assets/stripe.esm-DEi2D291.js +0 -2
  324. package/site/dist/assets/styles-BsotlekN-Aq27nZGc.js +0 -9
  325. package/site/dist/assets/styles-Cm9H63WO-p_ic_dxE.js +0 -92
  326. package/site/dist/assets/styles-DVyDvTdj-CjsWqAld.js +0 -74
  327. package/site/dist/assets/styles-hslXy6xG-D5QHIrJU.js +0 -164
  328. package/site/dist/assets/telegram-B-JqnkqZ-Dqkm4rDn.js +0 -1
  329. package/site/dist/assets/to-ui-error-DWe-qwS3-Bi1HUQI9.js +0 -4
  330. package/site/dist/assets/toViemAccount-B5iDGq1b-CdFZwv07.js +0 -2
  331. package/site/dist/assets/transaction-BNTP-bFm-M5tJx8vY.js +0 -1
  332. package/site/dist/assets/transfer-6YztDh-t-BVQkTvG-.js +0 -1
  333. package/site/dist/assets/triangle-alert-BG3Qw-Hq.js +0 -1
  334. package/site/dist/assets/twitch-5IOe4sIQ-CaXHFlAu.js +0 -1
  335. package/site/dist/assets/types-4CuOD23i.js +0 -1
  336. package/site/dist/assets/use-headless-crypto-deposit-Yn-QRLB3-CQqeyhGY.js +0 -1
  337. package/site/dist/assets/use-unlink-wallet-BKK4_5Ur-C0xQN_Lp.js +0 -1
  338. package/site/dist/assets/useActiveWallet-B-yZYGc6-BkK6rFBm.js +0 -123
  339. package/site/dist/assets/useGetSolPrice-x7gfUIHJ-CLVXqv6I.js +0 -1
  340. package/site/dist/assets/useGetTokenPrice-CaoUkb79-DAXXs3rs.js +0 -1
  341. package/site/dist/assets/useI18n-C-5n_noF-CGpkgIVy.js +0 -1
  342. package/site/dist/assets/useWalletBalance-CZnP5f_f-Cfd64m4n.js +0 -1
  343. package/site/dist/assets/useWallets-X1ndvJ_U-CAYqg7Za.js +0 -1
  344. package/site/dist/assets/utils-BkR7EQjg.js +0 -1
  345. package/site/dist/assets/utils-DJhBNOZA.js +0 -1
  346. package/site/dist/assets/utils-DdckVfQr.js +0 -1
  347. package/site/dist/assets/w3m-modal-B4H_uiPH.js +0 -1174
  348. package/site/dist/assets/wallet-BNI12IYk.js +0 -1
  349. package/site/dist/assets/wallet-CITSaPlB.js +0 -2
  350. package/site/dist/assets/wallet-connect-BvJtUsDt-CVlSNIXP.js +0 -1
  351. package/site/dist/assets/walletconnect-registry-xxG8XLCb-BpzpBq5s.js +0 -1
  352. package/site/dist/assets/walletconnect-v2-3frQFwbg-CzZ6oybX.js +0 -12
  353. package/site/dist/assets/wui-ux-by-reown-XyHdtV1O.js +0 -1147
  354. package/site/dist/assets/x-gjDrY2rk.js +0 -1
  355. package/src/discover/explore.js +0 -291
  356. package/src/market/settle.js +0 -165
  357. package/src/market/terms.js +0 -200
  358. package/src/publish/cli.js +0 -336
  359. package/src/publish/envelope.js +0 -185
  360. package/src/publish/manifest.js +0 -142
package/src/agent/mcp.js CHANGED
@@ -1,289 +1,136 @@
1
1
  #!/usr/bin/env node
2
- // Every Fangorn app's tools, behind one stdio MCP server.
2
+ // Fangorn for agents: every publisher's records, found through the explorer.
3
3
  //
4
- // fangorn-mcp [--from-block <n>]
4
+ // fangorn-mcp
5
5
  // claude mcp add fangorn -- fangorn-mcp
6
6
  //
7
- // One MCP server for every app, so an agent registers once. It is a directory
8
- // and a proxy:
7
+ // The explorer (FANGORN_EXPLORER, default https://explorer.fangorn.network) is the Fangorn
8
+ // app that indexes every publisher. This server downloads the explorer's view and searches
9
+ // it here, so no query ever leaves the machine; it never reads the chain to find anything:
9
10
  //
10
- // list-apps apps read off the chain (AppAgentChanged → verified cards)
11
- // open-app verify one app's card and add its tools to this server as
12
- // `<app>__<tool>` (tools/list_changed)
13
- // call-app-tool the same call by name, for clients that ignore list_changed
11
+ // search records across every publisher, or one (`publisher`)
12
+ // publishers who publishes what: each publisher's repos, with row counts
13
+ // get one record in full
14
+ // buy a record a publisher sells, quoted at the explorer, paid over x402
15
+ // wallet the person's address and balances
16
+ // publish records into the person's own repo, on their yes
14
17
  //
15
- // An app's tools are its own MCP server's (the card's `mcp`, the app's Worker at
16
- // /mcp), relayed. The Worker alone reads the app's lens from R2, so nothing is
17
- // downloaded here and access is the app's to gate.
18
- // A private app's paid tools answer with x402's PaymentRequired. With FANGORN_MCP_WALLET_KEY set,
19
- // this server pays: a pass when the card sells one at or under FANGORN_MCP_MAX_PASS (USDC; unset,
20
- // never), else that one call at or under FANGORN_MCP_MAX_PRICE (default 0.10). Without a wallet the
21
- // price is handed back to the agent, to say to the person.
18
+ // Buying needs FANGORN_MCP_WALLET_KEY and pays at most FANGORN_MCP_MAX_PRICE a record
19
+ // (default 0.10). Publishing signs with that key, else the fangorn CLI's.
22
20
 
23
21
  import net from "node:net";
24
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
22
+ import { readFileSync } from "node:fs";
25
23
  import { homedir } from "node:os";
26
24
  import { createInterface } from "node:readline";
27
- import { APP_EXTENSION, listApps, pluginOf, skillOf, toApp } from "../discover/apps.js";
28
25
 
29
26
  const log = (...a) => console.error("[fangorn-mcp]", ...a); // stdout is the protocol
30
- // FANGORN_MCP_TRACE=1: step timings on stderr, for finding where an answer waits.
31
- const T0 = performance.now();
32
- const trace = process.env.FANGORN_MCP_TRACE ? (what) => log(`${Math.round(performance.now() - T0)}ms ${what}`) : () => {};
33
27
 
34
28
  // FANGORN_NETWORK: the SDK's network name ("baseSepolia"); unset, its default.
35
29
  const network = async () => (await import("../publish/view.js")).network();
36
30
 
37
- let client;
38
- async function fangorn() {
39
- if (!client) {
40
- const [{ Fangorn }, { generatePrivateKey }] = await Promise.all([
41
- import("@fangorn-network/sdk"), import("viem/accounts")]);
42
- client = Fangorn.create({ privateKey: generatePrivateKey(), config: await network() }); // reads only
43
- // list-apps verifies every card and open-app verifies one again: once a minute is enough.
44
- const verify = client.discoverApp.bind(client), seen = new Map();
45
- client.discoverApp = (url) => {
46
- const hit = seen.get(url);
47
- if (hit && Date.now() - hit.at < 60_000) return hit.p;
48
- const p = verify(url).catch((e) => { seen.delete(url); throw e; });
49
- seen.set(url, { at: Date.now(), p });
50
- return p;
51
- };
52
- }
53
- return client;
54
- }
55
-
56
- /** A card URL → its app's MCP endpoint (the card's `mcp`), once the card verifies on chain. */
57
- async function resolveCard(cardUrl) {
58
- const card = await (await fetch(cardUrl)).json();
59
- if (!card.capabilities?.extensions?.some((e) => e.uri === APP_EXTENSION)) throw new Error(`${cardUrl} is not a Fangorn app card`);
60
- const { mcp } = toApp(await (await fangorn()).discoverApp(cardUrl), cardUrl);
61
- return { card, mcp };
62
- }
63
-
64
- /** An app's own MCP server over streamable HTTP → `{ tools, call }`.
65
- * The app's Worker is stateless (one JSON body per POST); a session id or an SSE
66
- * reply from any other server is followed too. */
67
- export async function remoteTools(url, fetcher = fetch, { access = null, wallet = process.env.FANGORN_MCP_WALLET_KEY,
68
- maxCall = process.env.FANGORN_MCP_MAX_PRICE ?? "0.10", maxPass = process.env.FANGORN_MCP_MAX_PASS } = {}) {
69
- let id = 0, session, bearer;
70
- const rpc = async (method, params) => {
71
- const r = await fetcher(url, { method: "POST", body: JSON.stringify({ jsonrpc: "2.0", id: ++id, method, params }),
72
- headers: { "content-type": "application/json", accept: "application/json, text/event-stream", ...(session && { "mcp-session-id": session }),
73
- ...(bearer && { authorization: `Bearer ${bearer}` }) } });
74
- session ??= r.headers.get("mcp-session-id") ?? undefined;
75
- let body = await r.text();
76
- if (!r.ok) throw new Error(`${url}: HTTP ${r.status} ${body.slice(0, 200)}`);
77
- if (r.headers.get("content-type")?.includes("text/event-stream")) body = body.split("\n").filter((l) => l.startsWith("data:")).pop()?.slice(5) ?? "";
78
- const m = JSON.parse(body);
79
- if (m.error) throw new Error(`${url}: ${m.error.message}`);
80
- return m.result;
81
- };
82
- await rpc("initialize", { protocolVersion: "2025-06-18", capabilities: {}, clientInfo: { name: "fangorn-mcp", version: "0.1.0" } });
83
- const { tools } = await rpc("tools/list", {});
84
- const usdc = (n) => BigInt(Math.round(Number(n) * 10 ** (access?.decimals ?? 6)));
85
- const call = async (name, args) => {
86
- const params = { name, arguments: args ?? {} };
87
- const r = await rpc("tools/call", params);
88
- const asked = r?.isError && r.structuredContent?.x402Version === 2 ? r.structuredContent : null;
89
- if (!asked) return r;
90
- if (!wallet) return { ...r, content: [...r.content, { type: "text", text: "This server has no wallet (FANGORN_MCP_WALLET_KEY), so nothing was paid: tell the person the price." }] };
91
- const x = await import("./x402.js");
92
- const account = (await import("viem/accounts")).privateKeyToAccount(`0x${wallet.trim().replace(/^0x/i, "")}`); // a key file may lack 0x
93
- if (access && maxPass != null && BigInt(access.pass) <= usdc(maxPass)) {
94
- bearer = (await x.buyPass(access, { account, fetchImpl: fetcher })).token;
95
- return rpc("tools/call", params);
96
- }
97
- const payment = await x.signPayment(x.choose(asked.accepts, { maxPrice: usdc(maxCall) }), account, { resource: asked.resource });
98
- return rpc("tools/call", { ...params, _meta: { "x402/payment": payment } });
99
- };
100
- return { tools, call };
101
- }
102
-
103
- // ── the network ─────────────────────────────────────────────────────────────
104
- // Catalogs, manifests and cards change on a rebake: held for a minute, so one
105
- // search (or resolving then verifying a card) does not fetch each twice.
106
- const CACHE = `${homedir()}/.cache/fangorn-mcp`; // the embedding model (find-apps) and the chain logs read
107
- function cachingFetch(real = globalThis.fetch) {
108
- const memo = new Map();
109
- return async (input, init) => {
110
- const url = String(input?.url ?? input);
111
- if ((init?.method ?? input?.method ?? "GET") !== "GET" || !/^https?:/.test(url)) return real(input, init);
112
- const path = new URL(url).pathname;
113
- // A card, catalog or manifest is small: 8s without one means the host is
114
- // down, and a dead card in the directory must not stall a listing for a
115
- // TCP timeout.
116
- init = { ...init, signal: init?.signal ?? AbortSignal.timeout(8000) };
117
- if (/\/cdn\/catalog$|\/manifest$|\/agent-card\.json$/.test(path)) {
118
- const hit = memo.get(url);
119
- if (hit && Date.now() - hit.at < 60_000) return new Response(hit.body, { headers: hit.headers });
120
- const r = await real(input, init);
121
- if (!r.ok) return r;
122
- const body = new Uint8Array(await r.arrayBuffer());
123
- memo.set(url, { at: Date.now(), body, headers: r.headers });
124
- return new Response(body, { headers: r.headers });
125
- }
126
- return real(input, init);
127
- };
128
- }
129
-
130
- /**
131
- * The chain logs a scan has read, on disk, so a new session reads only the
132
- * blocks since (apps.js cachedLogs). Logs carry bigints; JSON does not.
133
- * ponytail: two servers writing at once can tear the file. A torn file reads
134
- * as empty and the next scan starts over; write-then-rename if that ever bites.
135
- */
136
- function fileStore(path) {
137
- const big = (_, v) => (typeof v === "bigint" ? { $n: String(v) } : v);
138
- const unbig = (_, v) => (v?.$n !== undefined ? BigInt(v.$n) : v);
139
- let data;
140
- try { data = JSON.parse(readFileSync(path, "utf8"), unbig); } catch { data = {}; }
141
- return {
142
- get: (k) => data[k],
143
- set: (k, v) => {
144
- data[k] = v;
145
- try { mkdirSync(CACHE, { recursive: true }); writeFileSync(path, JSON.stringify(data, big)); }
146
- catch (e) { log(`log cache not saved: ${e.message}`); }
147
- },
148
- };
149
- }
150
-
151
- /** JSON-RPC over newline-delimited stdio. Returns `notify` for server → client messages. */
152
- export function serve(handlers, { input = process.stdin, output = process.stdout } = {}) {
153
- const send = (m) => output.write(JSON.stringify({ jsonrpc: "2.0", ...m }) + "\n");
154
- createInterface({ input }).on("line", async (line) => {
155
- let msg; try { msg = JSON.parse(line); } catch { return; }
156
- if (msg.id === undefined) return; // notifications
157
- const h = handlers[msg.method];
158
- if (!h) return send({ id: msg.id, error: { code: -32601, message: `unknown method ${msg.method}` } });
159
- try { send({ id: msg.id, result: await h(msg.params ?? {}) }); }
160
- catch (e) { send({ id: msg.id, error: { code: -32000, message: e?.message ?? String(e) } }); }
161
- });
162
- return (method, params = {}) => send({ method, params });
163
- }
164
-
31
+ // ── the server ──────────────────────────────────────────────────────────────
165
32
  const text = (o) => ({ content: [{ type: "text", text: typeof o === "string" ? o : JSON.stringify(o, null, 1) }] });
166
- // MCP tool names: [a-zA-Z0-9_-]{1,64}. `__` separates app from tool.
167
- const slug = (name) => String(name).toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/^-|-$/g, "").slice(0, 24) || "app";
33
+ const ADDRESS = { type: "string", description: "A publisher's address (0x…)" };
168
34
 
169
- const META = [
170
- { name: "list-apps",
171
- description: "List the Fangorn apps registered on chain whose agent cards verify: name, description, card URL, and its appId. Filter with `query`. Call open-app on one to use its tools.",
172
- inputSchema: { type: "object", properties: { query: { type: "string", description: "Case-insensitive match on name or description" } } } },
173
- { name: "find-apps",
174
- description: "Find Fangorn apps for a question, ranked by what their data is about (e.g. \"where can I learn about weather?\", \"music\"). Each app carries its site, agent card, ERC-8004 agent with an 8004scan link, ERC-8004 reputation (feedback count and average), record count and bound block. Only apps whose cards verify on chain. `relevant: true` marks the ones worth opening; the rest are sorted, not relevant. With no query, lists every app. Then open-app.",
175
- inputSchema: { type: "object", properties: {
35
+ const TOOLS = [
36
+ { name: "search",
37
+ description: "Search every publisher's records on Fangorn by meaning. Each hit names its `publisher`, and carries `sale` ({publisher, repo, id, price} in USDC base units) when it is sold. Pass `publisher` to search one publisher's records.",
38
+ inputSchema: { type: "object", required: ["query"], properties: {
176
39
  query: { type: "string", description: "What the person is looking for, in their words" },
177
- limit: { type: "integer", minimum: 1, maximum: 50, default: 10 } } } },
178
- { name: "open-app",
179
- description: "Verify an app's card against the chain and add its tools to this server as `<app>__<tool>`. The tools are the app's own MCP server's (typically describe, search, get, similar, count, browse). `app` is a name from list-apps, an appId, or a card URL.",
180
- inputSchema: { type: "object", properties: { app: { type: "string" } }, required: ["app"] } },
181
- { name: "call-app-tool",
182
- description: "Call one tool of an opened app. Same as calling `<app>__<tool>` directly.",
183
- inputSchema: { type: "object", properties: { app: { type: "string" }, tool: { type: "string" }, arguments: { type: "object" } }, required: ["app", "tool"] } },
40
+ publisher: ADDRESS, limit: { type: "integer", minimum: 1, maximum: 50, default: 10 } } } },
41
+ { name: "publishers",
42
+ description: "Who publishes on Fangorn: each publisher's repos, with what they hold and how many records. Filter with `query` (matches a repo's name or description, or the address). The busiest first.",
43
+ inputSchema: { type: "object", properties: { query: { type: "string" } } } },
44
+ { name: "get",
45
+ description: "One record in full, by the id a search hit gave.",
46
+ inputSchema: { type: "object", required: ["id"], properties: { id: { type: "string" } } } },
47
+ { name: "buy",
48
+ description: "Buy one record a publisher sells (a search hit with `sale`) and return its sealed fields opened. Pays the price in USDC from the person's wallet (FANGORN_MCP_WALLET_KEY), never above FANGORN_MCP_MAX_PRICE. It checks the sealed data can be read before paying. Tell the person the price first.",
49
+ inputSchema: { type: "object", required: ["publisher", "repo", "id"], properties: {
50
+ publisher: ADDRESS, repo: { type: "string" }, id: { type: "string", description: "From the hit's `sale`" } } } },
51
+ { name: "wallet",
52
+ description: "The person's Fangorn wallet: its address and its USDC and ETH balances.",
53
+ inputSchema: { type: "object", properties: {} } },
54
+ { name: "publish",
55
+ description: "Publish records to the person's own repo on Fangorn, from their wallet, where any agent can find them. Public and permanent: first show the person exactly what will be published and call this only on their yes to these records. Propose it when something you made for them (research, a list, a dataset) would be worth to others.",
56
+ inputSchema: { type: "object", required: ["repo", "records"], properties: {
57
+ repo: { type: "string", description: "Repo name, lowercase, e.g. \"notes\" or \"rust-crates\"; created if new" },
58
+ records: { type: "array", items: { type: "object", required: ["id", "payload"], properties: {
59
+ id: { type: "string", description: "Stable: the same thing keeps the same id" },
60
+ type: { type: "string", description: "Record type, e.g. \"note.v1\"" },
61
+ payload: { type: "object", description: "The record. Include a title and text that say what it is, in words" } } } } } } },
184
62
  ];
185
63
 
186
64
  /**
187
65
  * The server. `deps` is the outside world, so the self-check can fake it:
188
- * findApps() → { apps: [{ name, desc, card, appId }], rejected }
189
- * exploreApps(query) → { apps: ranked explore.js entries, ranked_by }
190
- * findByName(name) → { card, appId } | null (optional: a direct registry read)
191
- * resolveCard(url) → { card, mcp }
192
- * remoteTools(url) → { tools, call(name, args) } (the app's own MCP server)
193
- * notify(method) set after serve()
66
+ * explorer() → { call(name, args) } (view-tools over the explorer's view, run here)
67
+ * quote(p, r, id) → the explorer's x402 URL for one record
68
+ * buy(quoteUrl) → { fields, price, tx } (src/agent/x402.js, from the person's wallet)
69
+ * wallet(), publish({ repo, records })
194
70
  */
195
71
  export function handlers(deps) {
196
- const open = new Map(); // slug → { slug, name, card, appId, tools, call }
197
-
198
- const findOpen = (app) => open.get(slug(app)) ?? [...open.values()].find((o) => o.card === app || o.appId === app);
199
-
200
- async function openApp(app) {
201
- const hit = findOpen(app);
202
- if (hit) return hit;
203
- let cardUrl = app, appId;
204
- if (!/^https?:\/\//.test(app)) {
205
- // One registry read when the name is the app's own; the scan only when it is not.
206
- trace(`open ${app}: lookup`);
207
- const a = await deps.findByName?.(app)
208
- ?? (await deps.findApps()).apps.find((x) => x.appId === app || slug(x.name) === slug(app));
209
- if (!a) throw new Error(`no verified app named ${JSON.stringify(app)}; see list-apps`);
210
- ({ card: cardUrl, appId } = a);
211
- }
212
- trace(`open ${app}: resolve ${cardUrl}`);
213
- const { card, mcp } = await deps.resolveCard(cardUrl);
214
- if (!mcp) throw new Error(`${card.name ?? cardUrl} has no MCP server (its card has no \`mcp\`)`);
215
- let s = slug(card.name ?? app);
216
- while (open.has(s)) s += "-";
217
- const ext = card.capabilities?.extensions?.find((e) => e.uri === APP_EXTENSION);
218
- const access = ext?.params?.access ?? null;
219
- const o = { slug: s, name: card.name, card: cardUrl, appId, skill: skillOf(ext), plugin: pluginOf(ext), ...await deps.remoteTools(mcp, { access }) };
220
- trace(`open ${app}: ready`);
221
- open.set(o.slug, o);
222
- deps.notify?.("notifications/tools/list_changed");
223
- return o;
224
- }
225
-
226
- async function call(app, tool, args) {
227
- const o = findOpen(app);
228
- if (!o) throw new Error(`${app} is not open; call open-app first`);
229
- if (!o.tools.some((t) => t.name === tool)) throw new Error(`${o.slug} has no tool ${tool}`);
230
- return o.call(tool, args);
231
- }
232
-
72
+ const ask = async (name, args) => {
73
+ const r = await (await deps.explorer()).call(name, args);
74
+ if (r.isError) throw new Error(`explorer: ${r.content?.[0]?.text ?? "error"}`);
75
+ return r;
76
+ };
233
77
  const run = {
234
- "list-apps": async ({ query }) => {
235
- const { apps, rejected } = await deps.findApps();
78
+ search: ({ query, publisher, limit = 10 }) =>
79
+ ask("search", { query, limit: Math.min(50, Math.max(1, limit)), ...(publisher ? { where: { owner: publisher } } : {}) }),
80
+ publishers: async ({ query }) => {
81
+ const { views = [] } = JSON.parse((await ask("describe", {})).content[0].text);
82
+ const by = new Map();
83
+ for (const r of views.flatMap((v) => v.repos ?? [])) {
84
+ const p = by.get(r.publisher.toLowerCase()) ?? { publisher: r.publisher, rows: 0, repos: [] };
85
+ p.rows += r.rows;
86
+ p.repos.push({ repo: r.repo, rows: r.rows, description: r.description });
87
+ by.set(r.publisher.toLowerCase(), p);
88
+ }
236
89
  const q = String(query ?? "").toLowerCase();
237
- return text({
238
- apps: apps.filter((a) => !q || `${a.name} ${a.desc}`.toLowerCase().includes(q)).map((a) => ({
239
- app: slug(a.name), name: a.name, description: a.desc, card: a.card, appId: a.appId,
240
- open: open.has(slug(a.name)),
241
- })),
242
- unverified: rejected.length,
243
- });
244
- },
245
- "find-apps": async ({ query, limit = 10 }) => {
246
- const { apps, ranked_by } = await deps.exploreApps(query);
247
- return text({ query: query ?? null, ranked_by, apps: apps.slice(0, Math.min(50, Math.max(1, limit)))
248
- .map((a) => ({ ...a, open: open.has(a.app) })) });
90
+ const hit = (p) => !q || p.publisher.toLowerCase().includes(q) || p.repos.some((r) => `${r.repo} ${r.description}`.toLowerCase().includes(q));
91
+ return text({ publishers: [...by.values()].filter(hit).sort((a, b) => b.rows - a.rows) });
249
92
  },
250
- "open-app": async ({ app }) => {
251
- const o = await openApp(app);
252
- return text({ app: o.slug, name: o.name, card: o.card,
253
- call_as: `${o.slug}__<tool>, or call-app-tool`, tools: o.tools,
254
- // An app's own skill: standing instructions for the reader's agent, so never loaded
255
- // silently. The fangorn-index skill offers it to the user and pins the hash on a yes.
256
- ...(o.skill ? { skill: { ...o.skill, adopt: "The app ships an agent skill. Offer it to the user (what it would make you do, from its description); adopt it only on their yes, checking sha256, and ask again if the hash changes." } } : {}),
257
- // Its Claude Code plugin: hooks run code on every matching event, so the same rule, louder.
258
- ...(o.plugin ? { plugin: { ...o.plugin, install: `claude plugin install ${o.plugin.name} --marketplace ${o.plugin.marketplace}`,
259
- adopt: "The app ships a Claude Code plugin, whose hooks run code on this machine without being called. Offer it to the user once, saying what its hooks trigger on; install only on their yes. It takes effect next session." } } : {}) });
93
+ get: ({ id }) => ask("get", { id }),
94
+ buy: async ({ publisher, repo, id }) => text({ publisher, repo, id, ...await deps.buy(deps.quote(publisher, repo, id)) }),
95
+ wallet: async () => text(await deps.wallet()),
96
+ publish: async ({ repo, records }) => {
97
+ if (!records?.length) throw new Error("no records to publish");
98
+ return text(await deps.publish({ repo, records: records.map(({ id, type = "record.v1", payload }) => ({ id: String(id), type, payload })) }));
260
99
  },
261
- "call-app-tool": async ({ app, tool, arguments: args }) => call(app, tool, args),
262
100
  };
263
101
 
264
102
  return {
265
103
  initialize: async (p) => ({
266
104
  protocolVersion: p.protocolVersion ?? "2025-06-18",
267
- capabilities: { tools: { listChanged: true } },
268
- serverInfo: { name: "fangorn-mcp", version: "0.1.0" },
269
- instructions: "Fangorn apps are anchored on chain and each serves its own MCP. list-apps to find one, open-app to load it, then call its tools as <app>__<tool>. Start with <app>__describe when it exists.",
105
+ capabilities: { tools: { listChanged: false } },
106
+ serverInfo: { name: "fangorn-mcp", version: "0.2.0" },
107
+ instructions: "Fangorn is publishers' records, anchored on chain and indexed by the Fangorn explorer. search finds records across every publisher; publishers says who publishes what; get reads one; buy pays for a sold one (say the price first).",
270
108
  }),
271
109
  ping: async () => ({}),
272
- "tools/list": async () => ({ tools: [
273
- ...META,
274
- ...[...open.values()].flatMap((o) => o.tools.map((t) => ({ ...t, name: `${o.slug}__${t.name}`, description: `[${o.name}] ${t.description ?? ""}` }))),
275
- ] }),
110
+ "tools/list": async () => ({ tools: TOOLS }),
276
111
  "tools/call": async ({ name, arguments: args = {} }) => {
277
112
  try {
278
- if (run[name]) return await run[name](args);
279
- const i = name.indexOf("__");
280
- if (i > 0) return await call(name.slice(0, i), name.slice(i + 2), args);
281
- throw new Error(`no tool ${name}`);
113
+ if (!run[name]) throw new Error(`no tool ${name}`);
114
+ return await run[name](args);
282
115
  } catch (e) { return { isError: true, content: [{ type: "text", text: e?.message ?? String(e) }] }; }
283
116
  },
284
117
  };
285
118
  }
286
119
 
120
+ /** JSON-RPC over newline-delimited stdio. Returns `notify` for server → client messages. */
121
+ export function serve(handlers, { input = process.stdin, output = process.stdout } = {}) {
122
+ const send = (m) => output.write(JSON.stringify({ jsonrpc: "2.0", ...m }) + "\n");
123
+ createInterface({ input }).on("line", async (line) => {
124
+ let msg; try { msg = JSON.parse(line); } catch { return; }
125
+ if (msg.id === undefined) return; // notifications
126
+ const h = handlers[msg.method];
127
+ if (!h) return send({ id: msg.id, error: { code: -32601, message: `unknown method ${msg.method}` } });
128
+ try { send({ id: msg.id, result: await h(msg.params ?? {}) }); }
129
+ catch (e) { send({ id: msg.id, error: { code: -32000, message: e?.message ?? String(e) } }); }
130
+ });
131
+ return (method, params = {}) => send({ method, params });
132
+ }
133
+
287
134
  // ── main / self-check ───────────────────────────────────────────────────────
288
135
  // A command, not a module: installed as a bin, argv[1] is the symlink, so there is no is-main check.
289
136
  if (process.argv[2] === "--selfcheck") {
@@ -291,154 +138,104 @@ if (process.argv[2] === "--selfcheck") {
291
138
  const input = new PassThrough(), output = new PassThrough();
292
139
  const replies = [];
293
140
  createInterface({ input: output }).on("line", (l) => replies.push(JSON.parse(l)));
294
- const posts = [];
295
- // The app's Worker: initialize, tools/list, tools/call over JSON-RPC POSTs.
296
- const remote = await remoteTools("https://k.test/mcp", async (url, init) => {
297
- const m = JSON.parse(init.body);
298
- posts.push(m.method);
299
- const result = m.method === "tools/list" ? { tools: [{ name: "search" }] }
300
- : m.method === "tools/call" ? text({ via: "remote", ...m.params }) : {};
301
- return new Response(JSON.stringify({ jsonrpc: "2.0", id: m.id, result }), { headers: { "content-type": "application/json" } });
302
- });
303
- let opened = 0;
304
- const deps = {
305
- findApps: async () => ({ apps: [{ name: "Kings Foil", desc: "trials", card: "https://k.test/card", appId: "0xk" },
306
- { name: "Bare", desc: "", card: "https://b.test/card", appId: "0xb" }], rejected: [{}] }),
307
- exploreApps: async (q) => ({ ranked_by: q ? "meaning" : null, apps: [{ app: "kings-foil", name: "Kings Foil", relevant: true }, { app: "b", name: "B", relevant: false }] }),
308
- resolveCard: async (url) => (url.includes("b.test") ? { card: { name: "Bare" }, mcp: null } : { card: { name: "Kings Foil", capabilities: { extensions: [{ uri: APP_EXTENSION,
309
- params: { skill: { url: "https://k.test/skill/kings-foil/SKILL.md", sha256: "b".repeat(64) }, plugin: { marketplace: "k/kf", name: "kings-foil" } } }] } }, mcp: "https://k.test/mcp" }),
310
- remoteTools: async () => (opened++, remote),
311
- };
312
- deps.notify = serve(handlers(deps), { input, output });
141
+ const P = "0xAbC0000000000000000000000000000000000001", Q = "0x0000000000000000000000000000000000000002";
142
+ // The explorer's view tools, as view-tools.js answers them.
143
+ const views = [{ view: "view", rows: 6, repos: [
144
+ { publisher: P, repo: "weather", rows: 4, description: "hourly readings" },
145
+ { publisher: Q, repo: "notes", rows: 1, description: "" },
146
+ { publisher: P, repo: "tides", rows: 1, description: "" }] }];
147
+ const explorer = { call: async (name, args) => (name === "describe" ? text({ views }) : text({ via: name, ...args })) };
148
+ const published = [];
149
+ serve(handlers({
150
+ explorer: async () => explorer,
151
+ quote: (p, r, id) => `https://explorer.test/paid/${p}/${r}/${encodeURIComponent(id)}`,
152
+ buy: async (quoted) => ({ fields: { quoted }, price: "10000" }),
153
+ wallet: async () => ({ address: "0xme" }),
154
+ publish: async (a) => (published.push(a), { repo: a.repo, records: a.records.length }),
155
+ }), { input, output });
313
156
  const rpc = (id, method, params) => input.write(JSON.stringify({ jsonrpc: "2.0", id, method, params }) + "\n");
314
- const wait = () => new Promise((r) => setTimeout(r, 20));
315
157
  const call = (id, name, args) => rpc(id, "tools/call", { name, arguments: args });
158
+ const wait = () => new Promise((r) => setTimeout(r, 20));
316
159
  const res = (id) => replies.find((r) => r.id === id);
317
160
  const body = (id) => JSON.parse(res(id).result.content[0].text);
318
161
  const assert = (c, m) => { if (!c) throw new Error(m); };
319
162
 
320
- rpc(1, "initialize", {}); rpc(2, "tools/list"); call(3, "list-apps", { query: "TRIAL" }); call(4, "kings-foil__search"); await wait();
321
- assert(res(1).result.capabilities.tools.listChanged, "advertises list_changed");
322
- assert(res(2).result.tools.length === 4, "only the meta tools before anything is open");
323
- assert(body(3).apps.length === 1 && body(3).apps[0].app === "kings-foil" && body(3).unverified === 1, "list-apps filters and slugs");
324
- assert(res(4).result.isError && /not open/.test(res(4).result.content[0].text), "an unopened app's tool is an error");
325
-
326
- call(30, "find-apps", { query: "trials", limit: 1 }); await wait();
327
- assert(body(30).ranked_by === "meaning" && body(30).apps.length === 1 && body(30).apps[0].open === false, "find-apps ranks, caps, and says what is open");
328
-
329
- call(5, "open-app", { app: "Kings Foil" }); await wait();
330
- call(6, "open-app", { app: "https://k.test/card" }); await wait();
331
- assert(body(5).tools[0].name === "search" && opened === 1, "open once, by name or by card URL");
332
- assert(body(5).skill?.url === "https://k.test/skill/kings-foil/SKILL.md" && /user/.test(body(5).skill.adopt), "open-app offers the app's skill, for the user to adopt");
333
- assert(body(5).plugin?.marketplace === "k/kf" && /user/.test(body(5).plugin.adopt), "open-app offers the app's plugin, for the user to install");
334
- assert(replies.some((r) => r.method === "notifications/tools/list_changed"), "opening notifies");
335
- rpc(7, "tools/list"); call(8, "kings-foil__search", { query: "x" }); call(9, "call-app-tool", { app: "kings-foil", tool: "search" });
336
- call(10, "call-app-tool", { app: "kings-foil", tool: "boom" }); call(11, "open-app", { app: "nope" }); call(13, "open-app", { app: "Bare" }); rpc(12, "nope"); await wait();
337
- assert(res(7).result.tools.some((t) => t.name === "kings-foil__search"), "opened tools are listed under the app");
338
- assert(body(8).via === "remote" && body(8).arguments.query === "x" && body(9).name === "search", "both call paths reach the app's server");
339
- assert(posts.join() === "initialize,tools/list,tools/call,tools/call", `initialize, list, then calls: ${posts}`);
340
- assert(res(10).result.isError && res(11).result.isError, "failures are tool errors");
341
- assert(res(13).result.isError && /no MCP server/.test(res(13).result.content[0].text), "an app without `mcp` cannot open");
342
- assert(res(12).error.code === -32601, "unknown method");
343
- // A private app: a paid tool answers with PaymentRequired; with a wallet this server pays the
344
- // call (under FANGORN_MCP_MAX_PRICE), or buys a pass when the card sells one under FANGORN_MCP_MAX_PASS.
345
- const { generatePrivateKey } = await import("viem/accounts");
346
- const req = { scheme: "exact", network: "eip155:84532", amount: "10000", asset: `0x${"aa".repeat(20)}`, payTo: `0x${"5e".repeat(20)}`,
347
- maxTimeoutSeconds: 120, extra: { name: "USDC", version: "2" } };
348
- const asked = { x402Version: 2, error: "paid", resource: { url: "https://p.test/mcp" }, accepts: [req] };
349
- const seen = [];
350
- const paidServer = async (url, init) => {
351
- if (url === "https://p.test/pass") {
352
- if (!init.headers?.["PAYMENT-SIGNATURE"]) return new Response("{}", { status: 402, headers: { "PAYMENT-REQUIRED": btoa(JSON.stringify({ ...asked, accepts: [{ ...req, amount: "2000000" }] })) } });
353
- return new Response(JSON.stringify({ token: "pass.tok", expires: 9e9 }));
354
- }
355
- const m = JSON.parse(init.body);
356
- const paid = m.params?._meta?.["x402/payment"], pass = init.headers.authorization === "Bearer pass.tok";
357
- if (m.method === "tools/call") seen.push(pass ? "pass" : paid ? `paid ${paid.accepted.amount}` : "asked");
358
- const result = m.method === "tools/list" ? { tools: [{ name: "search" }] } : m.method !== "tools/call" ? {}
359
- : paid || pass ? text({ hits: [1] }) : { isError: true, structuredContent: asked, content: [{ type: "text", text: JSON.stringify(asked) }] };
360
- return new Response(JSON.stringify({ jsonrpc: "2.0", id: m.id, result }), { headers: { "content-type": "application/json" } });
361
- };
362
- const broke = await remoteTools("https://p.test/mcp", paidServer, { wallet: undefined });
363
- if (!(await broke.call("search", {})).content.at(-1).text.includes("no wallet")) throw new Error("no wallet: the price goes back to the agent");
364
- const access = { pass_url: "https://p.test/pass", pass: "2000000", call: "10000", decimals: 6 };
365
- const perCall = await remoteTools("https://p.test/mcp", paidServer, { access, wallet: generatePrivateKey(), maxCall: "0.05" });
366
- if (JSON.parse((await perCall.call("search", {})).content[0].text).hits[0] !== 1) throw new Error("a paid call");
367
- const passer = await remoteTools("https://p.test/mcp", paidServer, { access, wallet: generatePrivateKey(), maxPass: "5" });
368
- await passer.call("search", {}); await passer.call("search", {});
369
- if (seen.join() !== "asked,asked,paid 10000,asked,pass,pass") throw new Error(`payments: ${seen}`);
370
- console.log("mcp.js self-check ok — one server, apps opened on demand, calls relayed to each app's own MCP server; a private app's tools paid per call or with a pass bought once, and with no wallet the price is handed back");
163
+ rpc(1, "initialize", {}); rpc(2, "tools/list"); call(3, "search", { query: "rain", publisher: P }); call(4, "search", { query: "rain", limit: 500 });
164
+ call(5, "publishers", {}); call(6, "publishers", { query: "HOURLY" }); call(7, "get", { id: "a" }); call(8, "nope"); rpc(9, "nope"); await wait();
165
+ assert(res(2).result.tools.map((t) => t.name).join() === "search,publishers,get,buy,wallet,publish", "the six tools, no apps");
166
+ assert(body(3).via === "search" && body(3).where.owner === P && body(4).where === undefined && body(4).limit === 50, "search goes to the explorer, narrowed to a publisher on request, capped");
167
+ assert(body(5).publishers.map((p) => [p.publisher, p.rows, p.repos.length]).join("|") === `${P},5,2|${Q},1,1`, "publishers groups repos by publisher, busiest first");
168
+ assert(body(6).publishers.length === 1 && body(6).publishers[0].publisher === P, "publishers filters on what a repo holds");
169
+ assert(body(7).via === "get" && body(7).id === "a", "get goes to the explorer");
170
+ assert(res(8).result.isError && res(9).error.code === -32601, "unknown tool and method");
171
+ call(10, "buy", { publisher: P, repo: "weather", id: "a/b" }); call(11, "publish", { repo: "notes", records: [{ id: 1, payload: { title: "t" } }] });
172
+ call(12, "publish", { repo: "notes", records: [] }); call(13, "wallet"); await wait();
173
+ assert(body(10).fields.quoted === `https://explorer.test/paid/${P}/weather/a%2Fb` && body(10).price === "10000", "buy quotes the record at the explorer, the id escaped");
174
+ assert(body(11).records === 1 && published[0].records[0].id === "1" && published[0].records[0].type === "record.v1", "publish commits, ids as strings, a default type");
175
+ assert(res(12).result.isError && body(13).address === "0xme", "publishing nothing is an error; wallet reports");
176
+ const failing = handlers({ explorer: async () => ({ call: async () => ({ isError: true, content: [{ type: "text", text: "down" }] }) }) });
177
+ assert(/explorer: down/.test((await failing["tools/call"]({ name: "search", arguments: { query: "x" } })).content[0].text), "an explorer error says so");
178
+ console.log("mcp.js self-check ok — search, get and publishers ask the explorer and never the chain; publishers groups repos by publisher; buy quotes at the explorer; publish and wallet");
371
179
  process.exit(0);
372
180
  }
373
181
 
374
- const argv = process.argv.slice(2);
375
- globalThis.fetch = cachingFetch();
376
182
  // Node gives each address 250ms before trying the next; on a slow link, or a
377
183
  // host with no working IPv6, every attempt times out and fetch fails outright.
378
184
  net.setDefaultAutoSelectFamilyAttemptTimeout?.(2000);
379
- const vt = await import("./view-tools.js");
380
- const fb = argv.indexOf("--from-block");
381
- // The SDK's deployment by default: pass --from-block for another one.
382
- const fromBlock = fb >= 0 ? BigInt(argv[fb + 1]) : (await network()).appRegistryFromBlock;
383
- let found, foundAt = 0, explored, exploredAt = 0, pub, warming;
185
+ const EXPLORER = (process.env.FANGORN_EXPLORER || "https://explorer.fangorn.network").replace(/\/+$/, "");
186
+ let explorer;
187
+ // The person's key: this server's own, else the one the fangorn CLI signs with.
188
+ const FANGORN_DIR = `${homedir()}/.fangorn`;
189
+ let KEY = process.env.FANGORN_MCP_WALLET_KEY || undefined; // the plugin passes "" when unset
190
+ try { KEY ??= JSON.parse(readFileSync(`${FANGORN_DIR}/config.json`, "utf8")).privateKey; } catch { /* no CLI config */ }
191
+ KEY &&= `0x${KEY.trim().replace(/^0x/i, "")}`; // a key file may lack 0x
192
+ const needKey = () => { if (!KEY) throw new Error("no wallet: run `fangorn init`, or set FANGORN_MCP_WALLET_KEY"); return KEY; };
193
+ let signer, pub;
194
+ const signing = async () => (signer ??= (await import("@fangorn-network/sdk")).Fangorn.create({
195
+ privateKey: needKey(), config: await network(), storage: { signedUrl: {} } }));
384
196
  const chainClient = async () => {
385
197
  if (!pub) {
386
- const [{ createPublicClient, http }, net] = await Promise.all([import("viem"), network()]);
387
- pub = createPublicClient({ chain: net.chain, transport: http(net.rpcUrl) });
198
+ const [{ createPublicClient, http }, n] = await Promise.all([import("viem"), network()]);
199
+ pub = createPublicClient({ chain: n.chain, transport: http(n.rpcUrl) });
388
200
  }
389
201
  return pub;
390
202
  };
391
- const store = fileStore(`${CACHE}/logs-${(await network()).chain.id}.json`);
392
- // The embedder is for find-apps' ranking alone. Loading it blocks the thread for
393
- // seconds, so not at startup, where it would hold up list-apps.
394
- const warmEmbedder = () => (warming ??= vt.warm({ cacheDir: `${CACHE}/models` }).then((ok) => ok ? trace("embedder ready")
395
- : log(`embedder unavailable, searching by words: ${vt.warmError}`)));
203
+ // Circle's USDC on Base Sepolia. ponytail: one network's; FANGORN_USDC for another.
204
+ const USDC = process.env.FANGORN_USDC || "0x036CbD53842c5426634e7929541eC2318f3dCF7e";
396
205
  const deps = {
397
- // ponytail: cached for a minute. The logs since the last scan, plus a fetch per card.
398
- findApps: async () => {
399
- if (!found || Date.now() - foundAt > 60_000) {
400
- const [f, head] = await Promise.all([fangorn(), chainClient().then((c) => c.getBlockNumber())]);
401
- const { apps, rejected } = await listApps(f, { fromBlock, toBlock: head, store });
402
- found = { apps, rejected };
403
- foundAt = Date.now();
206
+ // The explorer's view, searched here. Re-read every 5 minutes, so a long session sees new publishers.
207
+ // ponytail: the first search downloads the whole view; route to the best domains (shard.js setDomains)
208
+ // once the network is big enough for that to hurt.
209
+ explorer: async () => {
210
+ if (!explorer || Date.now() - explorer.at > 300_000) {
211
+ const [vt, { resetShard }] = await Promise.all([import("./view-tools.js"), import("../core/shard.js")]);
212
+ resetShard();
213
+ explorer = { at: Date.now(), ...vt.viewTools({ name: "the Fangorn explorer", desc: "every publisher", views: [`${EXPLORER}/view`] }) };
404
214
  }
405
- return found;
215
+ return explorer;
406
216
  },
407
- // ponytail: cached for a minute, like findApps. Two getLogs, a card and catalog per app.
408
- exploreApps: async (query) => {
409
- if (query?.trim()) warmEmbedder(); // loads behind the directory read; a first download ranks by words
410
- if (!explored || Date.now() - exploredAt > 60_000) {
411
- const [ex, client, net] = await Promise.all([import("../discover/explore.js"), chainClient(), network()]);
412
- explored = { ex, dir: await ex.readDirectory(client, { chain: ex.chainOf(net), store }) };
413
- exploredAt = Date.now();
414
- }
415
- const { ex, dir } = explored;
416
- const { EMBED_MODEL } = await import("../core/embed.js");
417
- const qv = query?.trim() ? await vt.queryVector(query) : null;
418
- const apps = ex.rankApps(dir.apps, query, { qv, model: EMBED_MODEL }).map(ex.brief);
419
- return { apps, ranked_by: apps[0]?.ranked_by ?? null };
217
+ quote: (p, r, id) => `${EXPLORER}/paid/${encodeURIComponent(p)}/${encodeURIComponent(r)}/${encodeURIComponent(id)}`,
218
+ // Paying is opted into with this server's own key; the CLI's key alone only publishes.
219
+ buy: async (quote) => {
220
+ if (!process.env.FANGORN_MCP_WALLET_KEY) throw new Error("buying is off: set FANGORN_MCP_WALLET_KEY");
221
+ const { buyRecord } = await import("./x402.js");
222
+ return buyRecord(quote, { privateKey: needKey(), fangorn: await signing(),
223
+ maxPrice: BigInt(Math.round(Number(process.env.FANGORN_MCP_MAX_PRICE || "0.10") * 1e6)) });
420
224
  },
421
- findByName: async (name) => {
422
- const [f, { appId }] = await Promise.all([fangorn(), import("@fangorn-network/sdk")]);
423
- const reg = f.getAppRegistry();
424
- for (const id of /^0x[0-9a-fA-F]{64}$/.test(name) ? [name] : [...new Set([name, name.toLowerCase()])].map(appId)) {
425
- const card = await reg.appAgentUri(id).catch(() => "");
426
- if (card) return { card, appId: id };
427
- }
428
- return null;
225
+ wallet: async () => {
226
+ const [{ privateKeyToAccount }, { parseAbi, formatUnits, formatEther }, c] = await Promise.all([import("viem/accounts"), import("viem"), chainClient()]);
227
+ const address = privateKeyToAccount(needKey()).address;
228
+ const [usdc, eth] = await Promise.all([
229
+ c.readContract({ address: USDC, abi: parseAbi(["function balanceOf(address) view returns (uint256)"]), functionName: "balanceOf", args: [address] }),
230
+ c.getBalance({ address })]);
231
+ return { address, usdc: formatUnits(usdc, 6), eth: formatEther(eth) };
232
+ },
233
+ publish: async ({ repo, records }) => {
234
+ const f = await signing();
235
+ await f.initRepo(repo);
236
+ const { txHash, commitCid } = await f.uploadBatch(repo, records);
237
+ return { publisher: f.getAddress(), repo, records: records.length, commit: commitCid, tx: txHash };
429
238
  },
430
- resolveCard,
431
- remoteTools: (url, opts) => remoteTools(url, fetch, opts),
432
- };
433
- const h = handlers(deps);
434
- // The client starts this server with the session, long before an agent opens an
435
- // app: load the SDK then, behind the agent's first thought. After the initialize
436
- // reply, not before: it blocks the thread while it loads, and the client is
437
- // waiting on that reply.
438
- const reply = h.initialize;
439
- h.initialize = async (p) => {
440
- setImmediate(() => fangorn().then(() => trace("sdk ready"), () => {}));
441
- return reply(p);
442
239
  };
443
- deps.notify = serve(h);
444
- log(`ready: apps from block ${fromBlock}`);
240
+ serve(handlers(deps));
241
+ log(`ready: explorer ${EXPLORER}`);