@lowdefy/server-dev 6.0.0 → 7.0.0

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 (625) hide show
  1. package/client/App.jsx +52 -24
  2. package/client/Inspector.jsx +73 -30
  3. package/client/JourneyObserver.jsx +47 -0
  4. package/client/Page.jsx +54 -22
  5. package/client/Recorder.jsx +73 -0
  6. package/client/Routing.jsx +37 -13
  7. package/client/clientBuildImports.test.mjs +63 -0
  8. package/client/devStreamConnections.test.mjs +40 -0
  9. package/client/feedback/FeedbackMount.jsx +2 -2
  10. package/client/feedback/FeedbackOverlay.jsx +7 -4
  11. package/client/feedback/feedbackStyles.js +3 -0
  12. package/client/journeyObserver/startJourneyObserver.js +101 -0
  13. package/client/journeyObserver/startJourneyObserver.test.mjs +149 -0
  14. package/client/openInEditor/OpenInEditorListener.jsx +2 -0
  15. package/client/recorder/buildEngineRecord.js +37 -0
  16. package/client/recorder/buildInteractionRecord.js +64 -0
  17. package/client/recorder/buildTraceEvent.js +57 -0
  18. package/client/recorder/buildTraceEvent.test.mjs +81 -0
  19. package/client/recorder/chunkRecords.js +69 -0
  20. package/client/recorder/chunkRecords.test.mjs +59 -0
  21. package/client/recorder/collectEventActions.js +30 -0
  22. package/client/recorder/collectEventEndpoints.js +40 -0
  23. package/client/recorder/collectEventRequests.js +39 -0
  24. package/client/recorder/createPairingBuffer.js +187 -0
  25. package/client/recorder/createPairingBuffer.test.mjs +405 -0
  26. package/client/recorder/createPasswordRedactor.js +84 -0
  27. package/client/recorder/createRecordBatcher.js +54 -0
  28. package/client/recorder/createRecordBatcher.test.mjs +76 -0
  29. package/client/recorder/createRecorder.js +216 -0
  30. package/client/recorder/createRecorder.test.mjs +318 -0
  31. package/client/recorder/createRecordingSession.js +61 -0
  32. package/client/recorder/createRecordingSession.test.mjs +72 -0
  33. package/client/recorder/diffStateWrites.js +77 -0
  34. package/client/recorder/diffStateWrites.test.mjs +66 -0
  35. package/client/recorder/isEventCall.js +30 -0
  36. package/client/recorder/jsonType.js +39 -0
  37. package/client/recorder/keyChord.js +47 -0
  38. package/client/recorder/keyChord.test.mjs +50 -0
  39. package/client/recorder/sendRecords.js +40 -0
  40. package/client/recorder/toJsonSafe.js +29 -0
  41. package/lib/client/BuildingPage.jsx +3 -1
  42. package/lib/client/ErrorBar.jsx +17 -13
  43. package/lib/client/RedirectingPage.jsx +56 -0
  44. package/lib/client/auth/Auth.jsx +11 -6
  45. package/lib/client/auth/AuthConfigured.jsx +224 -39
  46. package/lib/client/auth/AuthNotConfigured.js +45 -4
  47. package/lib/client/auth/authNotConfiguredParity.test.mjs +61 -0
  48. package/lib/client/auth/authTemplateSync.test.mjs +40 -0
  49. package/lib/client/auth/betterAuthSessionContract.test.mjs +181 -0
  50. package/lib/client/auth/createFetchCheckingBuild.js +45 -0
  51. package/lib/client/bundleBuildId.js +25 -0
  52. package/lib/client/collectVisibleBlockIds.js +51 -0
  53. package/lib/client/getCspNonce.js +25 -0
  54. package/lib/client/getPagePath.js +36 -0
  55. package/lib/client/getPagePath.test.mjs +51 -0
  56. package/lib/client/utils/formatErrorsForCopy.js +30 -0
  57. package/lib/client/utils/formatErrorsForCopy.test.mjs +39 -0
  58. package/lib/client/utils/getErrorBarColor.js +25 -0
  59. package/lib/client/utils/getErrorBarColor.test.mjs +34 -0
  60. package/lib/client/utils/request.js +8 -1
  61. package/lib/client/utils/usePageConfig.js +89 -25
  62. package/lib/client/utils/usePageConfig.test.mjs +198 -13
  63. package/lib/configTree/buildConfigTree.mjs +58 -0
  64. package/lib/configTree/buildConfigTree.test.mjs +77 -0
  65. package/lib/configTree/collectConfigText.mjs +101 -0
  66. package/lib/configTree/collectConfigText.test.mjs +116 -0
  67. package/lib/configTree/prepareScratchServer.mjs +46 -0
  68. package/lib/configTree/runConfigTreeBuild.mjs +88 -0
  69. package/lib/configTree/runConfigTreeBuild.skipPluginChecks.test.mjs +74 -0
  70. package/lib/configTree/runConfigTreeBuild.test.mjs +202 -0
  71. package/lib/docs/JourneyStepError.js +30 -0
  72. package/lib/docs/acquireBrowserSlot.js +30 -0
  73. package/lib/docs/addNestedEndpoints.js +54 -0
  74. package/lib/docs/addNestedEndpoints.test.mjs +185 -0
  75. package/lib/docs/addWriteFlags.js +77 -0
  76. package/lib/docs/buildEditedPages.js +50 -0
  77. package/lib/docs/buildWaitHeader.js +21 -0
  78. package/lib/docs/captureAnnotatedScreenshot.js +23 -12
  79. package/lib/docs/checkAgentFiles.js +148 -0
  80. package/lib/docs/checkAgentFiles.test.mjs +225 -0
  81. package/lib/docs/checkStepReferences.js +239 -0
  82. package/lib/docs/checkStepReferences.test.mjs +173 -0
  83. package/lib/docs/checkpointStore.js +3 -0
  84. package/lib/docs/clientErrorStore.js +18 -1
  85. package/lib/docs/clientErrorStore.test.mjs +46 -0
  86. package/lib/docs/collectExercised.js +52 -0
  87. package/lib/docs/createBrowserLifecycle.js +102 -0
  88. package/lib/docs/createBrowserLifecycle.test.mjs +212 -0
  89. package/lib/docs/createDocsMcpServer.js +345 -388
  90. package/lib/docs/createDocsMcpServer.test.mjs +644 -4
  91. package/lib/docs/createDocsMcpServerRunJourney.test.mjs +148 -0
  92. package/lib/docs/createJourneyActors.chromium.test.mjs +227 -0
  93. package/lib/docs/createJourneyActors.js +213 -0
  94. package/lib/docs/createJourneyActors.test.mjs +79 -0
  95. package/lib/docs/createJourneyObservations.js +80 -0
  96. package/lib/docs/createLeftOriginError.js +26 -0
  97. package/lib/docs/createNetworkCounter.js +101 -0
  98. package/lib/docs/dataSets/checkSharedCollectionIds.js +67 -0
  99. package/lib/docs/dataSets/dataSessionRegistry.js +25 -0
  100. package/lib/docs/dataSets/dataSetJourneys.chromium.mongodb.test.mjs +679 -0
  101. package/lib/docs/dataSets/describeDataSetResult.js +25 -0
  102. package/lib/docs/dataSets/describeDatabaseTarget.js +46 -0
  103. package/lib/docs/dataSets/deserializeFixture.js +34 -0
  104. package/lib/docs/dataSets/drainSessionWork.js +37 -0
  105. package/lib/docs/dataSets/drainSessionWork.test.mjs +64 -0
  106. package/lib/docs/dataSets/duplicateIndexName.js +24 -0
  107. package/lib/docs/dataSets/findCollectionCollisions.js +52 -0
  108. package/lib/docs/dataSets/getDataStore.js +106 -0
  109. package/lib/docs/dataSets/getDataStore.mongodb.test.mjs +63 -0
  110. package/lib/docs/dataSets/getDataStore.test.mjs +195 -0
  111. package/lib/docs/dataSets/groupDataSetByCollection.js +44 -0
  112. package/lib/docs/dataSets/insertFixture.js +57 -0
  113. package/lib/docs/dataSets/insertGeneratedDocuments.js +41 -0
  114. package/lib/docs/dataSets/isDataStoreGone.js +29 -0
  115. package/lib/docs/dataSets/loadDataSetCollection.js +42 -0
  116. package/lib/docs/dataSets/mergeCollectionIndexes.js +52 -0
  117. package/lib/docs/dataSets/mergeCollectionIndexes.test.mjs +59 -0
  118. package/lib/docs/dataSets/openDataSession.js +119 -0
  119. package/lib/docs/dataSets/openDataSession.mongodb.test.mjs +343 -0
  120. package/lib/docs/dataSets/pickDataStorePort.js +58 -0
  121. package/lib/docs/dataSets/pickDataStorePort.test.mjs +63 -0
  122. package/lib/docs/dataSets/readConnectionArtifacts.js +46 -0
  123. package/lib/docs/dataSets/readDataSession.js +41 -0
  124. package/lib/docs/dataSets/readDataSession.test.mjs +57 -0
  125. package/lib/docs/dataSets/readDataSet.js +68 -0
  126. package/lib/docs/dataSets/readDataSet.test.mjs +220 -0
  127. package/lib/docs/dataSets/registerDataStoreShutdown.js +47 -0
  128. package/lib/docs/dataSets/removeDataStoreFiles.js +32 -0
  129. package/lib/docs/dataSets/resolveDataSetCollection.js +46 -0
  130. package/lib/docs/dataSets/resolveJourneyDataSet.js +106 -0
  131. package/lib/docs/dataSets/resolveJourneyDataSet.test.mjs +130 -0
  132. package/lib/docs/dataSets/sweepOrphanDatabases.js +33 -0
  133. package/lib/docs/dataSets/test/app/lowdefy.yaml +203 -0
  134. package/lib/docs/dataSets/test/app/modules/archive/module.lowdefy.yaml +30 -0
  135. package/lib/docs/dataSets/test/app/tests/data/shop.yaml +8 -0
  136. package/lib/docs/dataSets/test/buildTestApp.mjs +43 -0
  137. package/lib/docs/devEventBus.js +136 -0
  138. package/lib/docs/devEventBus.test.mjs +269 -0
  139. package/lib/docs/devToolDefinitions.js +529 -0
  140. package/lib/docs/docSources.js +21 -0
  141. package/lib/docs/docs.test.mjs +171 -15
  142. package/lib/docs/docsIndex.test.mjs +377 -0
  143. package/lib/docs/evalOperator.js +5 -3
  144. package/lib/docs/evalOperatorHeadless.js +84 -12
  145. package/lib/docs/evalOperatorHeadless.test.mjs +7 -1
  146. package/lib/docs/evalOperatorInTab.js +2 -1
  147. package/lib/docs/findConfig.js +95 -49
  148. package/lib/docs/fitResponse.js +81 -0
  149. package/lib/docs/fitResponse.test.mjs +128 -0
  150. package/lib/docs/formatFeedback.js +26 -5
  151. package/lib/docs/formatFeedback.test.mjs +48 -0
  152. package/lib/docs/getAppMap.js +7 -1
  153. package/lib/docs/getBrowser.js +150 -80
  154. package/lib/docs/getBrowser.test.mjs +407 -16
  155. package/lib/docs/getBuildId.js +61 -0
  156. package/lib/docs/getBuildId.test.mjs +84 -0
  157. package/lib/docs/getBuildStatus.js +54 -6
  158. package/lib/docs/getBuildStatus.test.mjs +133 -0
  159. package/lib/docs/getBuildStatusAfterEdits.js +35 -0
  160. package/lib/docs/getBuildStatusAfterEdits.test.mjs +76 -0
  161. package/lib/docs/getCoreDoc.js +9 -1
  162. package/lib/docs/getDoc.js +100 -0
  163. package/lib/docs/getDocsIndex.js +152 -0
  164. package/lib/docs/getHazards.js +115 -0
  165. package/lib/docs/getHazards.test.mjs +175 -0
  166. package/lib/docs/getMailOutboxDirectory.js +26 -0
  167. package/lib/docs/getOverview.js +79 -13
  168. package/lib/docs/getOverviewBasePath.test.mjs +37 -0
  169. package/lib/docs/getPageBuildStatus.js +46 -0
  170. package/lib/docs/getPluginDoc.js +21 -23
  171. package/lib/docs/getSchema.js +33 -1
  172. package/lib/docs/getStaleStatus.js +36 -0
  173. package/lib/docs/getStaleStatus.test.mjs +74 -0
  174. package/lib/docs/guardJourneyOrigin.js +39 -0
  175. package/lib/docs/inspectState.js +5 -3
  176. package/lib/docs/inspectStateFromTab.js +2 -2
  177. package/lib/docs/inspectStateHeadless.js +89 -27
  178. package/lib/docs/inspectStateHeadless.test.mjs +20 -1
  179. package/lib/docs/installHeadlessShell.failure.test.mjs +45 -0
  180. package/lib/docs/installHeadlessShell.js +123 -0
  181. package/lib/docs/installHeadlessShell.test.mjs +70 -0
  182. package/lib/docs/installHeadlessShell.timeout.test.mjs +72 -0
  183. package/lib/docs/isHeadlessShellIncomplete.js +34 -0
  184. package/lib/docs/isHeadlessShellIncomplete.test.mjs +63 -0
  185. package/lib/docs/isPageReady.js +77 -0
  186. package/lib/docs/isPageReady.test.mjs +235 -0
  187. package/lib/docs/isRunErrorEntry.js +25 -0
  188. package/lib/docs/journeyMutantCookie.chromium.test.mjs +150 -0
  189. package/lib/docs/journeyRunRecording.js +35 -0
  190. package/lib/docs/journeyRunRecording.test.mjs +39 -0
  191. package/lib/docs/keepEventStreamAlive.js +68 -0
  192. package/lib/docs/keepEventStreamAlive.test.mjs +77 -0
  193. package/lib/docs/launchBrowser.js +83 -0
  194. package/lib/docs/launchBrowser.test.mjs +175 -0
  195. package/lib/docs/listModuleDocEntries.js +69 -0
  196. package/lib/docs/listPackageDocFiles.js +67 -0
  197. package/lib/docs/listPluginDocEntries.js +111 -0
  198. package/lib/docs/listPlugins.js +9 -58
  199. package/lib/docs/listTypes.js +14 -6
  200. package/lib/docs/loadFrameworkHazards.js +40 -0
  201. package/lib/docs/loadFrameworkHazards.test.mjs +43 -0
  202. package/lib/docs/loadState.js +103 -35
  203. package/lib/docs/makeModuleManifestDoc.js +174 -0
  204. package/lib/docs/matchExercisedPages.js +35 -0
  205. package/lib/docs/matchJourneyRoute.js +69 -0
  206. package/lib/docs/matchJourneyRoute.test.mjs +178 -0
  207. package/lib/docs/mergeNetworkSnapshots.js +49 -0
  208. package/lib/docs/nextJourneySequence.js +30 -0
  209. package/lib/docs/noBrowserError.js +22 -0
  210. package/lib/docs/observe/TRANSIENT_SELECTOR.js +21 -0
  211. package/lib/docs/observe/claimExpectedErrors.js +67 -0
  212. package/lib/docs/observe/createJourneyAppErrors.js +236 -0
  213. package/lib/docs/observe/createJourneyAppErrors.test.mjs +385 -0
  214. package/lib/docs/observe/evaluateInvariants.js +183 -0
  215. package/lib/docs/observe/evaluateInvariants.test.mjs +299 -0
  216. package/lib/docs/observe/findingKey.js +29 -0
  217. package/lib/docs/observe/hasNoEffect.js +31 -0
  218. package/lib/docs/observe/hasNoEffect.test.mjs +37 -0
  219. package/lib/docs/observe/installStepObserver.js +72 -0
  220. package/lib/docs/observe/isAppApiUrl.js +33 -0
  221. package/lib/docs/observe/parseAppApiUrl.js +37 -0
  222. package/lib/docs/observe/readStepObserver.js +42 -0
  223. package/lib/docs/observe/relativeSource.js +29 -0
  224. package/lib/docs/observe/resolveConfigKeySource.js +33 -0
  225. package/lib/docs/observe/takeJourneyEvents.js +36 -0
  226. package/lib/docs/observe/toAppErrorFailure.js +61 -0
  227. package/lib/docs/observe/usesSearchStage.js +45 -0
  228. package/lib/docs/observe/waitForClientErrorReports.js +31 -0
  229. package/lib/docs/observe/watchJourneyContext.js +75 -0
  230. package/lib/docs/observe/withAppErrors.js +31 -0
  231. package/lib/docs/observe/withAppErrors.test.mjs +101 -0
  232. package/lib/docs/openJourney.js +94 -0
  233. package/lib/docs/openJourneyEmail.js +41 -0
  234. package/lib/docs/openJourneyEmail.test.mjs +187 -0
  235. package/lib/docs/pageBlocks.js +47 -0
  236. package/lib/docs/pageBuildStatus.test.mjs +103 -0
  237. package/lib/{client/setPageId.js → docs/parseModuleSource.js} +11 -12
  238. package/lib/docs/rails/authEngineActions.js +53 -0
  239. package/lib/docs/rails/authEngineActions.test.mjs +69 -0
  240. package/lib/docs/rails/authEngineMethods.js +52 -0
  241. package/lib/docs/rails/authEngineMethods.test.mjs +36 -0
  242. package/lib/docs/rails/checkAuthActionsRan.js +66 -0
  243. package/lib/docs/rails/checkAuthActionsRan.test.mjs +149 -0
  244. package/lib/docs/rails/checkClickRails.js +56 -0
  245. package/lib/docs/rails/createDataSetRails.js +82 -0
  246. package/lib/docs/rails/createDataSetRails.test.mjs +172 -0
  247. package/lib/docs/rails/findExternalBlocks.js +147 -0
  248. package/lib/docs/rails/findExternalBlocks.test.mjs +254 -0
  249. package/lib/docs/rails/installAuthActionRecorder.js +56 -0
  250. package/lib/docs/rails/readAuthActionRecorder.js +32 -0
  251. package/lib/docs/rails/readBuiltArtifact.js +30 -0
  252. package/lib/docs/readConnectionPlugins.js +26 -0
  253. package/lib/docs/readDevAuthMode.js +32 -0
  254. package/lib/docs/readDocEntry.js +40 -0
  255. package/lib/docs/readDocTitle.js +26 -0
  256. package/lib/docs/readJourneyEmailMatch.js +57 -0
  257. package/lib/docs/readJourneyEmailMatch.test.mjs +110 -0
  258. package/lib/docs/readJourneySession.js +56 -0
  259. package/lib/docs/readMailOutbox.js +37 -0
  260. package/lib/docs/readPagePath.js +27 -0
  261. package/lib/docs/readPluginPackages.js +99 -0
  262. package/lib/docs/readProxyBuildWait.js +38 -0
  263. package/lib/docs/readRoutineSteps.js +45 -0
  264. package/lib/docs/registerShownTextEngine.js +118 -0
  265. package/lib/docs/requestRestart.js +30 -0
  266. package/lib/docs/requestRestart.test.mjs +49 -0
  267. package/lib/docs/resolvePageInstance.js +46 -0
  268. package/lib/docs/resolvePageInstance.test.mjs +81 -0
  269. package/lib/docs/resolveSource.js +11 -7
  270. package/lib/docs/resolveSource.test.mjs +1 -1
  271. package/lib/docs/resolveToolCaller.js +62 -0
  272. package/lib/docs/reviewPage.js +51 -0
  273. package/lib/docs/reviewPageBuilds.js +56 -0
  274. package/lib/docs/reviewPageBuilds.test.mjs +207 -0
  275. package/lib/docs/runCheck.js +75 -0
  276. package/lib/docs/runCheck.test.mjs +91 -0
  277. package/lib/docs/runCheckChild.js +49 -0
  278. package/lib/docs/runEndpoint.js +185 -0
  279. package/lib/docs/runEndpoint.test.mjs +509 -0
  280. package/lib/docs/runErrorBuffers.js +99 -0
  281. package/lib/docs/runErrorBuffers.test.mjs +95 -0
  282. package/lib/docs/runJourney.js +347 -0
  283. package/lib/docs/runJourney.test.mjs +2383 -0
  284. package/lib/docs/runJourneyAsUsers.js +44 -0
  285. package/lib/docs/runJourneyDataSet.test.mjs +274 -0
  286. package/lib/docs/runJourneySteps.js +1320 -0
  287. package/lib/docs/runRequest.js +100 -59
  288. package/lib/docs/runRequest.test.mjs +190 -0
  289. package/lib/docs/saveAnnotatedScreenshot.js +1 -1
  290. package/lib/docs/scaffoldPage.js +3 -1
  291. package/lib/docs/screenshotPage.js +163 -11
  292. package/lib/docs/screenshotPage.test.mjs +263 -1
  293. package/lib/docs/screenshotPageClip.test.mjs +4 -0
  294. package/lib/docs/searchDocs.js +82 -30
  295. package/lib/docs/searchDocs.test.mjs +100 -0
  296. package/lib/docs/searchIcons.js +90 -0
  297. package/lib/docs/searchIcons.test.mjs +127 -0
  298. package/lib/docs/selectExercised.js +39 -0
  299. package/lib/docs/selectFinalState.js +66 -0
  300. package/lib/docs/selectFinalState.test.mjs +51 -0
  301. package/lib/docs/serverErrorStore.js +54 -0
  302. package/lib/docs/serverErrorStore.test.mjs +50 -0
  303. package/lib/docs/setupTestFixtures.mjs +133 -39
  304. package/lib/docs/shownTextSelector.js +28 -0
  305. package/lib/docs/snapshotState.js +13 -5
  306. package/lib/docs/splitSearchTerms.js +62 -0
  307. package/lib/docs/splitSearchTerms.test.mjs +50 -0
  308. package/lib/docs/stateCheckpoints.test.mjs +77 -0
  309. package/lib/docs/tabAvailable.js +6 -5
  310. package/lib/docs/tabChannel.js +127 -21
  311. package/lib/docs/tabChannel.test.mjs +187 -29
  312. package/lib/docs/unsettledPageNote.js +24 -0
  313. package/lib/docs/validateJourneyMail.js +36 -0
  314. package/lib/docs/validateJourneyTimeout.js +38 -0
  315. package/lib/docs/validateJourneyTimeout.test.mjs +29 -0
  316. package/lib/docs/validateRecordingParam.js +42 -0
  317. package/lib/docs/validateStateSelection.js +33 -0
  318. package/lib/docs/validateStateSelection.test.mjs +33 -0
  319. package/lib/docs/validateViewport.js +51 -0
  320. package/lib/docs/validateViewport.test.mjs +54 -0
  321. package/lib/docs/waitForBuild.js +50 -0
  322. package/lib/docs/waitForBuild.test.mjs +75 -0
  323. package/lib/docs/waitForJourneyEmail.js +77 -0
  324. package/lib/docs/withBrowserSlot.js +45 -0
  325. package/lib/docs/withBrowserSlot.test.mjs +75 -0
  326. package/lib/docs/withDataSession.js +53 -0
  327. package/lib/server/applyDataSetRedirect.js +83 -0
  328. package/lib/server/applyDataSetRedirect.test.mjs +340 -0
  329. package/lib/server/applyDataSetRedirectTenantReach.test.mjs +204 -0
  330. package/lib/server/applyDataSetRedirectWalled.test.mjs +131 -0
  331. package/lib/server/auth/createSystemContext.js +73 -0
  332. package/lib/server/auth/getAuth.js +46 -0
  333. package/lib/server/auth/getHeadlessUser.js +9 -8
  334. package/lib/server/auth/getHeadlessUser.test.mjs +41 -0
  335. package/lib/server/auth/getMockUser.js +18 -14
  336. package/lib/server/auth/{getAuthConfig.js → getStrategies.js} +12 -10
  337. package/lib/server/auth/headlessUser.js +3 -2
  338. package/lib/server/auth/journeyActor.js +35 -0
  339. package/lib/server/auth/resolveHeadlessUser.test.mjs +0 -4
  340. package/lib/server/checkPageRecord.js +44 -0
  341. package/lib/server/contextMapBudget.js +24 -0
  342. package/lib/server/contextMapBudget.test.mjs +98 -0
  343. package/lib/server/continueJitKeys.js +28 -0
  344. package/lib/server/continueJitKeys.test.mjs +43 -0
  345. package/lib/server/createDataSessionEndedError.js +33 -0
  346. package/lib/server/createDataSetRefusal.js +26 -0
  347. package/lib/server/createLowdefyContext.js +174 -41
  348. package/lib/server/createLowdefyContext.test.mjs +437 -0
  349. package/lib/server/getClientAddress.js +51 -0
  350. package/lib/server/getClientAddress.test.mjs +56 -0
  351. package/lib/server/getPageJitEnrichment.test.mjs +29 -11
  352. package/lib/server/hashConfigContent.js +26 -0
  353. package/lib/server/importAppCode.test.mjs +57 -0
  354. package/lib/server/isRoutineStep.js +27 -0
  355. package/lib/server/jitPageBuilder.js +309 -126
  356. package/lib/server/jitPageBuilder.skipPluginChecks.test.mjs +78 -0
  357. package/lib/server/jitPageBuilder.test.mjs +218 -0
  358. package/lib/server/journeyCookies/JOURNEY_COOKIES.js +37 -0
  359. package/lib/server/journeyCookies/forwardJourneyCookies.js +46 -0
  360. package/lib/server/journeyCookies/readJourneyCookie.js +52 -0
  361. package/lib/server/journeyCookies/writeJourneyCookie.js +46 -0
  362. package/lib/server/journeyCookies.js +22 -0
  363. package/lib/server/journeyCookies.test.mjs +145 -0
  364. package/lib/server/loadDynamicJsMap.js +47 -0
  365. package/lib/server/log/createHandleError.js +38 -2
  366. package/lib/server/log/createHandleError.test.mjs +125 -2
  367. package/lib/server/log/createLogger.js +3 -0
  368. package/lib/server/log/createLogger.test.mjs +77 -0
  369. package/lib/server/log/logRequest.js +2 -3
  370. package/lib/server/mutantDataSetWrappers.test.mjs +112 -0
  371. package/lib/server/mutants/applyMutant.js +39 -0
  372. package/lib/server/mutants/artifactPatterns.js +25 -0
  373. package/lib/server/mutants/buildAnchor.js +45 -0
  374. package/lib/server/mutants/collectSharedIds.js +41 -0
  375. package/lib/server/mutants/configPathSegments.js +38 -0
  376. package/lib/server/mutants/createMutantReadConfigFile.js +42 -0
  377. package/lib/server/mutants/describeEvent.js +22 -0
  378. package/lib/server/mutants/enumerateArtifact.js +56 -0
  379. package/lib/server/mutants/findNodeByKey.js +45 -0
  380. package/lib/server/mutants/findNodeByPointer.js +49 -0
  381. package/lib/server/mutants/getArtifactKind.js +32 -0
  382. package/lib/server/mutants/groupMutantCopies.js +56 -0
  383. package/lib/server/mutants/hashArtifact.js +25 -0
  384. package/lib/server/mutants/hashMutantTarget.js +34 -0
  385. package/lib/server/mutants/listMutants.js +122 -0
  386. package/lib/server/mutants/listMutants.test.mjs +502 -0
  387. package/lib/server/mutants/mutantId.js +30 -0
  388. package/lib/server/mutants/mutantRuns.js +57 -0
  389. package/lib/server/mutants/mutantRuns.test.mjs +136 -0
  390. package/lib/server/mutants/operators/dropAction.js +47 -0
  391. package/lib/server/mutants/operators/dropBlock.js +44 -0
  392. package/lib/server/mutants/operators/dropPayload.js +52 -0
  393. package/lib/server/mutants/operators/dropStep.js +44 -0
  394. package/lib/server/mutants/operators/flipVisible.js +53 -0
  395. package/lib/server/mutants/operators/index.js +37 -0
  396. package/lib/server/mutants/operators/retargetLink.js +72 -0
  397. package/lib/server/mutants/operators/skipValidate.js +77 -0
  398. package/lib/server/mutants/operators/swapIf.js +112 -0
  399. package/lib/server/mutants/operators.test.mjs +396 -0
  400. package/lib/server/mutants/positionFreePath.js +38 -0
  401. package/lib/server/mutants/removeFromParent.js +28 -0
  402. package/lib/server/mutants/test/fixtures.mjs +173 -0
  403. package/lib/server/mutants/test/keyArtifact.mjs +63 -0
  404. package/lib/server/mutants/validateMutantParam.js +69 -0
  405. package/lib/server/mutants/validateMutantsBody.js +88 -0
  406. package/lib/server/mutants/walkArtifact.js +179 -0
  407. package/lib/server/pageBuildRecords.js +131 -0
  408. package/lib/server/pageBuildRecords.test.mjs +258 -0
  409. package/lib/server/pageCache.mjs +22 -7
  410. package/lib/server/pageCache.test.mjs +22 -7
  411. package/lib/server/pageRebuilds.test.mjs +543 -0
  412. package/lib/server/pruneJitMaps.js +36 -0
  413. package/lib/server/pruneJitMaps.test.mjs +55 -0
  414. package/lib/server/readInstanceRecord.js +33 -0
  415. package/lib/server/readInstanceRecord.test.mjs +99 -0
  416. package/lib/server/readMergedMaps.js +86 -0
  417. package/lib/server/readMergedMaps.test.mjs +79 -0
  418. package/lib/server/recording/appendRecords.js +33 -0
  419. package/lib/server/recording/errorRecordingStamp.js +34 -0
  420. package/lib/server/recording/errorRecordingStamp.test.mjs +31 -0
  421. package/lib/server/recording/isRecordingEnabled.js +31 -0
  422. package/lib/server/recording/readRecordingCookie.js +71 -0
  423. package/lib/server/recording/readRecordingCookie.test.mjs +73 -0
  424. package/lib/server/recording/recordingCookiePayload.js +33 -0
  425. package/lib/server/recording/servedBuilds.js +46 -0
  426. package/lib/server/recording/servedBuilds.test.mjs +42 -0
  427. package/lib/server/scrubSecrets.js +23 -0
  428. package/lib/server/sinkExternalConnections.js +95 -0
  429. package/lib/server/sinkExternalConnections.test.mjs +135 -0
  430. package/lib/server/skipStaleMapWrites.js +62 -0
  431. package/lib/server/skipStaleMapWrites.test.mjs +113 -0
  432. package/lib/server/startIdleGc.js +67 -0
  433. package/lib/server/startIdleGc.test.mjs +138 -0
  434. package/lib/server/testFixtures/importAppCodeTwice.mjs +70 -0
  435. package/lib/server/trackSessionWork.js +30 -0
  436. package/lib/vite/createDevServerExclude.js +40 -0
  437. package/lib/vite/createDevServerExclude.test.mjs +79 -0
  438. package/manager/getContext.mjs +24 -2
  439. package/manager/processes/initialBuild.mjs +14 -0
  440. package/manager/processes/initialBuild.test.mjs +101 -0
  441. package/manager/processes/lowdefyBuild.mjs +30 -7
  442. package/manager/processes/lowdefyBuild.skipPluginChecks.test.mjs +62 -0
  443. package/manager/processes/lowdefyBuild.test.mjs +136 -1
  444. package/manager/processes/optimizeDependencies.mjs +59 -0
  445. package/manager/processes/restartServer.mjs +34 -6
  446. package/manager/processes/restartServer.test.mjs +162 -0
  447. package/manager/processes/shutdownServer.mjs +11 -0
  448. package/manager/processes/shutdownServer.test.mjs +45 -0
  449. package/manager/processes/startFirstServer.mjs +46 -0
  450. package/manager/processes/startFirstServer.test.mjs +326 -0
  451. package/manager/processes/startMailSink.mjs +135 -0
  452. package/manager/processes/startMailSink.test.mjs +146 -0
  453. package/manager/processes/startProxy.mjs +129 -12
  454. package/manager/processes/startProxy.test.mjs +366 -0
  455. package/manager/processes/startServer.mjs +47 -6
  456. package/manager/processes/startServer.test.mjs +155 -0
  457. package/manager/processes/startWatchers.mjs +6 -4
  458. package/manager/processes/syncServer.mjs +69 -0
  459. package/manager/processes/syncServer.test.mjs +141 -0
  460. package/manager/processes/waitForDevServer.mjs +48 -0
  461. package/manager/processes/waitForDevServer.test.mjs +112 -0
  462. package/manager/run.mjs +128 -43
  463. package/manager/utils/BatchChanges.mjs +21 -5
  464. package/manager/utils/BatchChanges.test.mjs +77 -0
  465. package/manager/utils/acquireDevInstance.mjs +99 -0
  466. package/manager/utils/acquireDevInstance.test.mjs +160 -0
  467. package/manager/utils/createBuildActivity.mjs +67 -0
  468. package/manager/utils/createBuildActivity.test.mjs +58 -0
  469. package/manager/utils/createChangeSignal.mjs +39 -0
  470. package/manager/utils/createChangeSignal.test.mjs +62 -0
  471. package/manager/utils/createCustomPluginMessagesMap.mjs +23 -33
  472. package/manager/utils/createCustomPluginTypesMap.mjs +24 -30
  473. package/manager/utils/createCustomPluginTypesMap.test.mjs +94 -0
  474. package/manager/utils/createDotPathIgnore.mjs +44 -0
  475. package/manager/utils/createDotPathIgnore.test.mjs +65 -0
  476. package/manager/utils/createIdleGc.mjs +51 -0
  477. package/manager/utils/createIdleGc.test.mjs +77 -0
  478. package/manager/utils/createRequestActivity.mjs +100 -0
  479. package/manager/utils/createRequestActivity.test.mjs +121 -0
  480. package/manager/utils/createServerArtifactTracker.mjs +83 -0
  481. package/manager/utils/createServerArtifactTracker.test.mjs +78 -0
  482. package/manager/utils/createServerEnv.mjs +39 -0
  483. package/manager/utils/filesHaveSameBytes.mjs +41 -0
  484. package/manager/utils/findBuildFilesOutsideWatch.mjs +59 -0
  485. package/manager/utils/findBuildFilesOutsideWatch.test.mjs +111 -0
  486. package/manager/utils/importFresh.mjs +46 -0
  487. package/manager/utils/importFresh.test.mjs +72 -0
  488. package/manager/utils/importFreshWorker.mjs +20 -0
  489. package/manager/utils/killTaggedBrowser.mjs +35 -0
  490. package/manager/utils/pagePathChanged.mjs +56 -0
  491. package/manager/utils/pruneRecordings.mjs +85 -0
  492. package/manager/utils/pruneRecordings.test.mjs +117 -0
  493. package/manager/utils/publishBuildDirectory.mjs +121 -0
  494. package/manager/utils/publishBuildDirectory.order.test.mjs +74 -0
  495. package/manager/utils/publishBuildDirectory.test.mjs +262 -0
  496. package/manager/utils/readBasePath.mjs +27 -0
  497. package/manager/utils/readBasePath.test.mjs +43 -0
  498. package/manager/utils/readBuildStatusWait.mjs +67 -0
  499. package/manager/utils/readManagerVersion.mjs +30 -0
  500. package/manager/utils/readManagerVersion.test.mjs +35 -0
  501. package/manager/utils/readPluginDefinitions.mjs +36 -0
  502. package/manager/utils/resolveDevAuthUrl.mjs +58 -0
  503. package/manager/utils/resolveDevAuthUrl.test.mjs +107 -0
  504. package/manager/utils/resolvePorts.mjs +49 -0
  505. package/manager/utils/setupWatcher.mjs +19 -11
  506. package/manager/utils/startRecordingPruner.mjs +45 -0
  507. package/manager/utils/startRecordingPruner.test.mjs +66 -0
  508. package/manager/utils/waitForChildExit.mjs +53 -0
  509. package/manager/utils/waitForChildExit.test.mjs +70 -0
  510. package/manager/utils/waitForServer.mjs +44 -0
  511. package/manager/utils/writeBuildStatus.mjs +1 -1
  512. package/manager/watchers/createPluginSourceIgnore.mjs +77 -0
  513. package/manager/watchers/envWatcher.mjs +9 -2
  514. package/manager/watchers/envWatcher.test.mjs +87 -0
  515. package/manager/watchers/listServerSidePackages.mjs +47 -0
  516. package/manager/watchers/listServerSidePackages.test.mjs +38 -0
  517. package/manager/watchers/lowdefyBuildWatcher.mjs +106 -22
  518. package/manager/watchers/lowdefyBuildWatcher.test.mjs +309 -0
  519. package/manager/watchers/pluginSourceWatcher.mjs +79 -0
  520. package/manager/watchers/pluginSourceWatcher.test.mjs +222 -0
  521. package/manager/watchers/restartRequestWatcher.mjs +71 -0
  522. package/manager/watchers/restartRequestWatcher.test.mjs +111 -0
  523. package/manager/watchers/selectLocalPluginPackages.mjs +48 -0
  524. package/manager/watchers/selectLocalPluginPackages.test.mjs +73 -0
  525. package/manager/watchers/serverPackageWatcher.mjs +38 -0
  526. package/package.json +61 -49
  527. package/package.original.json +61 -49
  528. package/src/app.js +131 -20
  529. package/src/html/renderDevPage.js +63 -11
  530. package/src/html/renderDevPage.test.mjs +138 -0
  531. package/src/lib/getRequestPath.js +29 -0
  532. package/src/middleware/createSameOriginGuard.js +37 -0
  533. package/src/middleware/createSameOriginGuard.test.mjs +102 -0
  534. package/src/middleware/errorHandler.js +51 -1
  535. package/src/middleware/errorHandler.test.mjs +196 -14
  536. package/src/middleware/isRebindingSafeHost.js +44 -0
  537. package/src/middleware/isRebindingSafeHost.test.mjs +32 -0
  538. package/src/middleware/localDevToolsOnly.js +38 -0
  539. package/src/middleware/localDevToolsOnly.test.mjs +61 -0
  540. package/src/middleware/noStore.js +27 -0
  541. package/src/middleware/noStore.test.mjs +48 -0
  542. package/src/middleware/requestTimeout.js +38 -0
  543. package/src/middleware/staleFlag.js +76 -0
  544. package/src/middleware/staleFlag.test.mjs +127 -0
  545. package/src/middleware/stampCaller.js +38 -0
  546. package/src/middleware/stampCaller.test.mjs +64 -0
  547. package/src/routes/agent.js +15 -3
  548. package/src/routes/auth.js +29 -20
  549. package/src/routes/auth.test.mjs +30 -56
  550. package/src/routes/clientError.js +13 -13
  551. package/src/routes/cron.js +3 -1
  552. package/src/routes/cronForward.js +4 -2
  553. package/src/routes/detached.js +22 -9
  554. package/src/routes/devInspect.js +17 -8
  555. package/src/routes/devInspect.test.mjs +44 -7
  556. package/src/routes/devRecording.js +113 -0
  557. package/src/routes/devRecording.test.mjs +253 -0
  558. package/src/routes/docs/buildStatus.js +10 -2
  559. package/src/routes/docs/buildStatus.test.mjs +46 -0
  560. package/src/routes/docs/content.js +5 -3
  561. package/src/routes/docs/evalOperator.js +15 -1
  562. package/src/routes/docs/events.js +50 -0
  563. package/src/routes/docs/events.test.mjs +117 -0
  564. package/src/routes/docs/find.js +9 -1
  565. package/src/routes/docs/icons.js +30 -0
  566. package/src/routes/docs/inspectState.js +16 -1
  567. package/src/routes/docs/journey.js +194 -0
  568. package/src/routes/docs/journey.test.mjs +401 -0
  569. package/src/routes/docs/journeyRefusals.test.mjs +71 -0
  570. package/src/routes/docs/loadState.js +4 -2
  571. package/src/routes/docs/mcp.js +34 -3
  572. package/src/routes/docs/mcp.test.mjs +130 -0
  573. package/src/routes/docs/mutants.js +78 -0
  574. package/src/routes/docs/mutants.test.mjs +180 -0
  575. package/src/routes/docs/parsePathParamsParam.js +41 -0
  576. package/src/routes/docs/parsePathParamsParam.test.mjs +42 -0
  577. package/src/routes/docs/parseUserParam.js +27 -21
  578. package/src/routes/docs/parseUserParam.test.mjs +17 -5
  579. package/src/routes/docs/restart.js +35 -0
  580. package/src/routes/docs/restart.test.mjs +64 -0
  581. package/src/routes/docs/runEndpoint.js +54 -0
  582. package/src/routes/docs/runEndpoint.test.mjs +165 -0
  583. package/src/routes/docs/runRequest.js +25 -8
  584. package/src/routes/docs/runRequest.test.mjs +92 -0
  585. package/src/routes/docs/screenshot.js +50 -2
  586. package/src/routes/docs/screenshot.test.mjs +117 -0
  587. package/src/routes/docs/search.js +15 -2
  588. package/src/routes/docs/snapshotState.js +17 -3
  589. package/src/routes/endpoints.js +27 -26
  590. package/src/routes/endpointsTextAnswer.test.mjs +77 -0
  591. package/src/routes/feedback.js +10 -13
  592. package/src/routes/jitPage.js +99 -35
  593. package/src/routes/jitPage.test.mjs +248 -0
  594. package/src/routes/mcp.js +82 -5
  595. package/src/routes/mcp.test.mjs +215 -0
  596. package/src/routes/mcpMemberToken.test.mjs +125 -0
  597. package/src/routes/mcpProtectedResourceMetadata.js +34 -0
  598. package/src/routes/mcpProtectedResourceMetadata.test.mjs +65 -0
  599. package/src/routes/mountDevPageRoutes.js +25 -0
  600. package/src/routes/mountDevPageRoutes.test.mjs +30 -0
  601. package/src/routes/mountOauthDiscovery.js +44 -0
  602. package/src/routes/mountOauthDiscovery.test.mjs +79 -0
  603. package/src/routes/normalizeMcpProtocolVersionHeader.js +46 -0
  604. package/src/routes/normalizeMcpProtocolVersionHeader.test.mjs +62 -0
  605. package/src/routes/reload.js +16 -0
  606. package/src/routes/request.js +6 -4
  607. package/src/routes/request.test.mjs +9 -5
  608. package/src/routes/requestBody.test.mjs +159 -0
  609. package/src/routes/usage.js +6 -2
  610. package/src/routes/user.js +27 -0
  611. package/src/routes/websocket.js +29 -8
  612. package/src/routes/websocket.test.mjs +65 -0
  613. package/src/routes/wellKnownFallback.js +30 -0
  614. package/src/routes/wellKnownFallback.test.mjs +32 -0
  615. package/src/websocket/devWebSocket.js +41 -26
  616. package/src/websocket/devWebSocket.test.mjs +157 -0
  617. package/vite.config.js +53 -23
  618. package/lib/server/auth/getDevSession.js +0 -70
  619. package/lib/server/auth/getDevSession.test.mjs +0 -138
  620. package/lib/server/auth/session.js +0 -39
  621. package/manager/processes/warnAuthUrlPortMismatch.mjs +0 -47
  622. package/manager/processes/warnAuthUrlPortMismatch.test.mjs +0 -93
  623. package/manager/watchers/moduleBuildWatcher.mjs +0 -79
  624. package/manager/watchers/serverArtifactWatcher.mjs +0 -120
  625. package/src/routes/apiPage.js +0 -37
@@ -15,51 +15,63 @@
15
15
  */
16
16
 
17
17
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
- import { z } from 'zod';
18
+ import { createTraceId, type } from '@lowdefy/helpers';
19
+ import { validateJourneyUser } from '@lowdefy/node-utils';
19
20
 
20
21
  import checkpointToMocks from './checkpointToMocks.js';
21
22
  import createConfigCheckpoint from './createConfigCheckpoint.js';
23
+ import { subscribe as subscribeToDevEvents } from './devEventBus.js';
22
24
  import evalOperator from './evalOperator.js';
23
25
  import findConfig from './findConfig.js';
24
26
  import getAppMap from './getAppMap.js';
25
27
  import getBuildStatus from './getBuildStatus.js';
26
- import getCoreDoc from './getCoreDoc.js';
28
+ import getBuildStatusAfterEdits from './getBuildStatusAfterEdits.js';
29
+ import readJourneySession from './readJourneySession.js';
30
+ import readProxyBuildWait from './readProxyBuildWait.js';
31
+ import getDoc from './getDoc.js';
27
32
  import getExamples from './getExamples.js';
28
33
  import getOverview from './getOverview.js';
29
34
  import getPageConfig from './getPageConfig.js';
30
35
  import getPluginDoc from './getPluginDoc.js';
31
36
  import getSchema from './getSchema.js';
37
+ import getStaleStatus from './getStaleStatus.js';
32
38
  import inspectState from './inspectState.js';
33
39
  import listConfigCheckpoints from './listConfigCheckpoints.js';
34
40
  import listPlugins from './listPlugins.js';
35
41
  import listTypes from './listTypes.js';
36
42
  import loadState from './loadState.js';
37
43
  import revertConfigCheckpoint from './revertConfigCheckpoint.js';
44
+ import requestRestart from './requestRestart.js';
45
+ import runCheck from './runCheck.js';
46
+ import runEndpoint from './runEndpoint.js';
47
+ import runJourney from './runJourney.js';
48
+ import runJourneyAsUsers from './runJourneyAsUsers.js';
49
+ import selectExercised from './selectExercised.js';
38
50
  import runRequest from './runRequest.js';
39
51
  import snapshotState from './snapshotState.js';
40
52
  import { listStateCheckpoints } from './checkpointStore.js';
41
53
  import createLogger from '../server/log/createLogger.js';
54
+ import lowdefyConfig from '../build/config.js';
55
+ import devToolDefinitions, { INSTRUCTIONS } from './devToolDefinitions.js';
42
56
  import scaffoldPage from './scaffoldPage.js';
43
57
  import screenshotPage from './screenshotPage.js';
44
58
  import searchDocs from './searchDocs.js';
59
+ import searchIcons from './searchIcons.js';
45
60
 
46
61
  const logger = createLogger({ server: 'lowdefy-dev-mcp' });
47
62
 
48
- const INSTRUCTIONS = `Lowdefy documentation and feedback server for this project. Lowdefy apps are YAML config composing blocks (UI), operators (logic), actions (event handlers), and connections/requests (data).
49
-
50
- Discovery workflow: start with lowdefy_overview. Use lowdefy_list_types with a kind to discover ALL installed blocks/operators/actions/connections/requests — never guess type names. Then lowdefy_get_schema and lowdefy_get_examples for the exact contract of a type, and lowdefy_get_doc / lowdefy_search_docs for concept documentation. lowdefy_list_plugins and lowdefy_get_plugin_doc cover this project's local plugin packages.
51
-
52
- Feedback loop: after EVERY config edit, call lowdefy_build_status — the dev server rebuilds on file change and this returns the current build errors/warnings (with source file locations) plus recent browser runtime errors. Fix what it reports, then confirm the page builds with lowdefy_get_page_config, and visually verify with lowdefy_screenshot_page. Use lowdefy_find_config to locate where any id (page, block, request) is defined. lowdefy_scaffold_page creates a canonical new page file. Use lowdefy_app_map first to understand an existing app.
53
-
54
- Live state: lowdefy_inspect_state reads the ACTUAL state, request results, and event log of a running page — when the developer has the page open in their browser it reads THEIR live tab (ask them to interact, then inspect), otherwise it runs the page headless. lowdefy_eval_operator evaluates any operator expression against that live state — use it to debug _state/_request bindings. lowdefy_run_request executes a request with a test payload to verify data shape (read-only unless the app opts into writes).
55
-
56
- Role-gated pages: the headless renderer signs in as a roleless user, so a page or request gated on a role renders empty or refused. Pass user to lowdefy_screenshot_page, lowdefy_inspect_state, lowdefy_eval_operator or lowdefy_load_state to act as a specific caller — e.g. user {"roles":["admin"]} — and vary it per call to compare what different roles see.
57
-
58
- Safety: lowdefy_checkpoint snapshots the config files before risky multi-file changes; lowdefy_revert_checkpoint restores them.
59
-
60
- Visual feedback: developers can press Cmd/Ctrl+/ in the running app to point at elements, draw, and copy annotated feedback to their clipboard, then paste it to you. Pasted annotation blocks start with "Feedback:" and carry the blockId, the resolved config file:line, drawn shapes, and usually an "Annotated screenshot:" file path — READ that image to see exactly what the developer drew. Treat them as precise UI feedback and use lowdefy_inspect_state for the page's live state.
61
-
62
- State checkpoints (testing): lowdefy_snapshot_state captures a page's live state AND its request/api responses into .lowdefy/state-checkpoints/<name>/ (one file per part; gitignored — checkpoints contain user/session data). lowdefy_load_state puts the app back into that state: headless for your own verification, or registry-only which returns a ?_checkpoint URL the developer can open to manually test the app in that exact state (recorded request data is served automatically). lowdefy_checkpoint_to_mocks converts a checkpoint into e2e mocks.yaml fixtures — use it when asked to write e2e tests.`;
63
+ // get_doc returns markdown rather than JSON, so hazards resolved for the
64
+ // requested type are appended as a section instead of a sibling key.
65
+ function appendHazards(doc) {
66
+ if (type.isNone(doc.hazards) || doc.hazards.length === 0) {
67
+ return doc.markdown;
68
+ }
69
+ const lines = doc.hazards.map((hazard) => {
70
+ const see = type.isNone(hazard.see) ? '' : ` (see \`${hazard.see}\`)`;
71
+ return `- **${hazard.id}**: ${hazard.message}${see}`;
72
+ });
73
+ return `${doc.markdown}\n\n## Hazards\n\n${lines.join('\n')}\n`;
74
+ }
63
75
 
64
76
  function textResult(value) {
65
77
  const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
@@ -70,76 +82,74 @@ function notFoundResult(message) {
70
82
  return { content: [{ type: 'text', text: message }], isError: true };
71
83
  }
72
84
 
73
- // Shared by every tool that renders a page headless, so one call can act as an
74
- // admin and the next as a plain member — each headless call gets its own browser
75
- // context, so they never share an identity.
76
- const userSchema = z
77
- .object({})
78
- .passthrough()
79
- .optional()
80
- .describe(
81
- 'Act as this caller instead of the default roleless headless user, e.g. {"roles":["user-admin"]} to render a role-gated page. Merged over the default, so include email/profile/attributes fields too if the page reads them — no auth engine runs for an injected caller, so nothing derives them. Headless only: it is never applied to a page the developer opens in their own browser, so combining it with source "tab" or load_state mode "registry-only" is an error rather than a silently dropped role.'
82
- );
83
-
84
- function createDocsMcpServer({ origin, honoContext } = {}) {
85
+ function createDocsMcpServer({ origin, honoContext, version } = {}) {
85
86
  const server = new McpServer(
86
- { name: 'lowdefy-docs', version: '1.0.0' },
87
- { instructions: INSTRUCTIONS }
87
+ { name: 'lowdefy', version },
88
+ { capabilities: { logging: {} }, instructions: INSTRUCTIONS }
88
89
  );
89
90
 
90
- // Debug-log every tool call (name + args, never the response) so agent
91
- // activity is visible in the dev terminal with --log-level=debug. Wrapping
92
- // registerTool here covers all tools without touching each registration.
93
- const registerTool = server.registerTool.bind(server);
94
- server.registerTool = (name, definition, handler) =>
95
- registerTool(name, definition, async (args, extra) => {
91
+ // Every tool's contract comes from devToolDefinitions, which the stdio shim
92
+ // also lists, so a tool registered here without a definition - or a
93
+ // definition left without a handler - would make the two disagree.
94
+ // registerDevTool looks the definition up, and the check after the last
95
+ // registration fails the server if any definition was missed.
96
+ //
97
+ // It also debug-logs every tool call (name + args, never the response) so
98
+ // agent activity is visible in the dev terminal with --log-level=debug.
99
+ const registered = new Set();
100
+ function registerDevTool(name, handler) {
101
+ const definition = devToolDefinitions[name];
102
+ if (definition === undefined) {
103
+ throw new Error(`Dev tool "${name}" has no entry in devToolDefinitions.`);
104
+ }
105
+ registered.add(name);
106
+ server.registerTool(name, definition, async (args, extra) => {
96
107
  try {
97
108
  logger.debug({ event: 'mcp_tool_call', tool: name, args }, `MCP tool call: ${name}`);
98
109
  } catch {
99
110
  // Logging must never break a tool call.
100
111
  }
101
- return handler(args, extra);
112
+ const result = await handler(args, extra);
113
+ try {
114
+ const stale = getStaleStatus();
115
+ // Prepended rather than merged so it covers textResult, notFoundResult
116
+ // and any future result shape without parsing the tool's own payload.
117
+ if (stale && Array.isArray(result?.content)) {
118
+ result.content.unshift({
119
+ type: 'text',
120
+ text: `STALE: ${stale.staleReason} ${JSON.stringify({
121
+ stale: true,
122
+ staleSince: stale.staleSince,
123
+ })}`,
124
+ });
125
+ }
126
+ } catch {
127
+ // A missing or half-written artifact must never break a tool call.
128
+ }
129
+ return result;
102
130
  });
131
+ }
103
132
 
104
- server.registerTool(
105
- 'lowdefy_inspect_state',
106
- {
107
- description:
108
- "Read the LIVE state of a running page: state, request results, event log (recent actions fired), global, user, input, and urlQuery. If the developer has the page open in a browser it reads their actual tab (ask them to interact first, then inspect); otherwise it runs the page headless. Use this to see what the app's data model really looks like.",
109
- inputSchema: {
110
- pageId: z.string().describe('The page id to inspect.'),
111
- source: z
112
- .enum(['tab', 'headless'])
113
- .optional()
114
- .describe('Force a source. Default: live tab if connected, else headless.'),
115
- user: userSchema,
116
- },
117
- },
118
- async ({ pageId, source, user }) => {
119
- const result = await inspectState({ origin, pageId, source, user });
120
- if (result.error) {
121
- return notFoundResult(result.error);
122
- }
123
- return textResult(result);
133
+ registerDevTool('lowdefy_inspect_state', async ({ pageId, pathParams, source, user, data }) => {
134
+ const result = await inspectState({ origin, pageId, pathParams, source, user, data });
135
+ if (result.error) {
136
+ return notFoundResult(result.error);
124
137
  }
125
- );
138
+ return textResult(result);
139
+ });
126
140
 
127
- server.registerTool(
141
+ registerDevTool(
128
142
  'lowdefy_eval_operator',
129
- {
130
- description:
131
- 'Evaluate a Lowdefy operator expression against the live state of a running page — a REPL for config. Pass the operator object in the "expression" argument — any JSON value, e.g. {"_state": "customer.name"} or {"_if": {...}}. Evaluates in the real browser runtime (live tab if connected, else headless).',
132
- inputSchema: {
133
- pageId: z.string().describe('The page id whose context to evaluate against.'),
134
- expression: z
135
- .any()
136
- .describe('The operator expression — any JSON value, e.g. {"_state": "key"}.'),
137
- source: z.enum(['tab', 'headless']).optional(),
138
- user: userSchema,
139
- },
140
- },
141
- async ({ pageId, expression, source, user }) => {
142
- const result = await evalOperator({ origin, pageId, expression, source, user });
143
+ async ({ pageId, pathParams, expression, source, user, data }) => {
144
+ const result = await evalOperator({
145
+ origin,
146
+ pageId,
147
+ pathParams,
148
+ expression,
149
+ source,
150
+ user,
151
+ data,
152
+ });
143
153
  if (result.error) {
144
154
  return notFoundResult(result.error);
145
155
  }
@@ -147,46 +157,43 @@ function createDocsMcpServer({ origin, honoContext } = {}) {
147
157
  }
148
158
  );
149
159
 
150
- server.registerTool(
160
+ registerDevTool(
151
161
  'lowdefy_run_request',
152
- {
153
- description:
154
- 'Execute a request in dev with a test payload to verify the data shape a page receives. Read-only request types always run; write requests are refused unless the app opts in (cli.agentTools.allowWriteRequests in lowdefy.yaml).',
155
- inputSchema: {
156
- pageId: z.string().describe('The page the request is defined on.'),
157
- requestId: z.string().describe('The request id.'),
158
- payload: z.record(z.any()).optional().describe('Test payload for _payload operators.'),
159
- },
160
- },
161
- async ({ pageId, requestId, payload }) =>
162
- textResult(await runRequest({ pageId, requestId, payload, honoContext }))
162
+ async ({ pageId, requestId, payload, user, data, saveResponse }) =>
163
+ textResult(
164
+ await runRequest({ pageId, requestId, payload, user, data, saveResponse, honoContext })
165
+ )
166
+ );
167
+
168
+ registerDevTool(
169
+ 'lowdefy_run_endpoint',
170
+ async ({ endpointId, payload, user, data, system, saveResponse }) =>
171
+ textResult(
172
+ await runEndpoint({ endpointId, payload, user, data, system, saveResponse, honoContext })
173
+ )
163
174
  );
164
175
 
165
- server.registerTool(
166
- 'lowdefy_app_map',
167
- {
168
- description:
169
- 'The whole-app graph in one call: every page (with its source file and, when built, block/request summaries), menus, connections, api endpoints, agents, and websockets. Call this first when working in an existing app.',
170
- inputSchema: {},
171
- },
172
- () => textResult(getAppMap())
176
+ registerDevTool('lowdefy_restart', ({ reason }) =>
177
+ textResult({
178
+ ...requestRestart({ reason }),
179
+ note: 'The dev server is restarting. Call GET /lowdefy-docs/build-status?wait=true (lowdefy_build_status with wait: true) before your next call; it answers once the server is back.',
180
+ })
173
181
  );
174
182
 
175
- server.registerTool(
183
+ registerDevTool('lowdefy_app_map', () => textResult(getAppMap()));
184
+
185
+ registerDevTool(
176
186
  'lowdefy_snapshot_state',
177
- {
178
- description:
179
- "Capture the live state AND recorded request/api responses of a running page into a checkpoint folder (.lowdefy/state-checkpoints/<name>/, one file per part; gitignored — checkpoints contain user/session data). Snapshot the developer's open tab after they reproduce a scenario, or a headless run. Use for building test fixtures and reproducible app states.",
180
- inputSchema: {
181
- pageId: z.string().describe('The page to snapshot.'),
182
- name: z.string().describe('Checkpoint name (letters, numbers, - and _).'),
183
- notes: z.string().optional().describe('What this checkpoint captures.'),
184
- source: z.enum(['tab', 'headless']).optional(),
185
- overwrite: z.boolean().optional(),
186
- },
187
- },
188
- async ({ pageId, name, notes, source, overwrite }) => {
189
- const result = await snapshotState({ origin, pageId, name, notes, source, overwrite });
187
+ async ({ pageId, pathParams, name, notes, source, overwrite }) => {
188
+ const result = await snapshotState({
189
+ origin,
190
+ pageId,
191
+ pathParams,
192
+ name,
193
+ notes,
194
+ source,
195
+ overwrite,
196
+ });
190
197
  if (result.error) {
191
198
  return notFoundResult(result.error);
192
199
  }
@@ -194,338 +201,288 @@ function createDocsMcpServer({ origin, honoContext } = {}) {
194
201
  }
195
202
  );
196
203
 
197
- server.registerTool(
198
- 'lowdefy_load_state',
199
- {
200
- description:
201
- "Put the app back into a saved state checkpoint. mode 'headless' (default) verifies the restored state itself; mode 'registry-only' loads the recorded request data into the dev server and returns a ?_checkpoint URL the developer can open to manually test the app in that exact state.",
202
- inputSchema: {
203
- name: z.string().describe('The checkpoint name.'),
204
- mode: z.enum(['headless', 'registry-only']).optional(),
205
- user: userSchema,
206
- },
207
- },
208
- async ({ name, mode, user }) => {
209
- const result = await loadState({ origin, name, mode, user });
210
- if (result.error) {
211
- return notFoundResult(result.error);
212
- }
213
- return textResult(result);
204
+ registerDevTool('lowdefy_load_state', async ({ name, mode, user, data }) => {
205
+ const result = await loadState({ origin, name, mode, user, data });
206
+ if (result.error) {
207
+ return notFoundResult(result.error);
214
208
  }
215
- );
209
+ return textResult(result);
210
+ });
216
211
 
217
- server.registerTool(
218
- 'lowdefy_list_state_checkpoints',
219
- {
220
- description: 'List saved state checkpoints (name, page, captured time, notes).',
221
- inputSchema: {},
222
- },
223
- () => textResult(listStateCheckpoints())
224
- );
212
+ registerDevTool('lowdefy_list_state_checkpoints', () => textResult(listStateCheckpoints()));
225
213
 
226
- server.registerTool(
227
- 'lowdefy_checkpoint_to_mocks',
228
- {
229
- description:
230
- 'Convert a state checkpoint into @lowdefy/e2e-utils mocks.yaml fixtures (requests/api entries plus rendered yaml). Use when writing e2e tests from a captured scenario.',
231
- inputSchema: {
232
- name: z.string().describe('The checkpoint name.'),
233
- },
234
- },
235
- ({ name }) => {
236
- const result = checkpointToMocks({ name });
237
- if (result.error) {
238
- return notFoundResult(result.error);
239
- }
240
- return textResult(result);
214
+ registerDevTool('lowdefy_checkpoint_to_mocks', ({ name }) => {
215
+ const result = checkpointToMocks({ name });
216
+ if (result.error) {
217
+ return notFoundResult(result.error);
241
218
  }
242
- );
219
+ return textResult(result);
220
+ });
243
221
 
244
- server.registerTool(
245
- 'lowdefy_checkpoint',
246
- {
247
- description:
248
- 'Snapshot all config files before risky changes. Returns a checkpoint id for lowdefy_revert_checkpoint. Use before multi-file edits so you can restore instantly.',
249
- inputSchema: {
250
- label: z.string().describe('Short label for the checkpoint, e.g. "before-refactor".'),
251
- },
252
- },
253
- ({ label }) => textResult(createConfigCheckpoint({ label }))
222
+ registerDevTool('lowdefy_checkpoint', ({ label }) =>
223
+ textResult(createConfigCheckpoint({ label }))
254
224
  );
255
225
 
256
- server.registerTool(
257
- 'lowdefy_revert_checkpoint',
258
- {
259
- description:
260
- 'Restore config files from a checkpoint made with lowdefy_checkpoint (restores changed files and removes files added since). Omit id to list available checkpoints.',
261
- inputSchema: {
262
- id: z.string().optional().describe('Checkpoint id. Omit to list checkpoints.'),
263
- },
264
- },
265
- ({ id }) => {
266
- if (!id) {
267
- return textResult(listConfigCheckpoints());
268
- }
269
- return textResult(revertConfigCheckpoint({ id }));
226
+ registerDevTool('lowdefy_revert_checkpoint', ({ id }) => {
227
+ if (!id) {
228
+ return textResult(listConfigCheckpoints());
270
229
  }
271
- );
230
+ return textResult(revertConfigCheckpoint({ id }));
231
+ });
272
232
 
273
- server.registerTool(
274
- 'lowdefy_build_status',
275
- {
276
- description:
277
- 'Call after every config edit. Returns the current build status: errors and warnings from the last build (with source file locations), plus recent browser runtime errors. The dev server rebuilds automatically on file change — edit, then call this to see what broke.',
278
- inputSchema: {},
279
- },
280
- () => textResult(getBuildStatus())
281
- );
233
+ registerDevTool('lowdefy_check', async () => textResult(await runCheck()));
282
234
 
283
- server.registerTool(
284
- 'lowdefy_get_page_config',
285
- {
286
- description:
287
- 'Get the fully built config for a page, or its structured build errors if the page fails to build. Use to verify a page after editing it.',
288
- inputSchema: {
289
- pageId: z.string().describe('The page id.'),
290
- },
291
- },
292
- async ({ pageId }) => {
293
- const result = await getPageConfig({ pageId });
294
- if (result === null) {
295
- return notFoundResult(
296
- `Page "${pageId}" not found. Use lowdefy_overview or check pageRegistry for valid page ids.`
297
- );
298
- }
299
- return textResult(result);
235
+ registerDevTool('lowdefy_build_status', async ({ wait }) => {
236
+ if (wait === true) {
237
+ const proxyWait = readProxyBuildWait({
238
+ getHeader: (name) => honoContext?.req.header(name),
239
+ });
240
+ return textResult(await getBuildStatusAfterEdits({ proxyWait }));
300
241
  }
301
- );
242
+ return textResult(await getBuildStatus());
243
+ });
244
+
245
+ registerDevTool('lowdefy_get_page_config', async ({ pageId }) => {
246
+ const result = await getPageConfig({ pageId });
247
+ if (result === null) {
248
+ return notFoundResult(
249
+ `Page "${pageId}" not found. Use lowdefy_overview or check pageRegistry for valid page ids.`
250
+ );
251
+ }
252
+ return textResult(result);
253
+ });
302
254
 
303
- server.registerTool(
304
- 'lowdefy_find_config',
305
- {
306
- description:
307
- 'Find where a config entity is defined: pass a page, block, or request id and get the source yaml file (and line where available). For block/request ids also pass the owning pageId so the page is built first.',
308
- inputSchema: {
309
- id: z.string().describe('The id to find, e.g. a pageId, blockId, or requestId.'),
310
- pageId: z
311
- .string()
312
- .optional()
313
- .describe(
314
- 'The page the id belongs to — required for block/request ids on pages not yet built.'
315
- ),
316
- },
317
- },
318
- async ({ id, pageId }) => textResult(await findConfig({ id, pageId }))
255
+ registerDevTool('lowdefy_find_config', async ({ id, pageId }) =>
256
+ textResult(await findConfig({ id, pageId }))
319
257
  );
320
258
 
321
- server.registerTool(
259
+ registerDevTool(
322
260
  'lowdefy_screenshot_page',
323
- {
324
- description:
325
- 'Screenshot a page of the running dev server (headless Chromium) to visually verify layout and rendering. Returns a PNG image.',
326
- inputSchema: {
327
- pageId: z.string().describe('The page id to screenshot.'),
328
- fullPage: z.boolean().optional().describe('Capture the full scrollable page.'),
329
- clip: z
330
- .object({
331
- x: z.number(),
332
- y: z.number(),
333
- width: z.number(),
334
- height: z.number(),
335
- })
336
- .optional()
337
- .describe(
338
- "Crop to a viewport-relative region — pass an annotation's geometry from feedback."
339
- ),
340
- scrollX: z.number().optional().describe('Scroll offset the clip was recorded at.'),
341
- scrollY: z.number().optional().describe('Scroll offset the clip was recorded at.'),
342
- user: userSchema,
343
- },
344
- },
345
- async ({ pageId, fullPage, clip, scrollX, scrollY, user }) => {
261
+ async ({
262
+ pageId,
263
+ pathParams,
264
+ urlQuery,
265
+ steps,
266
+ fullPage,
267
+ clip,
268
+ scrollX,
269
+ scrollY,
270
+ user,
271
+ data,
272
+ width,
273
+ height,
274
+ colorScheme,
275
+ }) => {
346
276
  if (!origin) {
347
277
  return notFoundResult('Screenshot unavailable: server origin unknown for this transport.');
348
278
  }
349
279
  const result = await screenshotPage({
350
280
  origin,
351
281
  pageId,
282
+ pathParams,
283
+ urlQuery,
284
+ steps,
352
285
  fullPage,
353
286
  clip,
354
287
  scrollX,
355
288
  scrollY,
356
289
  user,
290
+ data,
291
+ width,
292
+ height,
293
+ colorScheme,
357
294
  });
358
295
  if (result.error) {
359
296
  return notFoundResult(result.error);
360
297
  }
361
- return { content: [{ type: 'image', data: result.data, mimeType: result.mimeType }] };
298
+ const image = { type: 'image', data: result.data, mimeType: result.mimeType };
299
+ if (!result.failure && result.screenshots.length === 0) {
300
+ return { content: [image] };
301
+ }
302
+ // The final capture shows the page where the steps (or the failing step)
303
+ // left it; the images of `screenshot` steps follow it in step order, named
304
+ // in the JSON.
305
+ const summary = { passed: !result.failure, failure: result.failure };
306
+ if (result.screenshots.length > 0) {
307
+ summary.screenshots = result.screenshots.map(({ name }) => ({ name }));
308
+ }
309
+ return {
310
+ content: [
311
+ { type: 'text', text: JSON.stringify(summary, null, 2) },
312
+ image,
313
+ ...result.screenshots.map(({ data, mimeType }) => ({ type: 'image', data, mimeType })),
314
+ ],
315
+ };
362
316
  }
363
317
  );
364
318
 
365
- server.registerTool(
366
- 'lowdefy_scaffold_page',
367
- {
368
- description:
369
- 'Create a new page yaml file with a canonical minimal structure. Refuses if the page already exists. Returns the created file path and the lowdefy.yaml registration step you must do next.',
370
- inputSchema: {
371
- pageId: z.string().describe('The new page id (letters, numbers, - and _).'),
372
- title: z.string().optional().describe('Page title. Defaults to the pageId.'),
373
- },
374
- },
375
- ({ pageId, title }) => {
376
- const result = scaffoldPage({ pageId, title });
319
+ registerDevTool(
320
+ 'lowdefy_run_journey',
321
+ async ({ pageId, pathParams, steps, user, urlQuery, state, timeout, data, exercised }) => {
322
+ if (!origin) {
323
+ return notFoundResult('Journey unavailable: server origin unknown for this transport.');
324
+ }
325
+ const { error: userError } = validateJourneyUser({ user, data });
326
+ if (userError) {
327
+ return notFoundResult(userError);
328
+ }
329
+ // One params object for both branches, so neither can drop a parameter
330
+ // the other passes. basePath is read as the HTTP route reads it, so
331
+ // expect.calls counts and app errors match under a basePath.
332
+ const params = {
333
+ origin,
334
+ pageId,
335
+ pathParams,
336
+ steps,
337
+ urlQuery,
338
+ state,
339
+ stepTimeout: timeout,
340
+ data,
341
+ basePath: lowdefyConfig.basePath ?? '',
342
+ };
343
+ if (type.isArray(user)) {
344
+ const { passed, runs } = await runJourneyAsUsers({ ...params, users: user });
345
+ // Every run's screenshots follow the JSON as images, run by run in
346
+ // the order of the runs, each named in its run.
347
+ const summary = {
348
+ passed,
349
+ runs: runs.map(({ screenshots = [], exercised: runExercised, ...run }) => ({
350
+ ...run,
351
+ ...selectExercised({ exercised: runExercised, selection: exercised }),
352
+ screenshots: screenshots.map(({ name }) => ({ name })),
353
+ })),
354
+ };
355
+ return {
356
+ content: [
357
+ { type: 'text', text: JSON.stringify(summary, null, 2) },
358
+ ...runs.flatMap(({ screenshots = [] }) =>
359
+ screenshots.map(({ data: image, mimeType }) => ({
360
+ type: 'image',
361
+ data: image,
362
+ mimeType,
363
+ }))
364
+ ),
365
+ ],
366
+ };
367
+ }
368
+ const result = await runJourney({
369
+ ...params,
370
+ user,
371
+ // Each call is its own journey run, recorded apart from the suite's.
372
+ recording: { source: 'journey', run: { id: createTraceId(), by: 'agent', journey: null } },
373
+ });
377
374
  if (result.error) {
378
375
  return notFoundResult(result.error);
379
376
  }
380
- return textResult(result);
377
+ // The PNGs travel as image content blocks (the shape lowdefy_screenshot_page
378
+ // returns) so an MCP client renders them; the JSON keeps only their names.
379
+ const { screenshots, exercised: resultExercised, ...rest } = result;
380
+ const summary = {
381
+ ...rest,
382
+ ...selectExercised({ exercised: resultExercised, selection: exercised }),
383
+ screenshots: screenshots.map(({ name }) => ({ name })),
384
+ };
385
+ return {
386
+ content: [
387
+ { type: 'text', text: JSON.stringify(summary, null, 2) },
388
+ ...screenshots.map(({ data, mimeType }) => ({ type: 'image', data, mimeType })),
389
+ ],
390
+ };
381
391
  }
382
392
  );
383
393
 
384
- server.registerTool(
385
- 'lowdefy_overview',
386
- {
387
- description:
388
- 'Start here. Overview of everything this Lowdefy project has available: counts of blocks/operators/actions/connections/requests, installed plugins, doc sections, and which tool to use next.',
389
- inputSchema: {},
390
- },
391
- () => textResult(getOverview())
392
- );
394
+ registerDevTool('lowdefy_scaffold_page', ({ pageId, title }) => {
395
+ const result = scaffoldPage({ pageId, title });
396
+ if (result.error) {
397
+ return notFoundResult(result.error);
398
+ }
399
+ return textResult(result);
400
+ });
393
401
 
394
- server.registerTool(
395
- 'lowdefy_list_types',
396
- {
397
- description:
398
- 'List ALL available types of a kind — every block, operator, action, connection, or request type installed in this project (core and local plugins), whether used yet or not. Call this before writing any config to get exact type names.',
399
- inputSchema: {
400
- kind: z
401
- .enum([
402
- 'blocks',
403
- 'operators',
404
- 'actions',
405
- 'connections',
406
- 'requests',
407
- 'agents',
408
- 'notifications',
409
- 'websockets',
410
- ])
411
- .describe('The kind of types to list.'),
412
- },
413
- },
414
- ({ kind }) => textResult(listTypes({ kind }))
415
- );
402
+ registerDevTool('lowdefy_overview', () => textResult(getOverview()));
416
403
 
417
- server.registerTool(
418
- 'lowdefy_list_plugins',
419
- {
420
- description:
421
- "List installed plugin packages (including this project's local custom plugins) and the type names each provides.",
422
- inputSchema: {},
423
- },
424
- () => textResult(listPlugins())
425
- );
404
+ registerDevTool('lowdefy_list_types', ({ kind }) => textResult(listTypes({ kind })));
426
405
 
427
- server.registerTool(
428
- 'lowdefy_get_schema',
429
- {
430
- description:
431
- 'Get the JSON Schema for a specific type: all properties, events, and their descriptions. Use the exact type name from lowdefy_list_types.',
432
- inputSchema: {
433
- kind: z
434
- .enum(['blocks', 'operators', 'actions', 'connections', 'requests'])
435
- .describe('The kind of the type.'),
436
- type: z.string().describe('The exact type name, e.g. "Button", "_get", "MongoDBFind".'),
437
- },
438
- },
439
- ({ kind, type }) => {
440
- const schema = getSchema({ kind, type });
441
- if (schema === null) {
442
- return notFoundResult(
443
- `No schema found for ${kind} type "${type}". Use lowdefy_list_types to see available types.`
444
- );
445
- }
446
- return textResult(schema);
447
- }
448
- );
406
+ registerDevTool('lowdefy_list_plugins', () => textResult(listPlugins()));
449
407
 
450
- server.registerTool(
451
- 'lowdefy_get_examples',
452
- {
453
- description:
454
- 'Get real YAML usage examples for a block type (gallery and example configs shipped with the plugin).',
455
- inputSchema: {
456
- type: z.string().describe('The exact block type name, e.g. "Button".'),
457
- },
458
- },
459
- ({ type }) => {
460
- const examples = getExamples({ type });
461
- if (examples === null) {
462
- return notFoundResult(
463
- `No examples shipped for block type "${type}". Use lowdefy_get_schema for its contract, or lowdefy_get_doc for its documentation page.`
464
- );
465
- }
466
- return textResult(examples);
408
+ registerDevTool('lowdefy_get_schema', ({ kind, type }) => {
409
+ const schema = getSchema({ kind, type });
410
+ if (schema === null) {
411
+ return notFoundResult(
412
+ `No schema found for ${kind} type "${type}". Use lowdefy_list_types to see available types.`
413
+ );
467
414
  }
468
- );
469
-
470
- server.registerTool(
471
- 'lowdefy_get_doc',
472
- {
473
- description:
474
- 'Get a core Lowdefy documentation page as markdown. Look up by slug (e.g. "concepts/lowdefy-schema", "operators/_get") or by kind + type name. Key concept slugs: concepts/lowdefy-schema, concepts/blocks, concepts/events-and-actions, concepts/connections-and-requests, concepts/operators, concepts/page-and-app-state.',
475
- inputSchema: {
476
- slug: z.string().optional().describe('Doc slug, e.g. "operators/_get".'),
477
- kind: z
478
- .enum(['block', 'operator', 'action', 'connection'])
479
- .optional()
480
- .describe('Kind of the type to find the doc for.'),
481
- type: z.string().optional().describe('Type name to find the doc for, e.g. "_get".'),
482
- },
483
- },
484
- ({ slug, kind, type }) => {
485
- const doc = getCoreDoc({ slug, kind, type });
486
- if (doc === null) {
487
- return notFoundResult(
488
- `No doc found${slug ? ` for slug "${slug}"` : ''}${
489
- type ? ` for type "${type}"` : ''
490
- }. Use lowdefy_search_docs to find the right slug.`
491
- );
492
- }
493
- return textResult(doc.markdown);
415
+ return textResult(schema);
416
+ });
417
+
418
+ registerDevTool('lowdefy_get_examples', ({ type }) => {
419
+ const examples = getExamples({ type });
420
+ if (examples === null) {
421
+ return notFoundResult(
422
+ `No examples shipped for block type "${type}". Use lowdefy_get_schema for its contract, or lowdefy_get_doc for its documentation page.`
423
+ );
494
424
  }
425
+ return textResult(examples);
426
+ });
427
+
428
+ registerDevTool('lowdefy_get_doc', ({ slug, kind, type }) => {
429
+ const doc = getDoc({ slug, kind, type });
430
+ if (doc === null) {
431
+ return notFoundResult(
432
+ `No doc found${slug ? ` for slug "${slug}"` : ''}${
433
+ type ? ` for type "${type}"` : ''
434
+ }. Use lowdefy_search_docs to find the right slug.`
435
+ );
436
+ }
437
+ return textResult(appendHazards(doc));
438
+ });
439
+
440
+ registerDevTool('lowdefy_search_docs', ({ query, source }) =>
441
+ textResult(searchDocs({ query, source }))
495
442
  );
496
443
 
497
- server.registerTool(
498
- 'lowdefy_search_docs',
499
- {
500
- description: 'Search the core Lowdefy docs by keyword. Returns matching slugs with snippets.',
501
- inputSchema: {
502
- query: z.string().describe('Search keywords.'),
503
- },
504
- },
505
- ({ query }) => textResult(searchDocs({ query }))
444
+ registerDevTool('lowdefy_search_icons', async ({ query, limit }) =>
445
+ textResult(await searchIcons({ query, limit }))
506
446
  );
507
447
 
508
- server.registerTool(
509
- 'lowdefy_get_plugin_doc',
510
- {
511
- description:
512
- "Get markdown documentation shipped inside an installed plugin package (README, guides). Useful for this project's local custom plugins.",
513
- inputSchema: {
514
- package: z.string().describe('The package name, e.g. "@lowdefy/blocks-antd".'),
515
- },
516
- },
517
- ({ package: packageName }) => {
518
- const doc = getPluginDoc({ packageName });
519
- if (doc === null) {
520
- return notFoundResult(
521
- `Package "${packageName}" ships no markdown docs. Use lowdefy_list_types and lowdefy_get_schema for its types.`
522
- );
523
- }
524
- return textResult(doc.markdown);
448
+ registerDevTool('lowdefy_get_plugin_doc', ({ package: packageName }) => {
449
+ const doc = getPluginDoc({ packageName });
450
+ if (doc === null) {
451
+ return notFoundResult(
452
+ `Package "${packageName}" ships no markdown docs. Use lowdefy_list_types and lowdefy_get_schema for its types.`
453
+ );
525
454
  }
526
- );
455
+ return textResult(doc.markdown);
456
+ });
527
457
 
458
+ registerDevTool('lowdefy_journey_session', ({ id, since }) => {
459
+ const result = readJourneySession({ id, since });
460
+ if (!type.isUndefined(result.error)) {
461
+ return notFoundResult(result.error);
462
+ }
463
+ return textResult(result.text);
464
+ });
465
+
466
+ const unregistered = Object.keys(devToolDefinitions).filter((name) => !registered.has(name));
467
+ if (unregistered.length > 0) {
468
+ throw new Error(`Dev tools defined without a handler: ${unregistered.join(', ')}.`);
469
+ }
528
470
  return server;
529
471
  }
530
472
 
473
+ // Forwards dev events to one connected MCP client as notifications/message
474
+ // (the only server→client notification MCP clients surface generically; it
475
+ // needs the logging capability declared above). Build failures go out at
476
+ // error level so clients that filter by level still see them.
477
+ function subscribeMcpServerToDevEvents(server) {
478
+ return subscribeToDevEvents((event) =>
479
+ server.server.sendLoggingMessage({
480
+ level: event.type === 'build' && event.status === 'error' ? 'error' : 'info',
481
+ logger: 'lowdefy',
482
+ data: event,
483
+ })
484
+ );
485
+ }
486
+
487
+ export { subscribeMcpServerToDevEvents };
531
488
  export default createDocsMcpServer;