@lowdefy/docs-content 0.0.0-experimental-20260928120643 → 6.1.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 (226) hide show
  1. package/README.md +0 -1
  2. package/content/actions/link.md +0 -17
  3. package/content/actions/login.md +5 -23
  4. package/content/actions/logout.md +5 -5
  5. package/content/actions/pdfmake.md +2 -2
  6. package/content/actions/setdarkmode.md +2 -2
  7. package/content/agents/agent-properties.md +2 -4
  8. package/content/agents/agentchat.md +6 -21
  9. package/content/agents/agentconversations.md +2 -2
  10. package/content/concepts/ai-agent-docs.md +101 -313
  11. package/content/concepts/cli.md +3 -34
  12. package/content/concepts/connections-and-requests.md +0 -402
  13. package/content/concepts/custom-html.md +0 -40
  14. package/content/concepts/custom-styling.md +5 -50
  15. package/content/concepts/events-and-actions.md +2 -64
  16. package/content/concepts/i18n.md +2 -7
  17. package/content/concepts/keyboard-shortcuts.md +2 -2
  18. package/content/concepts/layout-overview.md +1 -1
  19. package/content/concepts/logger.md +0 -81
  20. package/content/concepts/lowdefy-schema.md +1 -4
  21. package/content/concepts/menus.md +7 -7
  22. package/content/concepts/module-authoring.md +2 -2
  23. package/content/connections/anthropic.md +2 -162
  24. package/content/connections/awss3.md +0 -54
  25. package/content/connections/google.md +2 -162
  26. package/content/connections/mongodb.md +3 -121
  27. package/content/connections/openai-connection.md +2 -162
  28. package/content/container-blocks/affix.md +7 -47
  29. package/content/container-blocks/alert.md +24 -76
  30. package/content/container-blocks/badge.md +24 -100
  31. package/content/container-blocks/box.md +9 -9
  32. package/content/container-blocks/card.md +25 -80
  33. package/content/container-blocks/carousel.md +7 -60
  34. package/content/container-blocks/collapse.md +9 -87
  35. package/content/container-blocks/configprovider.md +13 -35
  36. package/content/container-blocks/confirmmodal.md +23 -69
  37. package/content/container-blocks/content.md +98 -62
  38. package/content/container-blocks/descriptions.md +4 -45
  39. package/content/container-blocks/drawer.md +28 -92
  40. package/content/container-blocks/dropdownmenu.md +31 -119
  41. package/content/container-blocks/flex.md +62 -40
  42. package/content/container-blocks/footer.md +11 -15
  43. package/content/container-blocks/googlemapsheatmap.md +1 -1
  44. package/content/container-blocks/googlemapsscript.md +2 -2
  45. package/content/container-blocks/header.md +36 -71
  46. package/content/container-blocks/label.md +7 -34
  47. package/content/container-blocks/layout.md +34 -52
  48. package/content/container-blocks/masonry.md +71 -17
  49. package/content/container-blocks/mobilemenu.md +88 -131
  50. package/content/container-blocks/modal.md +26 -114
  51. package/content/container-blocks/pageheadermenu.md +31 -81
  52. package/content/container-blocks/pagesidebarlayout.md +26 -169
  53. package/content/container-blocks/pagesidermenu.md +42 -95
  54. package/content/container-blocks/popover.md +31 -34
  55. package/content/container-blocks/result.md +21 -32
  56. package/content/container-blocks/sider.md +122 -85
  57. package/content/container-blocks/span.md +7 -7
  58. package/content/container-blocks/spinner.md +3 -3
  59. package/content/container-blocks/splitter.md +68 -150
  60. package/content/container-blocks/tabs.md +12 -64
  61. package/content/container-blocks/tooltip.md +46 -22
  62. package/content/container-blocks/watermark.md +4 -22
  63. package/content/controls/reject.md +2 -4
  64. package/content/controls/throw.md +0 -1
  65. package/content/controls/try.md +2 -3
  66. package/content/deployment/deployment-environments.md +6 -8
  67. package/content/deployment/docker.md +7 -22
  68. package/content/deployment/node-server.md +0 -4
  69. package/content/deployment/vercel.md +6 -13
  70. package/content/display-blocks/aggrid.md +11 -26
  71. package/content/display-blocks/anchor.md +15 -15
  72. package/content/display-blocks/avatar.md +37 -57
  73. package/content/display-blocks/breadcrumb.md +21 -70
  74. package/content/display-blocks/button.md +18 -92
  75. package/content/display-blocks/calendar.md +0 -12
  76. package/content/display-blocks/dangeroushtml.md +1 -1
  77. package/content/display-blocks/difflist.md +1 -1
  78. package/content/display-blocks/difftimeline.md +1 -1
  79. package/content/display-blocks/divider.md +3 -64
  80. package/content/display-blocks/dropdownbutton.md +28 -79
  81. package/content/display-blocks/echart.md +1 -35
  82. package/content/display-blocks/floatbutton.md +42 -129
  83. package/content/display-blocks/html.md +1 -61
  84. package/content/display-blocks/icon.md +67 -250
  85. package/content/display-blocks/menu.md +73 -145
  86. package/content/display-blocks/message.md +124 -38
  87. package/content/display-blocks/notification.md +222 -94
  88. package/content/display-blocks/paragraph.md +9 -59
  89. package/content/display-blocks/progress.md +17 -96
  90. package/content/display-blocks/qrcode.md +4 -19
  91. package/content/display-blocks/s3download.md +1 -1
  92. package/content/display-blocks/search.md +7 -17
  93. package/content/display-blocks/skeletonbutton.md +0 -1
  94. package/content/display-blocks/skeletoninput.md +0 -1
  95. package/content/display-blocks/statistic.md +17 -75
  96. package/content/display-blocks/steps.md +25 -58
  97. package/content/display-blocks/tag.md +17 -102
  98. package/content/display-blocks/title.md +11 -50
  99. package/content/display-blocks/tour.md +23 -33
  100. package/content/input-blocks/autocomplete.md +9 -81
  101. package/content/input-blocks/buttonselector.md +8 -45
  102. package/content/input-blocks/checkboxselector.md +7 -97
  103. package/content/input-blocks/checkboxswitch.md +3 -33
  104. package/content/input-blocks/colorselector.md +14 -63
  105. package/content/input-blocks/daterangeselector.md +45 -165
  106. package/content/input-blocks/dateselector.md +41 -139
  107. package/content/input-blocks/datetimeselector.md +49 -158
  108. package/content/input-blocks/inputgallery.md +3 -3
  109. package/content/input-blocks/monthselector.md +52 -120
  110. package/content/input-blocks/multipleselector.md +39 -150
  111. package/content/input-blocks/numberinput.md +12 -98
  112. package/content/input-blocks/pagination.md +14 -70
  113. package/content/input-blocks/paragraphinput.md +37 -51
  114. package/content/input-blocks/passwordinput.md +6 -60
  115. package/content/input-blocks/phonenumberinput.md +26 -59
  116. package/content/input-blocks/radioselector.md +7 -83
  117. package/content/input-blocks/ratingslider.md +26 -57
  118. package/content/input-blocks/s3uploadbutton.md +24 -24
  119. package/content/input-blocks/s3uploaddragger.md +3 -3
  120. package/content/input-blocks/s3uploadphoto.md +5 -5
  121. package/content/input-blocks/segmentedselector.md +32 -72
  122. package/content/input-blocks/selector.md +21 -109
  123. package/content/input-blocks/switch.md +17 -65
  124. package/content/input-blocks/textarea.md +3 -32
  125. package/content/input-blocks/textinput.md +28 -69
  126. package/content/input-blocks/tiptapmentioninput.md +1 -1
  127. package/content/input-blocks/titleinput.md +12 -39
  128. package/content/input-blocks/treeinput.md +25 -49
  129. package/content/input-blocks/treemultipleselector.md +10 -127
  130. package/content/input-blocks/treeselector.md +9 -97
  131. package/content/input-blocks/upload.md +15 -64
  132. package/content/input-blocks/uploaddragger.md +1 -17
  133. package/content/input-blocks/uploadphoto.md +1 -18
  134. package/content/input-blocks/weekselector.md +55 -120
  135. package/content/list-blocks/controlledlist.md +15 -24
  136. package/content/list-blocks/list.md +11 -11
  137. package/content/list-blocks/masonrylist.md +34 -7
  138. package/content/migration/upgrade-guide.md +0 -1
  139. package/content/operators/_app.md +1 -2
  140. package/content/operators/_error.md +1 -23
  141. package/content/operators/_get.md +6 -6
  142. package/content/operators/_operator.md +3 -17
  143. package/content/operators/_ref.md +0 -258
  144. package/content/operators/_type.md +3 -14
  145. package/content/operators/_user.md +3 -3
  146. package/content/other/introduction.md +1 -1
  147. package/content/plugins/plugins-dev.md +0 -66
  148. package/content/plugins/plugins-introduction.md +1 -27
  149. package/content/testing/e2e-configuration.md +1 -8
  150. package/content/testing/e2e-getting-started.md +1 -1
  151. package/content/testing/e2e-introduction.md +1 -1
  152. package/content/tutorial/tutorial-actions-operators.md +2 -2
  153. package/content/tutorial/tutorial-add-blocks.md +2 -2
  154. package/content/tutorial/tutorial-create-page.md +2 -48
  155. package/content/tutorial/tutorial-display-data-page.md +3 -3
  156. package/content/tutorial/tutorial-requests-sql.md +0 -153
  157. package/content/tutorial/tutorial-start.md +1 -66
  158. package/content/user-authentication/login-and-logout.md +8 -158
  159. package/content/user-authentication/users-introduction.md +0 -8
  160. package/index.json +2 -6065
  161. package/package.json +1 -7
  162. package/scripts/extractAgentDocs.js +4 -36
  163. package/content/actions/emailotpsend.md +0 -58
  164. package/content/actions/emailotpverify.md +0 -77
  165. package/content/actions/getlocalstorage.md +0 -41
  166. package/content/actions/magiclinkverify.md +0 -113
  167. package/content/actions/posthogalias.md +0 -29
  168. package/content/actions/posthogcapture.md +0 -49
  169. package/content/actions/posthogcapturepageleave.md +0 -38
  170. package/content/actions/posthogfeatureflag.md +0 -50
  171. package/content/actions/posthoggroup.md +0 -37
  172. package/content/actions/posthogidentify.md +0 -54
  173. package/content/actions/posthoginit.md +0 -66
  174. package/content/actions/posthogoptin.md +0 -29
  175. package/content/actions/posthogoptout.md +0 -29
  176. package/content/actions/posthogpageview.md +0 -37
  177. package/content/actions/posthogreloadfeatureflags.md +0 -47
  178. package/content/actions/posthogreset.md +0 -28
  179. package/content/actions/posthogsetpersonproperties.md +0 -36
  180. package/content/actions/removelocalstorage.md +0 -26
  181. package/content/actions/setlocalstorage.md +0 -32
  182. package/content/adapter-reference/mongodbauthadapter.md +0 -46
  183. package/content/agents/agents-introduction.md +0 -107
  184. package/content/callback-reference/auth0logoutcallback.md +0 -54
  185. package/content/concepts/blocks.md +0 -150
  186. package/content/concepts/dynamic-page-content.md +0 -304
  187. package/content/concepts/html-attributes.md +0 -285
  188. package/content/concepts/lowdefy-api.md +0 -1110
  189. package/content/concepts/notifications.md +0 -155
  190. package/content/concepts/page-and-app-state.md +0 -244
  191. package/content/concepts/theming.md +0 -582
  192. package/content/connections/aigateway.md +0 -368
  193. package/content/connections/axioshttp.md +0 -181
  194. package/content/controls/while.md +0 -46
  195. package/content/display-blocks/clickablehtml.md +0 -239
  196. package/content/display-blocks/eventlog.md +0 -363
  197. package/content/display-blocks/pollingtimer.md +0 -263
  198. package/content/input-blocks/captcha.md +0 -40
  199. package/content/input-blocks/placesautocomplete.md +0 -319
  200. package/content/input-blocks/tagmultipleselector.md +0 -114
  201. package/content/input-blocks/tagselector.md +0 -114
  202. package/content/migration/auth-upgrade.md +0 -327
  203. package/content/migration/v3-to-v4.md +0 -417
  204. package/content/migration/v6-to-v7.md +0 -222
  205. package/content/operators/_cron.md +0 -292
  206. package/content/other/mongodbadapter.md +0 -3
  207. package/content/plugins/plugins-icon-sets.md +0 -267
  208. package/content/plugins/posthog.md +0 -275
  209. package/content/provider-reference/auth-js-providers.md +0 -107
  210. package/content/provider-reference/openidconnectprovider.md +0 -44
  211. package/content/testing/config-tests.md +0 -278
  212. package/content/tutorial/tutorial-requests-api.md +0 -155
  213. package/content/user-authentication/auth-configuration.md +0 -266
  214. package/content/user-authentication/auth-steps.md +0 -138
  215. package/content/user-authentication/mcp-oauth.md +0 -292
  216. package/content/user-authentication/organizations.md +0 -221
  217. package/content/user-authentication/protected-pages-apis.md +0 -97
  218. package/content/user-authentication/roles.md +0 -49
  219. package/content/user-authentication/two-factor.md +0 -229
  220. package/content/user-authentication/user-object.md +0 -58
  221. package/content/websockets/websocket-publish.md +0 -108
  222. package/content/websockets/websocket-subscriptions.md +0 -100
  223. package/content/websockets/websockets-introduction.md +0 -97
  224. package/hazards.json +0 -44
  225. package/scripts/hashDocSources.js +0 -38
  226. package/scripts/recordSourceHashes.js +0 -88
@@ -2,92 +2,45 @@
2
2
 
3
3
  When you run `lowdefy dev`, the development server also serves a documentation API and an [MCP](https://modelcontextprotocol.io) endpoint built for AI coding agents. It describes everything installed in _your_ project — every block, operator, action, connection and request type from core Lowdefy plugins _and_ your own local plugins — plus the full Lowdefy documentation as markdown. This means an agent like Claude Code never has to guess type names or property shapes: it can look up the exact schema, real examples, and the relevant docs page while it writes your config.
4
4
 
5
- Everything is served under the `/lowdefy-docs` path of your dev server. It is always on in dev, and never part of your production server. When your app sets `config.basePath`, the dev server serves these routes under the base path too, like the rest of the app: with `basePath: /app` they live at `/app/lowdefy-docs`. `lowdefy mcp` finds the right address by itself.
5
+ Everything is served under the `/lowdefy-docs` path of your dev server (default `http://localhost:3000`). No setup or configuration is needed — it is always on in dev, and never part of your production server.
6
6
 
7
7
  > The `/lowdefy-docs` route prefix is reserved by the dev server. A page with `id: lowdefy-docs` will not be reachable in dev.
8
8
 
9
- ## Connect your agent: `lowdefy mcp`
9
+ ## The MCP endpoint
10
10
 
11
- Run `lowdefy agent-setup` once in your project. It registers the `lowdefy-docs` MCP server in `.mcp.json`:
11
+ The dev server exposes an MCP server (streamable HTTP) at:
12
12
 
13
- ```json
14
- {
15
- "mcpServers": {
16
- "lowdefy-docs": {
17
- "type": "stdio",
18
- "command": "node",
19
- "args": ["apps/main/node_modules/lowdefy/dist/index.js", "mcp"]
20
- }
21
- }
22
- }
13
+ ```
14
+ http://localhost:3000/lowdefy-docs/mcp
23
15
  ```
24
16
 
25
- Your agent client starts `lowdefy mcp` itself at the start of each session, so the tools are always there — whether or not a dev server is running yet. Nothing in the entry names a port, so the same checked-in file works in every git worktree.
26
-
27
- `lowdefy mcp` sends each tool call to the dev server of the app and checkout the agent is working in:
28
-
29
- - **It works out the app** from the session's working directory. Every tool also takes an optional `directory` argument — pass it when the repository holds several apps, or when an agent works in a different git worktree from the session (a subagent in its own worktree, for example).
30
- - **It only acts on the session's checkout and its git worktrees.** Starting a dev server runs the app's `package.json` dev script, so a `directory` outside the checkout the session started in (and outside `git worktree list` of that repository) is refused. When the agent client supports MCP elicitation, `lowdefy mcp` asks you first instead; your answer holds for the session.
31
- - **It starts the dev server when needed**, through the [Lowdefy hub](/cli#hub): a small per-user background process that gives each app its own port, runs the app's own dev script (so a secrets-manager wrapper still applies — set `cli.devScript` if several scripts run `lowdefy dev`), and stops servers nobody uses. A dev server you started yourself with `lowdefy dev` is used as it is, and never stopped by an agent.
32
- - **Every result starts with the app and checkout it came from**, for example `apps/main @ app-wt-invoices · http://localhost:4102`, so an agent never mistakes another worktree's answer for its own.
33
-
34
- Agents manage the dev server with these tools, and never run `lowdefy dev`, choose ports, or kill processes themselves:
35
-
36
- | Tool | Purpose |
37
- | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
38
- | `lowdefy_dev_start` | Start the app's dev server (or return the running one) and wait until it is ready. `restart: true` after local plugin or `.env` changes; `clean: true` also deletes the build directory |
39
- | `lowdefy_dev_stop` | Stop the app's dev server if the hub started it |
40
- | `lowdefy_dev_status` | Owner, state, URL and build status, without starting anything |
41
- | `lowdefy_dev_logs` | Recent output of a hub-started dev server, optionally filtered |
42
- | `lowdefy_run_tests` | Run the app's journeys (`tests/journeys/*.yaml`, as `lowdefy test` does) against its dev server and return each result as data |
43
- | `lowdefy_dev_list` | Dev servers across the checkout's apps and other checkouts |
44
-
45
- The dev server itself also serves the MCP endpoint over streamable HTTP at `/lowdefy-docs/mcp`, for clients that connect by URL. Through `lowdefy mcp`, restart is `lowdefy_dev_start` with `restart: true` rather than `lowdefy_restart`.
46
-
47
- The `/lowdefy-docs` tools accept requests from agents and tools on your machine and from the dev app's own pages; a request a browser makes on behalf of another website is refused.
48
-
49
- ## The tools
50
-
51
- The dev server provides these tools:
52
-
53
- | Tool | Purpose |
54
- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
55
- | `lowdefy_overview` | Start here — what is installed, counts per kind, and how to use the rest |
56
- | `lowdefy_list_types` | List ALL available types of a kind (blocks, operators, actions, connections, requests) |
57
- | `lowdefy_list_plugins` | Installed plugin packages, including local plugins, and the types each provides |
58
- | `lowdefy_get_schema` | JSON Schema for a specific type — all properties and events |
59
- | `lowdefy_get_examples` | Real YAML usage examples for a block type |
60
- | `lowdefy_get_doc` | A Lowdefy docs page as markdown, by slug or by type name |
61
- | `lowdefy_search_docs` | Keyword search over the Lowdefy docs |
62
- | `lowdefy_search_icons` | Search icon names: semantic names first, then Lucide names, then the names of installed icon sets (qualified). Never invent an icon name |
63
- | `lowdefy_get_plugin_doc` | Markdown (READMEs, guides) shipped inside an installed plugin package |
64
- | `lowdefy_build_status` | Current build errors and warnings (with source file locations) plus recent browser runtime errors — call after every edit |
65
- | `lowdefy_check` | Validate the whole app as `lowdefy build` would, without building — every page, and the prod-only checks dev shows as warnings come back as errors. Call before calling a change done |
66
- | `lowdefy_get_page_config` | The fully built config for a page, or its structured build errors |
67
- | `lowdefy_screenshot_page` | PNG screenshot of a rendered page (headless Chromium) for visual verification; `width`, `height` and `colorScheme: "dark"` check a phone layout or dark mode |
68
- | `lowdefy_run_journey` | Drive a page headless through declarative steps (`click`, `fill`, `goto`, `email`, `as`, `expect`, …) and assert state, visibility, text, url, title or email — verify behaviour, not just layout |
69
- | `lowdefy_find_config` | Which yaml file (and line) defines a given page, block, or request id |
70
- | `lowdefy_scaffold_page` | Create a new page yaml file with a canonical minimal structure |
71
- | `lowdefy_app_map` | The whole-app graph: every page, menu, connection, endpoint, and agent in one call |
72
- | `lowdefy_inspect_state` | The LIVE state, request results, and event log of a running page — reads your open browser tab, or runs the page headless |
73
- | `lowdefy_eval_operator` | Evaluate any operator expression against live page state — a REPL for config |
74
- | `lowdefy_run_request` | Execute a request with a test payload, as a given `user`, to verify data shape (read-only unless opted in) |
75
- | `lowdefy_run_endpoint` | Execute an Api endpoint routine with a test payload, as a given `user` or as a system context (`system: true`), to see what it returns, rejects or throws (needs `allowWriteRequests`; a `:reject` comes back as data) |
76
- | `lowdefy_snapshot_state` | Capture live page state + request responses into a committable checkpoint folder |
77
- | `lowdefy_load_state` | Restore a state checkpoint — headless, or a `?_checkpoint=` URL for manual testing |
78
- | `lowdefy_list_state_checkpoints` | List saved state checkpoints |
79
- | `lowdefy_checkpoint_to_mocks` | Convert a state checkpoint into e2e `mocks.yaml` fixtures |
80
- | `lowdefy_restart` | Restart the dev server process — after editing a local plugin's server-side code, or when `build_status` looks stale. Then call `lowdefy_build_status` with `wait: true`, which answers once the server is back |
81
- | `lowdefy_checkpoint` | Snapshot all config files before risky changes |
82
- | `lowdefy_revert_checkpoint` | Restore config files from a checkpoint |
83
-
84
- ## Hazards — what the schema cannot tell you
85
-
86
- A hazard is a behaviour of a type that its schema does not show, but that an agent must know before writing config: the `Html` block sanitises its content, so a `<style>` tag is stripped; closing a `Modal` keeps the state of the blocks inside it, while `visible: false` prunes it; `_state` inside a request's `properties` is always `undefined`; a `_ref` to a `.njk` file renders at build time. Each hazard is `{ id, message, see }` — a stable kebab-case `id`, one or two sentences saying what happens and what to do instead, and a docs slug (or `null`) for `lowdefy_get_doc`.
87
-
88
- `lowdefy_get_schema`, `lowdefy_get_doc` (when looked up by kind and type) and `lowdefy_find_config` (per match) return `hazards` alongside their normal result — `lowdefy_get_doc` appends them as a `## Hazards` section of the markdown. Some hazards are contextual: `tenant-wall-lookup` is only returned for a request whose connection is tenant-walled, so `find_config` on that request explains the wall's `$lookup` injection while the same request over a `tenant: shared` connection stays quiet.
89
-
90
- Hazards come from two places. Framework-level hazards ship with `@lowdefy/docs-content` (`hazards.json`). Type-attached hazards are declared by the plugin: a block sets `meta.hazards`, a request sets `meta.hazards` on the request function, and an operator package exports a `./metas` module with `{ hazards }` per operator — see [Plugins](/plugins-introduction). Local plugins in your project use the same channel, so your own blocks can warn an agent about their own surprises.
17
+ It provides these tools:
18
+
19
+ | Tool | Purpose |
20
+ | ------------------------ | ----------------------------------------------------------------------- |
21
+ | `lowdefy_overview` | Start here — what is installed, counts per kind, and how to use the rest |
22
+ | `lowdefy_list_types` | List ALL available types of a kind (blocks, operators, actions, connections, requests) |
23
+ | `lowdefy_list_plugins` | Installed plugin packages, including local plugins, and the types each provides |
24
+ | `lowdefy_get_schema` | JSON Schema for a specific type — all properties and events |
25
+ | `lowdefy_get_examples` | Real YAML usage examples for a block type |
26
+ | `lowdefy_get_doc` | A Lowdefy docs page as markdown, by slug or by type name |
27
+ | `lowdefy_search_docs` | Keyword search over the Lowdefy docs |
28
+ | `lowdefy_get_plugin_doc` | Markdown (READMEs, guides) shipped inside an installed plugin package |
29
+ | `lowdefy_build_status` | Current build errors and warnings (with source file locations) plus recent browser runtime errors — call after every edit |
30
+ | `lowdefy_get_page_config`| The fully built config for a page, or its structured build errors |
31
+ | `lowdefy_screenshot_page`| PNG screenshot of a rendered page (headless Chromium) for visual verification |
32
+ | `lowdefy_find_config` | Which yaml file (and line) defines a given page, block, or request id |
33
+ | `lowdefy_scaffold_page` | Create a new page yaml file with a canonical minimal structure |
34
+ | `lowdefy_app_map` | The whole-app graph: every page, menu, connection, endpoint, and agent in one call |
35
+ | `lowdefy_inspect_state` | The LIVE state, request results, and event log of a running page — reads your open browser tab, or runs the page headless |
36
+ | `lowdefy_eval_operator` | Evaluate any operator expression against live page state — a REPL for config |
37
+ | `lowdefy_run_request` | Execute a request with a test payload to verify data shape (read-only unless opted in) |
38
+ | `lowdefy_snapshot_state` | Capture live page state + request responses into a committable checkpoint folder |
39
+ | `lowdefy_load_state` | Restore a state checkpoint — headless, or a `?_checkpoint=` URL for manual testing |
40
+ | `lowdefy_list_state_checkpoints` | List saved state checkpoints |
41
+ | `lowdefy_checkpoint_to_mocks` | Convert a state checkpoint into e2e `mocks.yaml` fixtures |
42
+ | `lowdefy_checkpoint` | Snapshot all config files before risky changes |
43
+ | `lowdefy_revert_checkpoint` | Restore config files from a checkpoint |
91
44
 
92
45
  ## Annotate your app — point, draw, copy, paste
93
46
 
@@ -115,57 +68,19 @@ Hold **Option** (macOS) or **Alt** (Windows/Linux) and click any element in your
115
68
  The dev server rebuilds automatically when config changes, so an agent works in a tight loop:
116
69
 
117
70
  1. Discover types and schemas, write or edit YAML.
118
- 2. Call `lowdefy_build_status` with `wait: true` (`GET /lowdefy-docs/build-status?wait=true`) — it answers once the dev server has processed your edit, rather than with the build before it. That includes a server restart or plugin install the edit needs (a new connection type, a new plugin package, a `.env` change), so the next call reaches a ready server. Did the build succeed? Errors come back with the exact source file and location. A build that fails with an internal error carries its message, stack and the config file it was resolving.
119
- 3. Pages are built when they are first requested, so the config build's status says nothing about page content. With `wait: true`, build status also builds every page your edit touched (a page whose files changed since its last build, a page built before the latest config build, or a page file changed since the dev server started) and reports under `pages`: `checked` lists the pages it built, `failed` the pages whose last build failed with their errors, `changedSinceBuild` pages changed on disk that the dev server has not rebuilt yet, and `unbuilt` how many pages nothing has built since the dev server started. `lowdefy_check` validates every page.
120
- 4. Call `lowdefy_get_page_config` to confirm the page builds, and `lowdefy_screenshot_page` to see it rendered.
121
- 5. Runtime errors from the browser (operator errors, block render errors) also appear in `lowdefy_build_status` under `clientErrors`, so problems that only show at runtime still reach the agent.
122
- 6. Server-side failures appear beside them under `serverErrors` — a request whose database filter is malformed, an endpoint step that throws, an MCP tool call or an agent tool call that fails — each with the yaml `source` (`file:line`) and `config` path that produced it, plus the `endpointId`, `requestId` and `pageId` where known. The store holds the last 50 errors and is cleared on dev server restart.
123
- 7. Each browser and server error carries the `buildId` it happened under: the time of the latest config build or page edit. Errors from before the current build are listed apart under `earlierErrors`, since they may already be fixed; `clientErrors` and `serverErrors` hold only errors under the current build.
124
-
125
- ### Events are pushed — no need to poll
126
-
127
- The dev server pushes what changed instead of waiting to be asked. Two channels carry the same events:
128
-
129
- - **MCP clients** receive them as `notifications/message` from logger `lowdefy` on the standalone GET stream of `/lowdefy-docs/mcp` (the server declares the `logging` capability, so any MCP client surfaces them). Failed builds arrive at level `error`, everything else at `info`.
130
- - **Everything else** uses `GET /lowdefy-docs/events`, a Server-Sent Events stream — `curl -N http://localhost:3000/lowdefy-docs/events` — with one frame per event, named by the event type.
131
-
132
- Every event carries `type` and an ISO `timestamp`. The four types:
133
-
134
- | Type | When | Carries |
135
- | -------------- | --------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
136
- | `restart` | First event on every connection | `bootedAt` — the dev server process start time, so a reconnecting client can tell a restart from a dropped connection |
137
- | `build` | Every time a rebuild finishes, success or failure | `status`, `errorCount`, `warningCount`, `errors`, `warnings`, and `stale` / `staleSince` (see below) |
138
- | `client_error` | A browser reports a runtime error | The same entry `lowdefy_build_status` lists under `clientErrors` |
139
- | `server_error` | A request, endpoint, MCP tool or agent tool fails on the server | The same entry `lowdefy_build_status` lists under `serverErrors` |
140
-
141
- Events are not buffered: a client that connects after an event missed it, and should call `lowdefy_build_status` — which is derived from the same `build/buildStatus.json` — for the current picture. Act on a `build` event with `status: "error"` immediately; `lowdefy_build_status` remains the full report.
142
-
143
- ### When a build fails, answers are marked stale
144
-
145
- A failed rebuild does not take the dev server down — it keeps serving the last build that succeeded, so you can carry on looking at the app while you fix the error. That means every tool can keep answering from a build that predates the latest edits, which is a trap for an agent.
146
-
147
- So while the last build is failing, every answer says so:
148
-
149
- - MCP tool results start with a `STALE: ...` text item.
150
- - JSON responses from `/lowdefy-docs` carry `stale: true`, `staleSince` and `staleReason`.
151
- - Markdown responses are prefixed with a `> STALE: ...` banner.
152
- - Every `/lowdefy-docs` response carries the `X-Lowdefy-Stale` and `X-Lowdefy-Stale-Since` headers.
153
-
154
- Nothing is refused — the last-known-good schema or page config is often exactly what you need while fixing the build. The flag disappears as soon as a build succeeds. Call `lowdefy_build_status` (or `GET /lowdefy-docs/build-status`) for the errors.
155
-
156
- Some build warnings are only warnings in `lowdefy dev` — they fail `lowdefy build`. Those carry `"prodError": true` in `lowdefy_build_status`, are printed in the dev terminal as `[ConfigWarning · fails in prod] …`, and are badged **fails in prod** on a dark-orange bar in the browser error bar. Treat them as errors: the production build will not pass until they are fixed.
71
+ 2. Call `lowdefy_build_status` — did the build succeed? Errors come back with the exact source file and location.
72
+ 3. Call `lowdefy_get_page_config` to confirm the page builds, and `lowdefy_screenshot_page` to see it rendered.
73
+ 4. Runtime errors from the browser (operator errors, block render errors) also appear in `lowdefy_build_status`, so problems that only show at runtime still reach the agent.
157
74
 
158
75
  ## Live state — the agent sees what you see
159
76
 
160
77
  When you have a page open in your browser, the agent can read its **actual live state** — page state, request results, and the event log of recent actions — via `lowdefy_inspect_state`. Reproduce a problem by clicking through the app, then ask the agent to look: it inspects your exact tab, not a guess. `lowdefy_eval_operator` then evaluates any operator expression (like `{"_state": "customer.name"}`) against that same live context, so `_state`/`_request` binding bugs get debugged against real data. With no tab open, both tools run the page headless instead.
161
78
 
162
- A headless capture waits for the page's full async lifecycle before it reads anything: `onInit`, `onInitAsync`, `onMount` and `onMountAsync`, every in-flight request, and the first message on each websocket subscription. A page whose data arrives through an `onMountAsync` `Request` action is therefore captured with its data, not empty. If a page has not settled within 15 seconds the result is still returned, carrying `ready: false` and a note — read that result as a snapshot of a page that was still loading, not as the page's settled state. One case escapes the wait: an event with a `debounce` is not yet marked loading during its delay, so a debounced `onMountAsync` can be captured before it runs.
163
-
164
79
  ## Auth-protected pages
165
80
 
166
- The headless renderer behind `lowdefy_screenshot_page` and `lowdefy_inspect_state` authenticates as a signed-in user, so pages with `auth.public: false` render for the agent instead of bouncing to the sign-in page. That default user carries **no roles**, so a page or request gated on a role still comes back refused or empty.
81
+ The headless renderer behind `lowdefy_screenshot_page` and `lowdefy_inspect_state` authenticates as a signed-in user, so pages with `auth.public: false` render for the agent instead of returning a 404. That default user carries **no roles**, so a page or request gated on a role still comes back refused or empty.
167
82
 
168
- To act as a specific caller, pass `user` — every tool that renders a page headless accepts it (`lowdefy_screenshot_page`, `lowdefy_run_journey`, `lowdefy_inspect_state`, `lowdefy_eval_operator`, `lowdefy_load_state`), as do `lowdefy_run_request` and `lowdefy_run_endpoint`:
83
+ To act as a specific caller, pass `user` — every tool that renders a page headless accepts it: `lowdefy_screenshot_page`, `lowdefy_inspect_state`, `lowdefy_eval_operator` and `lowdefy_load_state`:
169
84
 
170
85
  ```json
171
86
  { "pageId": "users", "user": { "roles": ["admin"] } }
@@ -173,9 +88,9 @@ To act as a specific caller, pass `user` — every tool that renders a page head
173
88
 
174
89
  It is merged over the default user, so `{"roles": [...]}` is usually all you need. No auth engine runs for an injected caller, so nothing derives the rest of the record — include `email`, `profile` or `attributes` in the object if the page reads them. Every call opens its own browser context, so one call can act as an admin and the next as a plain member.
175
90
 
176
- `user` applies to the headless renderer only — it is never applied to a page you open in your own browser, which carries your real session and cannot be re-identified. `lowdefy_inspect_state` and `lowdefy_eval_operator` normally prefer your open tab, so passing `user` selects the headless source instead; combining it with `source: "tab"` is an error rather than a silently ignored role, as is combining it with `lowdefy_load_state`'s `mode: "registry-only"` (that mode hands you a URL to open yourself). The plain HTTP routes take the same param: `?user={"roles":["admin"]}` on the GET routes, a `user` key in the body of `POST /lowdefy-docs/journey`, `POST /lowdefy-docs/eval-operator`, `POST /lowdefy-docs/state-checkpoints/load`, `POST /lowdefy-docs/run-request` and `POST /lowdefy-docs/run-endpoint`. They answer a malformed or contradictory `user` with a `400`, distinct from the `502` a failed render returns.
91
+ `user` applies to the headless renderer only — it is never applied to a page you open in your own browser, which carries your real session and cannot be re-identified. `lowdefy_inspect_state` and `lowdefy_eval_operator` normally prefer your open tab, so passing `user` selects the headless source instead; combining it with `source: "tab"` is an error rather than a silently ignored role, as is combining it with `lowdefy_load_state`'s `mode: "registry-only"` (that mode hands you a URL to open yourself). The plain HTTP routes take the same param: `?user={"roles":["admin"]}` on the GET routes, a `user` key in the body of `POST /lowdefy-docs/eval-operator` and `POST /lowdefy-docs/state-checkpoints/load`. They answer a malformed or contradictory `user` with a `400`, distinct from the `502` a failed render returns.
177
92
 
178
- To bypass login for the whole dev server — your own browser included — start it with a mock user instead: `lowdefy dev --mock-user '{"id":"dev","roles":["admin"]}'` (or configure `auth.dev.mockUser`). See [Auth Configuration](/auth-configuration#mock-user-for-testing-dev-server-only).
93
+ To bypass login for the whole dev server — your own browser included — start it with a mock user instead: `lowdefy dev --mock-user '{"sub":"dev","roles":["admin"]}'` (or configure `auth.dev.mockUser`). See [Auth Configuration](/auth-configuration#mock-user-for-testing-dev-server-only).
179
94
 
180
95
  ## State checkpoints — put the app in a known state
181
96
 
@@ -198,11 +113,7 @@ Loading it back (`lowdefy_load_state`) serves the recorded request data from the
198
113
 
199
114
  ## Running requests safely
200
115
 
201
- `lowdefy_run_request` executes a request with a test payload so the agent can verify the data shape a page will receive. Read-only request types (like `MongoDBFind`) always run. The page is built first when it changed since its last build, so the request that runs is the one in the config now; a page that fails to build answers `refused: true` with its `buildErrors`.
202
-
203
- A response over 40,000 serialized characters is not returned inline: it is written in full to a JSON file under `.lowdefy/responses/` in the app directory, and the result carries `responseFile` (its path), `responseChars` and, for an array, `responseItems`. Pass `saveResponse: true` to always write the response there. `lowdefy_run_endpoint` does the same. Only the 20 most recent response files are kept.
204
-
205
- Pass `user` to run the request as a specific caller — `{ "pageId": "users", "requestId": "get_users", "user": { "roles": ["admin"] } }`. Without `user` the request runs as a roleless anonymous caller, so a tenant-walled or role-gated request returns empty rows rather than an error. Impersonation never unlocks writes: the write gate below applies to every caller. Write requests are refused unless you opt in:
116
+ `lowdefy_run_request` executes a request with a test payload so the agent can verify the data shape a page will receive. Read-only request types (like `MongoDBFind`) always run. Write requests are refused unless you opt in:
206
117
 
207
118
  ```yaml
208
119
  cli:
@@ -210,219 +121,97 @@ cli:
210
121
  allowWriteRequests: true
211
122
  ```
212
123
 
213
- ## Running endpoints
124
+ This is dev-only — enable it when you're comfortable with the agent writing to your dev data.
214
125
 
215
- `lowdefy_run_endpoint` (or `POST /lowdefy-docs/run-endpoint`) executes an `Api` endpoint routine headlessly with a test payload, so the agent can verify what a routine returns, rejects or throws without clicking through the page that calls it:
126
+ ## Setting up a project — one command
216
127
 
217
- ```json
218
- { "endpointId": "create_order", "payload": { "sku": "A1" }, "user": { "roles": ["admin"] } }
128
+ ```bash
129
+ npx lowdefy agent-setup
219
130
  ```
220
131
 
221
- Endpoints are not classified read-only — a routine has no `checkWrite` meta, and a single routine can read, write, call other endpoints and send notifications — so running one always requires the same `cli.agentTools.allowWriteRequests: true` opt-in as write requests. Without it the tool answers `refused: true` with the reason and how to enable it, and nothing runs.
132
+ This writes three files into your project (merging safely if they exist): `.mcp.json` registering the `lowdefy-docs` MCP server, `.claude/skills/lowdefy-config/SKILL.md` teaching Claude Code the workflow, and an `AGENTS.md` section for other coding agents. Use `--port` if your dev server doesn't run on 3000.
222
133
 
223
- The result is the same `{ error, response, status, success }` object the HTTP endpoint route returns. A `:reject` or `:throw` in the routine is not a tool failure: it comes back as `success: false` with `status: "reject"` or `"error"` and the routine's own `error`, so the agent can assert on the shape it designed. `InternalApi` endpoints are refused with the same message HTTP callers get, an unknown `endpointId` answers `refused: true`, and faults that escape the routine (an auth refusal, a missing connection) come back as `error: { name, message, source, configKey }`. Only malformed input — a missing `endpointId` or a non-object `user` — is a `400`.
134
+ ## Using it with Claude Code manually
224
135
 
225
- Scheduled and detached-only endpoints are `InternalApi` by design, so a plain run refuses them. Pass `system: true` to run the routine as a system context instead — the same code path `/api/cron` and `/api/detached` use: no session, no user (`_user` is `undefined`), the endpoint's `auth` is not checked, and `InternalApi` endpoints are allowed. This is the local test path for a `schedules` routine, without setting `CRON_SECRET` or curling the cron route:
136
+ Add the MCP server to your project so Claude Code can use it. In your project directory run:
226
137
 
227
- ```json
228
- { "endpointId": "purge_stale_conversations", "payload": { "mode": "full" }, "system": true }
138
+ ```bash
139
+ claude mcp add --transport http lowdefy-docs http://localhost:3000/lowdefy-docs/mcp
229
140
  ```
230
141
 
231
- `system` cannot be combined with `user` (a `400`); everything else — the `allowWriteRequests` gate, the result shape, rejects as data — is unchanged. One limit: a nested `CallApi` step with `detached: true` is not faked. It still dispatches over HTTP against the dev server's origin and needs `CRON_SECRET` set, so without it the routine's own error comes back as the result.
232
-
233
- This is dev-only — enable it when you're comfortable with the agent writing to your dev data.
234
-
235
- ## Journeys — verify behaviour, not just layout
236
-
237
- A screenshot shows what rendered; it cannot tell the agent whether the form submits, the modal opens or the filter works. `lowdefy_run_journey` (or `POST /lowdefy-docs/journey`) opens a page headless and drives it through a declarative list of steps — a tiny Playwright — then reports what happened as data. Blocks are addressed by their `blockId`, through the same `#bl-<blockId>` DOM contract `@lowdefy/e2e-utils` uses, so a journey reads like the config it tests:
142
+ Or commit a `.mcp.json` file at your project root so the whole team gets it:
238
143
 
239
144
  ```json
240
145
  {
241
- "pageId": "new_customer",
242
- "user": { "roles": ["admin"] },
243
- "urlQuery": { "ref": "campaign-1" },
244
- "steps": [
245
- { "fill": { "blockId": "name", "value": "Ada Lovelace" } },
246
- { "select": { "blockId": "country", "value": "United Kingdom" } },
247
- { "click": "submit" },
248
- { "wait": { "request": "create_customer" } },
249
- { "expect": { "state": { "path": "created", "equals": true } } },
250
- { "expect": { "visible": "success_modal" } },
251
- { "expect": { "text": { "blockId": "success_message", "contains": "Ada Lovelace" } } },
252
- { "screenshot": "after-submit" },
253
- { "press": "Escape" },
254
- { "click": "go_to_list" },
255
- { "expect": { "url": { "contains": "/customers" } } }
256
- ]
257
- }
258
- ```
259
-
260
- Every step is an object with exactly one key:
261
-
262
- | Step | What it does |
263
- | --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
264
- | `{ "click": target }` | Clicks the block — its interactive control (button, link, input, checkbox…) when it has one, otherwise the block itself — or the control a target object narrows to (below) |
265
- | `{ "fill": { ...target, value } }` | Fills the block's (or grid cell's) `input` or `textarea` with `value` (converted to a string) |
266
- | `{ "select": { ...target, value } }` | Opens the block's (or grid cell's) dropdown and clicks the option whose text is exactly `value`; a native `<select>` inside the block is set by label instead, and a radio, button or segmented selector's option is clicked by its label |
267
- | `{ "press": key }` | Presses a key or chord, e.g. `"Enter"`, `"Escape"`, `"Mod+k"` — `Mod` becomes `Meta` or `Control` for the platform the page reports, the same way the app's shortcuts resolve it |
268
- | `{ "back": true }` | Goes back one page, like the browser's Back button; fails when the journey has not navigated from an earlier page |
269
- | `{ "goto": pageId }` | Loads a page like a typed URL (`{ pageId, urlQuery }` adds a query); a protected page may redirect to sign-in, so assert where it landed |
270
- | `{ "email": { to, subject? } }` | Opens the newest email to `to` that arrived during the journey, waiting for it; `click` follows its links by text. Needs `LOWDEFY_DEV_SMTP_PORT` |
271
- | `{ "as": name }` | Switches to another person with their own browser and cookies; the journey starts as `"main"`, and a new name opens the journey's page |
272
- | `{ "wait": { "ms": n } }` | Sleeps `n` milliseconds |
273
- | `{ "wait": { "request": requestId } }` | Waits until the request has finished loading |
274
- | `{ "wait": { "state": path } }` | Waits until the state path is defined |
275
- | `{ "screenshot": name? }` | Captures a PNG of the viewport; returned with the result (`true`, `null` or a missing name defaults to `step-<index>`) |
276
- | `{ "expect": { "state": { path, equals } } }` | The state at `path` structurally equals `equals` (key order does not matter); a path that does not exist reads as `null`, so `equals: null` also passes for a misspelt path |
277
- | `{ "expect": { "visible": target } }` | The block, or the control a target narrows to, is visible |
278
- | `{ "expect": { "text": { ...target, contains } } }` | The block's text — or a grid row's or cell's — contains the string |
279
- | `{ "expect": { "url": { contains } } }` | The page url contains the string |
280
- | `{ "expect": { "title": { equals } } }` | The document title (the browser tab's text) is exactly the string; `{ contains }` checks part of it |
281
-
282
- A **target** is a `blockId` string, or an object that narrows the search to a control that is not itself a block — a grid row's cell buttons, a confirm dialog's OK, a dropdown menu's items:
283
-
284
- | Target key | Meaning |
285
- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------ |
286
- | `blockId` | The block to search inside |
287
- | `row` | A grid row, zero-based as displayed (`AgGrid*` blocks, `.ag-row[row-index]`); needs `blockId` |
288
- | `column` | A grid cell in that row, by the column's `field` or `colId` (`.ag-cell[col-id]`); needs `blockId` |
289
- | `text` | The visible interactive control whose text is exactly this — a button label, a tab, a menu item (`"Cat"` never matches `"Category"`) |
290
- | `nth` | When several controls match, the zero-based one to use |
291
-
292
- A radio, checkbox or segmented option is reached through its label: `{ "click": { "blockId": "period", "text": "Month" } }` clicks the Month option of a `SegmentedSelector`, whose own radio input has no size.
293
-
294
- `text` without a `blockId` searches the page, front-most layer first — an open dropdown menu (`[role="menu"]`), then an open dialog (`[role="dialog"]`), then the page — so `{ "click": { "text": "Delete" } }` with a confirm dialog open clicks the dialog's Delete and not the grid's, without counting buttons. `fill`, `select` and `expect.text` always need a `blockId`. Unknown target keys are rejected before a browser opens.
295
-
296
- ```json
297
- [
298
- { "click": { "blockId": "controls_grid", "row": 1, "text": "Delete" } },
299
- { "expect": { "visible": { "text": "Are you sure?" } } },
300
- { "click": { "text": "Delete" } },
301
- { "wait": { "request": "delete_control" } },
302
- {
303
- "expect": {
304
- "text": {
305
- "blockId": "controls_grid",
306
- "row": 1,
307
- "column": "title",
308
- "contains": "Key rotation"
309
- }
146
+ "mcpServers": {
147
+ "lowdefy-docs": {
148
+ "type": "http",
149
+ "url": "http://localhost:3000/lowdefy-docs/mcp"
310
150
  }
311
- },
312
- { "click": { "blockId": "controls_grid", "row": 0, "column": "more" } },
313
- { "click": { "text": "Archive" } }
314
- ]
315
- ```
316
-
317
- Each step gets 5 seconds (Playwright's own auto-waiting — a `click` waits for the block to be attached, visible and enabled; nothing is retried); pass `timeout` (milliseconds, up to 60000) to give every step of a journey longer on a slow machine. After an interaction the runner waits for the page to settle — the event the interaction fired, the requests it called — using the same readiness check the page open uses, so the next `expect` asserts against the outcome instead of racing it. The page opens with the same headless `user` and `urlQuery` handling as the other tools.
318
-
319
- A step that fails **stops the journey and comes back as data**, never as a tool error — a failed journey is the answer, not a fault:
320
-
321
- ```json
322
- {
323
- "pageId": "new_customer",
324
- "passed": false,
325
- "steps": [
326
- {
327
- "index": 0,
328
- "step": { "fill": { "blockId": "name", "value": "Ada Lovelace" } },
329
- "status": "ok",
330
- "durationMs": 18
331
- },
332
- { "index": 1, "step": { "click": "submit" }, "status": "ok", "durationMs": 71 },
333
- {
334
- "index": 2,
335
- "step": { "expect": { "state": { "path": "created", "equals": true } } },
336
- "status": "failed",
337
- "durationMs": 1
338
- },
339
- { "index": 3, "step": { "screenshot": "after-submit" }, "status": "skipped", "durationMs": 0 }
340
- ],
341
- "failure": {
342
- "index": 2,
343
- "step": { "expect": { "state": { "path": "created", "equals": true } } },
344
- "expected": true,
345
- "actual": null,
346
- "message": "Expected state \"created\" to equal true but found undefined."
347
- },
348
- "screenshots": [],
349
- "state": { "name": "Ada Lovelace", "country": null }
151
+ }
350
152
  }
351
153
  ```
352
154
 
353
- `expected` and `actual` hold the compared values for an `expect` step, and for an interaction step what the runner needed (`block "submit" to be actionable`, `option "Chile" in the dropdown of block "country"`) against Playwright's own message. The remaining steps are marked `skipped`. A passing journey returns `passed: true` and no `failure`. The final page `state` is included, pass or fail — it is what the agent needs to write the next assertion. A page holding large request results can have hundreds of KB of state, so when the state is over 10000 characters of JSON the result carries `stateOmitted` instead: its size, each top-level key with its size, and a note. The `state` option picks what comes back:
155
+ If your dev server runs on a different port, adjust the URL. The MCP server includes instructions that teach the agent the workflow (list types first, then fetch schemas and examples), so it works well without any extra prompting.
354
156
 
355
- | `state` | The result carries |
356
- | ----------------------- | --------------------------------------------------------------------- |
357
- | omitted | The whole state when it is small, otherwise `stateOmitted` |
358
- | `["form.name", "rows"]` | `state: { "form.name": …, "rows": … }`, `null` for a path not defined |
359
- | `true` | The whole state, however large |
360
- | `false` | No state |
157
+ ## Referencing it in a skill
361
158
 
362
- Screenshots taken before the failure are kept: over MCP they arrive as image content after the JSON (with the JSON listing only their names); over HTTP they are base64 in the JSON body.
159
+ For the best results, add a skill to your project that tells the agent to use the docs server whenever it writes Lowdefy config. Create `.claude/skills/lowdefy-config/SKILL.md`:
363
160
 
364
- Malformed steps are answered before a browser opens — an unknown key returns `Unknown journey step "hover". Steps are: click, fill, select, press, back, goto, email, as, wait, screenshot, expect.` (a `400` on the HTTP route), distinct from the `502` a render that could not run returns.
161
+ ```markdown
162
+ ---
163
+ name: lowdefy-config
164
+ description: Use when writing or editing Lowdefy YAML config — pages, blocks, operators, actions, connections, or requests. Looks up exact type names, schemas, and examples from the running dev server instead of guessing.
165
+ ---
365
166
 
366
- To test auth itself — sign-up, email verification, sign-in, invitations, organization switching — pass `"user": "none"`: no caller is injected, the journey starts signed out, and the app's own sessions carry through its steps. [Config Tests](/config-tests#testing-sign-up-and-sign-in) covers the mail sink and the database these journeys need.
167
+ # Writing Lowdefy config
367
168
 
368
- Journeys are also the file format of `tests/journeys/*.yaml`, which `lowdefy test` runs through this same route — write the journey the agent used to verify a change, and it becomes the regression test for it.
169
+ The dev server serves docs for everything installed in this project at
170
+ `http://localhost:3000/lowdefy-docs` (also as MCP tools via the `lowdefy-docs` server).
369
171
 
370
- ## Setting up a project — one command
172
+ Never guess type names or properties. Before writing config:
371
173
 
372
- ```bash
373
- npx lowdefy agent-setup
374
- ```
375
-
376
- This writes three files into your project (merging safely if they exist): `.mcp.json` registering the `lowdefy-docs` MCP server (`lowdefy mcp`, see [Connect your agent](#connect-your-agent-lowdefy-mcp)), `.claude/skills/lowdefy-config/SKILL.md` teaching Claude Code the workflow, and an `AGENTS.md` section for other coding agents. Add `lowdefy` to your app's `devDependencies` first, so `.mcp.json` runs your app's own version of the CLI; each git worktree then needs its own `pnpm install` (or equivalent) before an agent session starts there.
377
-
378
- Projects set up by an earlier version, with an `http://localhost:<port>/lowdefy-docs/mcp` entry, are migrated when you rerun the command: the entry is replaced, per-port entries such as `lowdefy-3010` are removed, and the skill and `AGENTS.md` section it wrote are updated. The server keeps its `lowdefy-docs` name, so tool names and approvals carry over.
379
-
380
- ## Using it with Claude Code manually
381
-
382
- Commit a `.mcp.json` file at your project root, pointing at your app's installed CLI:
174
+ 1. Call `lowdefy_list_types` (or `GET /lowdefy-docs/blocks`, `/lowdefy-docs/operators`,
175
+ `/lowdefy-docs/actions`, `/lowdefy-docs/connections`, `/lowdefy-docs/requests`) to find the exact
176
+ type name — this includes this project's local plugins.
177
+ 2. Call `lowdefy_get_schema` (or `GET /lowdefy-docs/schema/{kind}/{type}`) for the
178
+ exact properties and events of that type.
179
+ 3. Call `lowdefy_get_examples` (or `GET /lowdefy-docs/examples/{type}`) to see real
180
+ usage YAML for blocks.
181
+ 4. For concepts (state, operators, events, requests), call `lowdefy_get_doc`
182
+ or `lowdefy_search_docs`.
383
183
 
384
- ```json
385
- {
386
- "mcpServers": {
387
- "lowdefy-docs": {
388
- "type": "stdio",
389
- "command": "node",
390
- "args": ["node_modules/lowdefy/dist/index.js", "mcp"]
391
- }
392
- }
393
- }
184
+ After every edit, call `lowdefy_build_status` and fix what it reports. Use
185
+ `lowdefy_inspect_state` to read the live state of the page (ask the developer
186
+ to interact with it first when debugging), `lowdefy_eval_operator` to test
187
+ operator expressions against real state, and `lowdefy_snapshot_state` /
188
+ `lowdefy_load_state` to capture and restore app states for testing.
394
189
  ```
395
190
 
396
- In a monorepo, use the path to the app's `node_modules`, for example `apps/main/node_modules/lowdefy/dist/index.js`. The MCP server includes instructions that teach the agent the workflow (list types first, then fetch schemas and examples; let `lowdefy mcp` run the dev server), so it works well without any extra prompting.
397
-
398
191
  ## Plain HTTP routes
399
192
 
400
193
  Everything the MCP tools serve is also available as plain GET routes — useful for `curl`, scripts, or agents without MCP support:
401
194
 
402
- | Route | Purpose |
403
- | ----------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
404
- | `GET /lowdefy-docs` | Overview and route index |
405
- | `GET /lowdefy-docs/{kind}` | List all available types of a kind, e.g. `/lowdefy-docs/blocks` |
406
- | `GET /lowdefy-docs/plugins` | Installed plugin packages and the types each provides |
407
- | `GET /lowdefy-docs/schema/{kind}/{type}` | JSON schema for a type, e.g. `/lowdefy-docs/schema/blocks/Button` |
408
- | `GET /lowdefy-docs/examples/{type}` | Example YAML for a block type |
409
- | `GET /lowdefy-docs/content/{slug}` | A docs page as markdown, e.g. `/lowdefy-docs/content/operators/_get` |
410
- | `GET /lowdefy-docs/search?q={query}` | Search the docs |
411
- | `GET /lowdefy-docs/plugin-doc/{package}` | Markdown shipped inside a plugin package |
412
- | `GET /lowdefy-docs/build-status` | Current build errors/warnings + recent browser runtime errors |
413
- | `GET /lowdefy-docs/events` | SSE stream of `restart`, `build`, `client_error` and `server_error` events |
414
- | `GET /lowdefy-docs/page-config/{pageId}` | Fully built page config, or its build errors |
415
- | `GET /lowdefy-docs/screenshot/{pageId}` | PNG screenshot of the rendered page; `?viewportWidth=390&viewportHeight=844&colorScheme=dark` sets the viewport and colour scheme |
416
- | `POST /lowdefy-docs/journey` | Drive a page headless through declarative steps and assert what happens; screenshots returned as base64 |
417
- | `GET /lowdefy-docs/find/{id}?pageId=` | Locate where a page/block/request id is defined |
418
- | `GET /lowdefy-docs/app-map` | Whole-app graph: pages, menus, connections, endpoints, agents |
419
- | `GET /lowdefy-docs/inspect-state/{pageId}` | Live state/requests/eventLog of a running page (tab or headless) |
420
- | `POST /lowdefy-docs/eval-operator` | Evaluate an operator expression against live page state |
421
- | `POST /lowdefy-docs/run-request` | Execute a request with a test payload (read-only unless opted in) |
422
- | `POST /lowdefy-docs/run-endpoint` | Execute an Api endpoint routine with a test payload and caller (needs `allowWriteRequests`; rejects return as data) |
423
- | `GET/POST /lowdefy-docs/checkpoints` + `/revert` | Config-file checkpoints |
424
- | `GET/POST /lowdefy-docs/state-checkpoints` + `/snapshot`, `/load` | State & data checkpoints |
425
- | `POST /lowdefy-docs/restart` | Restart the dev server process (`{reason}` optional; then `build-status?wait=true` answers once it is back) |
195
+ | Route | Purpose |
196
+ | --------------------------- | -------------------------------------------------------------- |
197
+ | `GET /lowdefy-docs` | Overview and route index |
198
+ | `GET /lowdefy-docs/{kind}` | List all available types of a kind, e.g. `/lowdefy-docs/blocks` |
199
+ | `GET /lowdefy-docs/plugins` | Installed plugin packages and the types each provides |
200
+ | `GET /lowdefy-docs/schema/{kind}/{type}` | JSON schema for a type, e.g. `/lowdefy-docs/schema/blocks/Button` |
201
+ | `GET /lowdefy-docs/examples/{type}` | Example YAML for a block type |
202
+ | `GET /lowdefy-docs/content/{slug}` | A docs page as markdown, e.g. `/lowdefy-docs/content/operators/_get` |
203
+ | `GET /lowdefy-docs/search?q={query}`| Search the docs |
204
+ | `GET /lowdefy-docs/plugin-doc/{package}` | Markdown shipped inside a plugin package |
205
+ | `GET /lowdefy-docs/build-status` | Current build errors/warnings + recent browser runtime errors |
206
+ | `GET /lowdefy-docs/page-config/{pageId}` | Fully built page config, or its build errors |
207
+ | `GET /lowdefy-docs/screenshot/{pageId}` | PNG screenshot of the rendered page |
208
+ | `GET /lowdefy-docs/find/{id}?pageId=` | Locate where a page/block/request id is defined |
209
+ | `GET /lowdefy-docs/app-map` | Whole-app graph: pages, menus, connections, endpoints, agents |
210
+ | `GET /lowdefy-docs/inspect-state/{pageId}` | Live state/requests/eventLog of a running page (tab or headless) |
211
+ | `POST /lowdefy-docs/eval-operator` | Evaluate an operator expression against live page state |
212
+ | `POST /lowdefy-docs/run-request` | Execute a request with a test payload (read-only unless opted in) |
213
+ | `GET/POST /lowdefy-docs/checkpoints` + `/revert` | Config-file checkpoints |
214
+ | `GET/POST /lowdefy-docs/state-checkpoints` + `/snapshot`, `/load` | State & data checkpoints |
426
215
 
427
216
  ## Local plugins
428
217
 
@@ -432,7 +221,6 @@ Your project's own plugins (declared under `plugins:` in `lowdefy.yaml`) are inc
432
221
  - Block schemas are derived from each block's `meta`, and connection/request schemas from the `schema` property on your connection and request definitions.
433
222
  - Ship a `gallery.yaml` or `examples.yaml` next to a block in your plugin's `dist/blocks/{BlockName}/` and it is served by `lowdefy_get_examples`.
434
223
  - A `README.md` or `docs/*.md` files in your plugin package are served by `lowdefy_get_plugin_doc`.
435
- - Editing a local plugin (a package linked from your workspace) rebuilds the config, so a new or renamed type is defined and a build the plugin broke clears once it is fixed. When the plugin has server-side types (connections, requests, server operators, agents, websockets, notifications, auth) the dev server also restarts, so the new implementation is what runs; block, action and client-operator edits hot-reload through Vite without a restart. If the server still looks stale, call `lowdefy_restart` or `POST /lowdefy-docs/restart`.
436
224
 
437
225
  ## Docs for crawling agents
438
226