@terreno/mcp 57.19.2 → 57.20.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 (197) hide show
  1. package/CHANGELOG.md +393 -0
  2. package/README.md +32 -269
  3. package/dist/bootstrap.d.ts +12 -0
  4. package/dist/bootstrap.d.ts.map +1 -1
  5. package/dist/bootstrap.js +77 -122
  6. package/dist/bootstrap.js.map +1 -1
  7. package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
  8. package/dist/docs/resources/overview.md +4 -2
  9. package/dist/docs/resources/patterns.md +237 -176
  10. package/dist/docs/resources/ui.md +48 -17
  11. package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
  12. package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
  13. package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
  14. package/dist/docs/upgrades/57.20.0.md +238 -0
  15. package/dist/docs/versioned/next/approval-rubric.md +30 -12
  16. package/dist/docs/versioned/next/explanation/README.md +5 -0
  17. package/dist/docs/versioned/next/explanation/account-linking.md +169 -0
  18. package/dist/docs/versioned/next/explanation/admin-interface.md +1 -1
  19. package/dist/docs/versioned/next/explanation/agent-guidelines.md +173 -0
  20. package/dist/docs/versioned/next/explanation/ai-development-loop.md +196 -0
  21. package/dist/docs/versioned/next/explanation/authentication.md +45 -1
  22. package/dist/docs/versioned/next/explanation/consent-forms.md +105 -0
  23. package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
  24. package/dist/docs/versioned/next/explanation/local-first-data.md +1 -1
  25. package/dist/docs/versioned/next/explanation/organizations.md +37 -0
  26. package/dist/docs/versioned/next/explanation/planning-plugins.md +4 -0
  27. package/dist/docs/versioned/next/explanation/responsive-design.md +143 -0
  28. package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
  29. package/dist/docs/versioned/next/explanation/schema-metadata.md +5 -0
  30. package/dist/docs/versioned/next/how-to/README.md +7 -2
  31. package/dist/docs/versioned/next/how-to/audit-log.md +22 -3
  32. package/dist/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
  33. package/dist/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
  34. package/dist/docs/versioned/next/how-to/circleci.md +15 -5
  35. package/dist/docs/versioned/next/how-to/configure-better-auth.md +673 -53
  36. package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
  37. package/dist/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
  38. package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
  39. package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
  40. package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
  41. package/dist/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
  42. package/dist/docs/versioned/next/how-to/run-tests-locally.md +37 -8
  43. package/dist/docs/versioned/next/how-to/set-up-social-login.md +424 -0
  44. package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
  45. package/dist/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
  46. package/dist/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
  47. package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
  48. package/dist/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
  49. package/dist/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
  50. package/dist/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
  51. package/dist/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
  52. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
  53. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
  54. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
  55. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
  56. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
  57. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
  58. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
  59. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
  60. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
  61. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
  62. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
  63. package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
  64. package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
  65. package/dist/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
  66. package/dist/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
  67. package/dist/docs/versioned/next/reference/README.md +1 -0
  68. package/dist/docs/versioned/next/reference/account-security.md +700 -0
  69. package/dist/docs/versioned/next/reference/admin-backend.md +3 -1
  70. package/dist/docs/versioned/next/reference/admin-frontend.md +225 -1
  71. package/dist/docs/versioned/next/reference/api.md +461 -35
  72. package/dist/docs/versioned/next/reference/cli.md +1 -1
  73. package/dist/docs/versioned/next/reference/comms.md +8 -0
  74. package/dist/docs/versioned/next/reference/create-terreno-app.md +3 -1
  75. package/dist/docs/versioned/next/reference/environment-variables.md +9 -3
  76. package/dist/docs/versioned/next/reference/legacy/rtk.md +19 -3
  77. package/dist/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
  78. package/dist/docs/versioned/next/reference/mcp-server.md +1158 -441
  79. package/dist/docs/versioned/next/reference/syncdb.md +8 -1
  80. package/dist/docs/versioned/next/reference/ui.md +1271 -27
  81. package/dist/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
  82. package/dist/docs/versioned/next/tutorials/README.md +4 -0
  83. package/dist/httpApp.d.ts +14 -0
  84. package/dist/httpApp.d.ts.map +1 -0
  85. package/dist/httpApp.js +99 -0
  86. package/dist/httpApp.js.map +1 -0
  87. package/dist/index.js +8 -90
  88. package/dist/index.js.map +1 -1
  89. package/dist/local/localTools.d.ts.map +1 -1
  90. package/dist/local/localTools.js +44 -14
  91. package/dist/local/localTools.js.map +1 -1
  92. package/dist/local/metro/metroDevSession.d.ts +18 -0
  93. package/dist/local/metro/metroDevSession.d.ts.map +1 -1
  94. package/dist/local/metro/metroDevSession.js +42 -3
  95. package/dist/local/metro/metroDevSession.js.map +1 -1
  96. package/dist/local/tools/databaseQuery.d.ts.map +1 -1
  97. package/dist/local/tools/databaseQuery.js +3 -13
  98. package/dist/local/tools/databaseQuery.js.map +1 -1
  99. package/dist/local/tools/databaseSchema.d.ts.map +1 -1
  100. package/dist/local/tools/databaseSchema.js +3 -13
  101. package/dist/local/tools/databaseSchema.js.map +1 -1
  102. package/dist/local/tools/localMongo.d.ts +10 -0
  103. package/dist/local/tools/localMongo.d.ts.map +1 -0
  104. package/dist/local/tools/localMongo.js +30 -0
  105. package/dist/local/tools/localMongo.js.map +1 -0
  106. package/dist/local/tools/runtime.d.ts +18 -2
  107. package/dist/local/tools/runtime.d.ts.map +1 -1
  108. package/dist/local/tools/runtime.js +75 -20
  109. package/dist/local/tools/runtime.js.map +1 -1
  110. package/dist/local/tools/syncDb.d.ts +11 -0
  111. package/dist/local/tools/syncDb.d.ts.map +1 -1
  112. package/dist/local/tools/syncDb.js +15 -3
  113. package/dist/local/tools/syncDb.js.map +1 -1
  114. package/dist/rateLimit.d.ts +35 -0
  115. package/dist/rateLimit.d.ts.map +1 -0
  116. package/dist/rateLimit.js +112 -0
  117. package/dist/rateLimit.js.map +1 -0
  118. package/dist/tools.d.ts.map +1 -1
  119. package/dist/tools.js +42 -60
  120. package/dist/tools.js.map +1 -1
  121. package/package.json +4 -3
  122. package/src/docs/guidelines/ui/guidelines/core.md +1 -1
  123. package/src/docs/resources/overview.md +4 -2
  124. package/src/docs/resources/patterns.md +237 -176
  125. package/src/docs/resources/ui.md +48 -17
  126. package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
  127. package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
  128. package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
  129. package/src/docs/upgrades/57.20.0.md +238 -0
  130. package/src/docs/versioned/next/approval-rubric.md +30 -12
  131. package/src/docs/versioned/next/explanation/README.md +5 -0
  132. package/src/docs/versioned/next/explanation/account-linking.md +169 -0
  133. package/src/docs/versioned/next/explanation/admin-interface.md +1 -1
  134. package/src/docs/versioned/next/explanation/agent-guidelines.md +173 -0
  135. package/src/docs/versioned/next/explanation/ai-development-loop.md +196 -0
  136. package/src/docs/versioned/next/explanation/authentication.md +45 -1
  137. package/src/docs/versioned/next/explanation/consent-forms.md +105 -0
  138. package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
  139. package/src/docs/versioned/next/explanation/local-first-data.md +1 -1
  140. package/src/docs/versioned/next/explanation/organizations.md +37 -0
  141. package/src/docs/versioned/next/explanation/planning-plugins.md +4 -0
  142. package/src/docs/versioned/next/explanation/responsive-design.md +143 -0
  143. package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
  144. package/src/docs/versioned/next/explanation/schema-metadata.md +5 -0
  145. package/src/docs/versioned/next/how-to/README.md +7 -2
  146. package/src/docs/versioned/next/how-to/audit-log.md +22 -3
  147. package/src/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
  148. package/src/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
  149. package/src/docs/versioned/next/how-to/circleci.md +15 -5
  150. package/src/docs/versioned/next/how-to/configure-better-auth.md +673 -53
  151. package/src/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
  152. package/src/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
  153. package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
  154. package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
  155. package/src/docs/versioned/next/how-to/password-reset.md +1 -1
  156. package/src/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
  157. package/src/docs/versioned/next/how-to/run-tests-locally.md +37 -8
  158. package/src/docs/versioned/next/how-to/set-up-social-login.md +424 -0
  159. package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
  160. package/src/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
  161. package/src/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
  162. package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
  163. package/src/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
  164. package/src/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
  165. package/src/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
  166. package/src/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
  167. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
  168. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
  169. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
  170. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
  171. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
  172. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
  173. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
  174. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
  175. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
  176. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
  177. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
  178. package/src/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
  179. package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
  180. package/src/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
  181. package/src/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
  182. package/src/docs/versioned/next/reference/README.md +1 -0
  183. package/src/docs/versioned/next/reference/account-security.md +700 -0
  184. package/src/docs/versioned/next/reference/admin-backend.md +3 -1
  185. package/src/docs/versioned/next/reference/admin-frontend.md +225 -1
  186. package/src/docs/versioned/next/reference/api.md +461 -35
  187. package/src/docs/versioned/next/reference/cli.md +1 -1
  188. package/src/docs/versioned/next/reference/comms.md +8 -0
  189. package/src/docs/versioned/next/reference/create-terreno-app.md +3 -1
  190. package/src/docs/versioned/next/reference/environment-variables.md +9 -3
  191. package/src/docs/versioned/next/reference/legacy/rtk.md +19 -3
  192. package/src/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
  193. package/src/docs/versioned/next/reference/mcp-server.md +1158 -441
  194. package/src/docs/versioned/next/reference/syncdb.md +8 -1
  195. package/src/docs/versioned/next/reference/ui.md +1271 -27
  196. package/src/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
  197. package/src/docs/versioned/next/tutorials/README.md +4 -0
@@ -1,642 +1,1354 @@
1
1
  # @terreno/mcp
2
2
 
3
- Published npm package for the Terreno Model Context Protocol (MCP) server. The monorepo directory is still `mcp-server/`. The package exposes:
3
+ Published npm package for the Terreno Model Context Protocol (MCP) servers. The monorepo directory
4
+ is `mcp-server/`. The package ships two servers with separate tool sets:
4
5
 
5
- - **`terreno-mcp`** — HTTP server used in Cloud Run and local debugging (`src/index.ts`)
6
- - **`terreno-mcp-local`** — stdio server for project runtime tools (`src/local/index.ts`): app/database/log/browser diagnostics, Redux/RTK inspection, and SyncDB state/action/snapshot tools. `evaluate`, `navigate`, and SyncDB state changes are gated by `TERRENO_MCP_EVAL`. Metro URL defaults from `frontend/package.json` `--port` or `TERRENO_METRO_URL`. `application_info` reads bootstrap `backend/` + `frontend/`, or this monorepo's `example-backend/` + `example-frontend/` (bootstrap names win when both exist).
6
+ | Server | npm bin | Entry | Transport | Registers |
7
+ |--------|---------|-------|-----------|-----------|
8
+ | Hosted `terreno-mcp` | `terreno-mcp` → `dist/index.js` | `src/index.ts`, `src/httpApp.ts` | Streamable HTTP, stateless | 14 tools, 9 prompts, documentation resources |
9
+ | Local `terreno-mcp-local` | `terreno-mcp-local` → `dist/local/index.js` | `src/local/index.ts`, `src/local/localServer.ts` | stdio | 13 tools (no prompts, no resources) |
7
10
 
8
- It provides AI coding assistants with documentation access, code generation tools, and workflow prompts.
9
- For editor configuration and runtime prerequisites, see
10
- [Set up Terreno MCP](../how-to/set-up-terreno-mcp.md). For the end-to-end diagnostic workflow, see
11
- [Debug a Terreno app with MCP](../how-to/debug-with-mcp.md).
11
+ The hosted server gives an agent documentation search, upgrade notes, and code generators. It never
12
+ sees your project. The local server runs inside your project and reads its packages, database, logs,
13
+ Metro, and running app.
12
14
 
13
- Both HTTP MCP surfaces (`@terreno/mcp` and the `modelRouter` endpoint in
14
- `@terreno/api`) use the MCP TypeScript SDK v2 and speak the stateless
15
- `2026-07-28` protocol revision. The HTTP handlers retain the SDK's stateless
16
- legacy fallback for 2025-era clients. `terreno-mcp-local` uses v2 `serveStdio`,
17
- which negotiates the connection era and pins one server instance for that
15
+ For editor configuration, see [Set up Terreno MCP](../how-to/set-up-terreno-mcp.md). For the
16
+ end-to-end diagnostic workflow, see [Debug a Terreno app with MCP](../how-to/debug-with-mcp.md).
17
+
18
+ Both HTTP MCP surfaces (`@terreno/mcp` and the `modelRouter` endpoint in `@terreno/api`) use the MCP
19
+ TypeScript SDK v2 and speak the stateless `2026-07-28` protocol revision. The HTTP handlers retain
20
+ the SDK's stateless legacy fallback for 2025-era clients. `terreno-mcp-local` uses v2 `serveStdio`
21
+ with `legacy: "serve"`, which negotiates the connection era and pins one server instance for that
18
22
  connection.
19
23
 
20
24
  ## Table of Contents
21
25
 
22
- - [Overview](#overview)
23
- - [Installation](#installation)
24
- - [Resources](#resources)
25
- - [Tools](#tools)
26
- - [Prompts](#prompts)
27
- - [Environment Variables](#environment-variables)
26
+ - [Hosted server (`terreno-mcp`)](#hosted-server-terreno-mcp)
27
+ - [Endpoint](#endpoint)
28
+ - [Hosted tools](#hosted-tools)
29
+ - [Prompts](#prompts)
30
+ - [Resources](#resources)
31
+ - [Rate limiting](#rate-limiting)
32
+ - [Local server (`terreno-mcp-local`)](#local-server-terreno-mcp-local)
33
+ - [Running the local server](#running-the-local-server)
34
+ - [Local tools](#local-tools)
35
+ - [`TERRENO_MCP_EVAL` gate](#terreno_mcp_eval-gate)
36
+ - [Metro and CDP connection](#metro-and-cdp-connection)
37
+ - [React Native DevTools and the `app` source](#react-native-devtools-and-the-app-source)
38
+ - [What the local server can and cannot see](#what-the-local-server-can-and-cannot-see)
39
+ - [Environment variables](#environment-variables)
28
40
  - [Development](#development)
41
+ - [Architecture](#architecture)
42
+ - [Deployment](#deployment)
29
43
 
30
- ## Overview
31
-
32
- The MCP server exposes Terreno's documentation and code generation capabilities through the Model Context Protocol, enabling AI assistants (like Claude in Cursor or Claude Desktop) to:
33
-
34
- - Access up-to-date documentation for all Terreno packages
35
- - Generate boilerplate code following Terreno conventions
36
- - Provide multi-step workflows for common development tasks
37
-
38
- **Key concepts:**
44
+ ## Hosted server (`terreno-mcp`)
39
45
 
40
- - **Resources**: Read-only documentation from `docs/` directory
41
- - **Tools**: Code generators that return text (AI writes files), documentation search (`terreno_search_docs`, `terreno_get_component_docs`), upgrade notes (`terreno_get_upgrade_guide`), plus local-only tools when using `terreno-mcp-local`
42
- - **Prompts**: Pre-built multi-step instructions for complex workflows
46
+ ### Endpoint
43
47
 
44
- ## Installation
48
+ | Item | Value |
49
+ |------|-------|
50
+ | URL | `https://mcp.terreno.app/mcp` |
51
+ | JSON-RPC routes | `ALL /mcp` and `POST /` (same handler, same rate-limit bucket) |
52
+ | Health check | `GET /` → `{"mcpEndpoint":"/mcp","service":"terreno-mcp","status":"ok"}` (never rate limited) |
53
+ | Sessions | None. Each request is independent; no `initialize` round trip is required. |
54
+ | Request headers | `Accept: application/json, text/event-stream` is required; without both types the server returns JSON-RPC error `-32000` `Not Acceptable: Client must accept both application/json and text/event-stream` |
55
+ | Response | An SSE stream of `event: message` frames (`responseMode: "auto"`) |
56
+ | Logging | Every `tools/call` and `prompts/get` is logged with its name and arguments (`logger.info`), and the server is wrapped with `Sentry.wrapMcpServerWithSentry`. Do not put secrets or PHI in tool arguments. |
45
57
 
46
- ### With Claude Desktop
58
+ `mcp.terreno.flourish.health` is the legacy hostname for the same service. Use
59
+ `https://mcp.terreno.flourish.health/mcp` while `mcp.terreno.app` does not resolve
60
+ (`getaddrinfo ENOTFOUND mcp.terreno.app`).
47
61
 
48
- Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
62
+ Editor entry (the Terreno servers `create-terreno-app` writes to `.cursor/mcp.json` and
63
+ `.mcp.json`; the full files are in [Set up Terreno MCP](../how-to/set-up-terreno-mcp.md)):
49
64
 
50
65
  ``````json
51
66
  {
52
67
  "mcpServers": {
53
- "terreno": {
54
- "command": "bun",
55
- "args": ["/absolute/path/to/terreno/mcp-server/dist/index.js"]
56
- }
68
+ "terreno": {"type": "http", "url": "https://mcp.terreno.app/mcp"},
69
+ "terreno-local": {"command": "bunx", "args": ["-p", "@terreno/mcp", "terreno-mcp-local"]}
57
70
  }
58
71
  }
59
72
  ``````
60
73
 
61
- ### With Claude Code CLI
74
+ Raw HTTP call against a local build (`PORT=8799 bun dist/index.js`):
62
75
 
63
- Add to your project's `.claude/settings.json`:
76
+ ``````bash
77
+ curl -s -X POST http://127.0.0.1:8799/mcp \
78
+ -H 'Content-Type: application/json' -H 'Accept: application/json, text/event-stream' \
79
+ -d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"terreno_get_upgrade_guide","arguments":{"fromVersion":"57.14.0","toVersion":"57.14.0"}}}'
80
+ ``````
64
81
 
65
- ``````json
66
- {
67
- "mcpServers": {
68
- "terreno": {
69
- "command": "bun",
70
- "args": ["./mcp-server/dist/index.js"]
71
- }
72
- }
73
- }
82
+ ``````text
83
+ event: message
84
+ data: {"result":{"content":[{"type":"text","text":"Recorded notes in 57.14.0 → 57.14.0: 57.14.0\n\n---\n\n# Upgrade to 57.14.0…"}]},"jsonrpc":"2.0","id":1}
74
85
  ``````
75
86
 
76
- ### Building from Source
87
+ JSON-RPC methods: `tools/list`, `tools/call`, `prompts/list`, `prompts/get`, `resources/list`,
88
+ `resources/read`.
77
89
 
78
- ``````bash
79
- # From monorepo root
80
- bun install
81
- bun run mcp:build
90
+ ### Hosted tools
91
+
92
+ Code generators return text. They do not write files: the agent receives the code and a target path
93
+ and writes it. Scaffold a new app with [`create-terreno-app`](create-terreno-app.md)
94
+ (`bunx create-terreno-app`), not an MCP tool. Every tool returns one text content block.
95
+
96
+ | Tool | Purpose |
97
+ |------|---------|
98
+ | [`terreno_bootstrap_ai_rules`](#terreno_bootstrap_ai_rules) | Generate AI assistant rule files for an app |
99
+ | [`terreno_search_docs`](#terreno_search_docs) | Keyword search over bundled Terreno docs |
100
+ | [`terreno_get_component_docs`](#terreno_get_component_docs) | Props table for one `@terreno/ui` component |
101
+ | [`terreno_get_upgrade_guide`](#terreno_get_upgrade_guide) | Bundled upgrade notes between two versions |
102
+ | [`terreno_search_update_notes`](#terreno_search_update_notes) | Search upgrade notes and help-API announcements |
103
+ | [`terreno_get_update_note`](#terreno_get_update_note) | Full text of one update note |
104
+ | [`terreno_ask_update_help`](#terreno_ask_update_help) | Ranked update-note excerpts for a question |
105
+ | [`terreno_generate_model`](#terreno_generate_model) | Mongoose model source |
106
+ | [`terreno_generate_route`](#terreno_generate_route) | `modelRouter` route source |
107
+ | [`terreno_generate_screen`](#terreno_generate_screen) | React Native screen source |
108
+ | [`terreno_generate_form_fields`](#terreno_generate_form_fields) | `@terreno/ui` form field source |
109
+ | [`terreno_validate_model_schema`](#terreno_validate_model_schema) | Check schema source against Terreno conventions |
110
+ | [`terreno_validate_ui_blocks`](#terreno_validate_ui_blocks) | Validate a whole-reply UI block document |
111
+ | [`terreno_install_admin`](#terreno_install_admin) | Admin panel files and setup steps |
112
+
113
+ Example outputs below are real tool output, shortened with `…`.
114
+
115
+ #### terreno_bootstrap_ai_rules
116
+
117
+ Generate AI coding assistant rules (`AGENTS.md`, `CLAUDE.md`, `.rulesync/` sources, and
118
+ `rulesync.jsonc`). Guideline bodies come from each package's `.ai/guidelines/core.md`, copied into
119
+ this package at build time (`bun run sync-package-guidelines`). Defined in `src/bootstrap.ts`.
120
+
121
+ | Parameter | Type | Required | Description |
122
+ |-----------|------|----------|-------------|
123
+ | `appName` | string | yes | App name in kebab-case |
124
+ | `appDisplayName` | string | yes | Human-readable name |
125
+ | `description` | string | no | One-line app description |
126
+ | `packages` | string[] | no | Guideline packages to merge: `api`, `ui`, `rtk`, `syncdb`, `admin-backend`, `admin-frontend`, or the `@terreno/*` name. Omitted or no valid id means all six. |
82
127
 
83
- # Or from mcp-server directory
84
- cd mcp-server
85
- bun run build
128
+ ``````json
129
+ {"name":"terreno_bootstrap_ai_rules","arguments":{"appName":"my-app","appDisplayName":"My App","packages":["api"]}}
86
130
  ``````
87
131
 
88
- ## Resources
132
+ ``````markdown
133
+ # Bootstrap AI Rules for My App
134
+
135
+ ## Files to Create
89
136
 
90
- Documentation resources accessible via `terreno://` protocol:
137
+ The following AI coding assistant configuration files need to be created:
91
138
 
92
- | URI | Description |
93
- |-----|-------------|
94
- | `terreno://docs/overview` | Monorepo overview and architecture |
95
- | `terreno://docs/api` | @terreno/api reference documentation |
96
- | `terreno://docs/ui` | @terreno/ui reference documentation |
97
- | `terreno://docs/rtk` | @terreno/rtk reference documentation |
98
- | `terreno://docs/patterns` | Common patterns and best practices |
139
+ - `.rulesync/rules/00-root.md`
140
+ - `backend/AGENTS.md`
141
+ - `backend/CLAUDE.md`
142
+ - `frontend/AGENTS.md`
143
+ - `frontend/CLAUDE.md`
144
+ - `rulesync.jsonc`
99
145
 
100
- **How it works:**
146
+ ## Instructions
101
147
 
102
- - Resources are loaded from markdown files in `docs/` directory
103
- - Content is transformed and served via MCP protocol
104
- - AI assistants cache resources for fast lookup
105
- - Updates require MCP server restart
148
+ 1. **Create all the files listed above.** Each file's content is provided below.
149
+ …
150
+ 3. **Before generating, move hand-written rules into `.rulesync/rules/`.** `rulesync generate` overwrites the root `CLAUDE.md`, `AGENTS.md`, and `.github/copilot-instructions.md` and deletes every file under `.claude/rules/`, `.cursor/rules/`, `.github/instructions/`, and `.agents/memories/` that it did not generate. …
106
151
 
107
- **Custom docs directory:**
152
+ 4. **Delete the stale `.cursorrules`** that `create-terreno-app` wrote. …
108
153
 
109
- Set `TERRENO_MCP_DOCS_DIR` environment variable to override default path.
154
+ 5. **Generate the editor files with rulesync:**
155
+ ```bash
156
+ bunx rulesync@9 generate
157
+ ```
158
+ …
159
+ ``````
110
160
 
111
- ## Tools
161
+ `rulesync.jsonc` targets `cursor`, `claudecode`, `copilot`, and `agentsmd`. The one
162
+ `root: true` rule in `00-root.md` covers all four. See
163
+ [Agent guidelines](../explanation/agent-guidelines.md#terreno_bootstrap_ai_rules) for the
164
+ generated editor files and how to add project rules.
112
165
 
113
- Code generation tools return TypeScript/JavaScript code as text. **Code generation tools do not
114
- write files** — the AI assistant receives the code and writes it to appropriate locations. Scaffold
115
- a new app with [`create-terreno-app`](create-terreno-app.md) (`bunx create-terreno-app`), not an MCP
116
- tool.
166
+ #### terreno_search_docs
117
167
 
118
- The local `browser` tool is different: its `screenshot` action writes the requested image path so an
119
- agent can attach the rendered result as verification evidence. It keeps one headless session alive
120
- for the MCP connection. Call `open` first, perform interactions, call `snapshot` and `screenshot` to
121
- prove the final state, then call `close`.
168
+ BM25-style keyword search over markdown bundled with the server: `docs/resources/*.md`, the Diátaxis
169
+ docs synced into `docs/versioned/`, and per-component excerpts from `ui-types-documentation.json`.
170
+ Call it before guessing a Terreno API.
122
171
 
123
- `open` accepts only `http:` and `https:` URLs. Snapshots redact password input values; use them for
124
- page structure and visible state, not credential inspection.
172
+ | Parameter | Type | Required | Description |
173
+ |-----------|------|----------|-------------|
174
+ | `queries` | string[] | yes | One or more search phrases |
175
+ | `packages` | string[] | no | Filter by package id or scope, e.g. `["api", "@terreno/ui"]` |
176
+ | `tokenLimit` | number | no | Approximate markdown budget. Default 3000, clamped to 8000. |
177
+ | `version` | string | no | `@terreno/*` lockstep version (e.g. `57.2.0`). Omit for current `next` docs. |
178
+
179
+ An unmatched `version` falls back to the nearest retained snapshot (`website/versioned_docs/`,
180
+ copied into `docs/versioned/` at build), and the response says so. Range prefixes such as
181
+ `^57.2.0` are stripped. A non-array `queries` returns a usage message instead of results.
125
182
 
126
183
  ``````json
127
- {"name":"browser","arguments":{"action":"open","url":"http://localhost:8082","width":1280,"height":720}}
128
- {"name":"browser","arguments":{"action":"wait","timeout":1000}}
129
- {"name":"browser","arguments":{"action":"click","selector":"button[type=submit]"}}
130
- {"name":"browser","arguments":{"action":"snapshot"}}
131
- {"name":"browser","arguments":{"action":"screenshot","output":"/opt/cursor/artifacts/app.png"}}
132
- {"name":"browser","arguments":{"action":"close"}}
184
+ {"name":"terreno_search_docs","arguments":{"queries":["instanceActions"],"packages":["api"],"tokenLimit":300}}
133
185
  ``````
134
186
 
135
- `browser` requires Bun 1.4 or newer. macOS uses system WebKit; Linux and Windows use an installed
136
- Chrome, Chromium, or Edge through the Chrome DevTools Protocol. Browser storage is ephemeral unless
137
- `open` receives `dataDir`. Set `TERRENO_MCP_EVAL=1` to enable its arbitrary-JavaScript `evaluate`
138
- action. Screenshot paths must stay under the project root or `/opt/cursor/artifacts`; persistent
139
- browser data must stay under the project root. Calls are serialized around the one session owned by
140
- the local MCP process.
187
+ ``````markdown
188
+ # Terreno documentation search results
141
189
 
142
- ### SyncDB runtime tools
190
+ Queries: "instanceActions"
191
+ Docs version: next
192
+ Package filter: api
143
193
 
144
- Create the app client with `debug: true` so it registers by its configured
145
- `name`. The local MCP uses the in-process registry when available and Hermes CDP
146
- for a separately running app.
194
+ ## modelRouter actions
195
+ _Source: `versioned/next/reference/api.md` · Packages: api_
196
+ …
197
+ _…Truncated to approximate token budget (~300 tokens)._
198
+ ``````
147
199
 
148
- | Tool | Purpose |
149
- | --- | --- |
150
- | `get_syncdb_state` | Read status, entities/tombstones, outbox, conflicts, cursors, streams, repair markers, values, and debugger events |
151
- | `syncdb_snapshot` | Capture, list, get, compare, or delete snapshots held by this MCP process |
152
- | `syncdb_action` | Mutate or directly edit local state, flush, reconcile/resync, resolve/retry, toggle offline, clear debug events, or merge a snapshot |
200
+ #### terreno_get_component_docs
153
201
 
154
- State and snapshot responses redact fields containing `password`, `token`,
155
- `secret`, `authorization`, or `cookie`. Snapshot merge payloads are retained
156
- privately and are not returned. Start the local MCP with
157
- `TERRENO_MCP_EVAL=1` before calling `syncdb_action`.
202
+ Return the props table for one `@terreno/ui` component from `ui-types-documentation.json`, plus
203
+ related markdown excerpts.
158
204
 
159
- See [Debug a Terreno app with MCP](../how-to/debug-with-mcp.md) for examples and
160
- safety guidance.
205
+ | Parameter | Type | Required | Description |
206
+ |-----------|------|----------|-------------|
207
+ | `component` | string | yes | Exported component name, e.g. `Button` |
208
+ | `version` | string | no | Lockstep version. A retained snapshot returns that version's generated component page (MDX chrome stripped); otherwise current props are used and the fallback is noted. |
161
209
 
162
- ### terreno_search_docs
210
+ ``````json
211
+ {"name":"terreno_get_component_docs","arguments":{"component":"Badge"}}
212
+ ``````
163
213
 
164
- BM25-style keyword search over markdown bundled with the MCP server: `docs/resources/*.md`, synced Diátaxis docs under `docs/versioned/`, and per-component excerpts derived from `ui-types-documentation.json`. **Prefer this tool before guessing** Terreno APIs (same posture as Laravel Boost `search-docs`).
214
+ ``````markdown
215
+ # Badge
165
216
 
166
- **Parameters:**
217
+ **Interface:** `BadgeProps`
167
218
 
168
- ``````typescript
169
- {
170
- queries: string[]; // Required — one or more search phrases
171
- packages?: string[]; // Optional — filter by package id or scope, e.g. ["api", "@terreno/ui"]
172
- tokenLimit?: number; // Approximate max tokens of markdown (default 3000)
173
- version?: string; // Optional @terreno/* lockstep version (e.g. 57.2.0). Omit for current `next` docs.
174
- }
219
+ ## Props
220
+
221
+ | Name | Type | Required | Description |
222
+ |------|------|----------|-------------|
223
+ | `color` | `"bold" | "subtle"` | No | For the "status" variant, whether the dot uses the bold (saturated) or …
224
+ …
175
225
  ``````
176
226
 
177
- Unmatched versions fall back to the nearest retained snapshot (`website/versioned_docs/`, copied into `docs/versioned/` at MCP build). The response names the resolved version and includes a note when a fallback happened. Pass the consumer app's `@terreno/*` version from `application_info` or `package.json`. Range prefixes such as `^57.2.0` are stripped before matching.
227
+ A build without `ui-types-documentation.json` returns:
228
+ `Component documentation is unavailable (ui-types-documentation.json not found). Run `bun run sync-ui-docs` in mcp-server before build.`
178
229
 
179
- ### terreno_get_component_docs
230
+ #### terreno_get_upgrade_guide
180
231
 
181
- Returns the full props table for a single `@terreno/ui` component from `ui-types-documentation.json`, plus short related markdown excerpts when the search index finds matches.
232
+ Return bundled lockstep upgrade notes between two versions. The response always lists which versions
233
+ in the range have notes and names the gaps, because an empty concatenation would read as "nothing
234
+ changed". Note format: `mcp-server/src/docs/upgrades/README.md`.
182
235
 
183
- **Parameters:**
236
+ | Parameter | Type | Required | Description |
237
+ |-----------|------|----------|-------------|
238
+ | `fromVersion` | string | yes | Installed `@terreno/*` semver. Must be ≤ `toVersion`. |
239
+ | `toVersion` | string | yes | Target semver |
184
240
 
185
- ``````typescript
186
- {
187
- component: string; // e.g. "Button", "TextField"
188
- version?: string; // Optional @terreno/* lockstep version. Omit for current TypeDoc props.
189
- }
241
+ ``````json
242
+ {"name":"terreno_get_upgrade_guide","arguments":{"fromVersion":"57.13.0","toVersion":"57.14.0"}}
190
243
  ``````
191
244
 
192
- When `version` matches a retained docs snapshot, the tool returns that version's generated component page (MDX chrome stripped). Snapshot filenames may be hyphenated (`text-field.mdx`) or concatenated (`userinactivity.mdx`); lookup tries both. Otherwise it uses current `ui-types-documentation.json` and notes the fallback.
245
+ ``````markdown
246
+ Recorded notes in 57.13.0 → 57.14.0: 57.14.0
193
247
 
194
- ### terreno_get_upgrade_guide
248
+ ---
195
249
 
196
- Return bundled Terreno lockstep upgrade notes between two semver versions (markdown). Use before major bumps to `@terreno/*` packages. The response always lists which versions in the range **have** notes. If some or all versions have none, it names those gaps — an empty concatenation would look like “nothing changed,” which is usually false. `fromVersion` must be less than or equal to `toVersion`. Note format: `mcp-server/src/docs/upgrades/README.md`.
250
+ # Upgrade to 57.14.0
197
251
 
198
- **Parameters:**
252
+ # Upgrading to 57.14.0
199
253
 
200
- ``````typescript
201
- {
202
- fromVersion: string; // Installed @terreno/* semver (e.g. 0.19.0)
203
- toVersion: string; // Target semver to upgrade to (e.g. 0.20.0)
204
- }
254
+ **Action required:** No
255
+ **Affected packages:** @terreno/syncdb
256
+ …
205
257
  ``````
206
258
 
207
- ### terreno_generate_model
259
+ #### terreno_search_update_notes
208
260
 
209
- Generate a Mongoose model with Terreno conventions (timestamps, soft delete, owner tracking, type definitions).
261
+ Search bundled upgrade notes and, when `TERRENO_HELP_API_URL` is set, product announcements from a
262
+ Terreno backend help API. Returns JSON.
210
263
 
211
- **Parameters:**
264
+ | Parameter | Type | Required | Description |
265
+ |-----------|------|----------|-------------|
266
+ | `queries` | string[] | no | Keyword phrases |
267
+ | `question` | string | no | Natural-language question, used as an extra phrase |
268
+ | `source` | `auto` \| `bundled` \| `announcement` | no | Default `auto` |
269
+ | `limit` | number | no | Default 10 |
270
+ | `includeArchived` | boolean | no | Include archived announcements |
212
271
 
213
- ``````typescript
272
+ ``````json
273
+ {"name":"terreno_search_update_notes","arguments":{"queries":["rate limit"],"limit":2}}
274
+ ``````
275
+
276
+ ``````json
214
277
  {
215
- name: string; // Model name (PascalCase)
216
- fields: Array<{
217
- name: string; // Field name (camelCase)
218
- type: string; // "String" | "Number" | "Boolean" | "Date" | "ObjectId"
219
- required?: boolean;
220
- default?: string; // Default value as string
221
- ref?: string; // Referenced model name (for ObjectId)
222
- description?: string; // Field description (recommended)
223
- }>;
224
- hasOwner?: boolean; // Add ownerId field (default: false)
225
- softDelete?: boolean; // Add deleted field (default: false)
226
- timestamps?: boolean; // Add created/updated fields (default: true)
278
+ "hits": [
279
+ {
280
+ "excerpt": "# Upgrading to 57.3.0 **Action required:** Yes **Affected packages:** @terreno/api, …",
281
+ "id": "upgrade:57.3.0",
282
+ "kind": "upgrade",
283
+ "title": "Terreno 57.3.0 upgrade notes",
284
+ "version": "57.3.0"
285
+ },
286
+ …
287
+ ],
288
+ "sources": [
289
+ "bundled-upgrades"
290
+ ]
227
291
  }
228
292
  ``````
229
293
 
230
- **Example:**
294
+ #### terreno_get_update_note
295
+
296
+ Return the full text of one update note as JSON (`body`, `id`, `kind`, `title`, `version`).
297
+
298
+ | Parameter | Type | Required | Description |
299
+ |-----------|------|----------|-------------|
300
+ | `id` | string | yes | `upgrade:<semver>`, `announcement:<mongoId>`, or a bare upgrade semver |
301
+ | `includeArchived` | boolean | no | Allow archived announcements from the help API |
302
+
303
+ ``````json
304
+ {"name":"terreno_get_update_note","arguments":{"id":"upgrade:57.14.0"}}
305
+ ``````
231
306
 
232
307
  ``````json
233
308
  {
234
- "name": "Product",
235
- "fields": [
236
- {"name": "title", "type": "String", "required": true, "description": "Product title"},
237
- {"name": "price", "type": "Number", "required": true, "description": "Price in cents"},
238
- {"name": "active", "type": "Boolean", "default": "true", "description": "Is product active"}
239
- ],
240
- "hasOwner": true,
241
- "softDelete": true
309
+ "body": "Recorded notes in 57.14.0 → 57.14.0: 57.14.0\n\n---\n\n# Upgrade to 57.14.0\n…",
310
+ "id": "upgrade:57.14.0",
311
+ "kind": "upgrade",
312
+ "title": "Terreno 57.14.0 upgrade notes",
313
+ "version": "57.14.0"
242
314
  }
243
315
  ``````
244
316
 
245
- **Returns:**
317
+ #### terreno_ask_update_help
246
318
 
247
- - Model schema code with proper type definitions
248
- - Methods and statics structure
249
- - Plugin configuration
250
- - Export statements
319
+ Answer a question with ranked update-note excerpts and the ids to fetch with
320
+ `terreno_get_update_note`. Returns text.
251
321
 
252
- ### terreno_generate_route
322
+ | Parameter | Type | Required | Description |
323
+ |-----------|------|----------|-------------|
324
+ | `question` | string | yes | Question about what changed |
325
+ | `limit` | number | no | Default 5 |
326
+ | `includeArchived` | boolean | no | Include archived announcements |
253
327
 
254
- Generate modelRouter configuration with permissions and query options.
328
+ ``````json
329
+ {"name":"terreno_ask_update_help","arguments":{"question":"How do I enable rate limiting?","limit":2}}
330
+ ``````
255
331
 
256
- **Parameters:**
332
+ ``````text
333
+ Question: How do I enable rate limiting?
334
+ Sources: bundled-upgrades
257
335
 
258
- ``````typescript
259
- {
260
- modelName: string; // Model name (PascalCase)
261
- routePath: string; // API path (e.g., "/products")
262
- permissions?: {
263
- create?: "any" | "authenticated" | "admin" | "owner";
264
- list?: "any" | "authenticated" | "admin" | "owner";
265
- read?: "any" | "authenticated" | "admin" | "owner";
266
- update?: "any" | "authenticated" | "admin" | "owner";
267
- delete?: "any" | "authenticated" | "admin" | "owner";
268
- };
269
- queryFields?: string[]; // Allowed query parameters
270
- ownerFiltered?: boolean; // Apply OwnerQueryFilter (default: false)
271
- sort?: string; // Default sort order (e.g., "-created")
272
- populate?: Array<{path: string; fields?: string[]}>;
273
- }
336
+ Top matches (call terreno_get_update_note with `id` for the full note):
337
+ - [upgrade] Terreno 57.3.0 upgrade notes (upgrade:57.3.0)
338
+ # Upgrading to 57.3.0 **Action required:** Yes …
339
+ - [upgrade] Terreno 58.0.0 upgrade notes (upgrade:58.0.0)
340
+ …
274
341
  ``````
275
342
 
276
- **Example:**
343
+ #### terreno_generate_model
344
+
345
+ Generate a Mongoose model with `strict: "throw"`, virtuals in `toJSON`/`toObject`, and
346
+ `addDefaultPlugins`. Target path: `backend/src/models/<name>.ts`.
347
+
348
+ | Parameter | Type | Required | Description |
349
+ |-----------|------|----------|-------------|
350
+ | `name` | string | yes | Model name, PascalCase |
351
+ | `fields` | object[] | yes | Each `{name*, type*, required?, unique?, ref?, default?}`. `type`: `String`, `Number`, `Boolean`, `Date`, `ObjectId`, `Array`, `Mixed`. `default` is a string. |
352
+ | `hasOwner` | boolean | no | Add a required `ownerId` referencing `User` |
353
+ | `softDelete` | boolean | no | Enable soft delete |
277
354
 
278
355
  ``````json
279
- {
280
- "modelName": "Product",
281
- "routePath": "/products",
282
- "permissions": {
283
- "create": "authenticated",
284
- "list": "any",
285
- "read": "any",
286
- "update": "owner",
287
- "delete": "admin"
356
+ {"name":"terreno_generate_model","arguments":{"name":"Product","fields":[{"name":"title","type":"String","required":true}],"hasOwner":true}}
357
+ ``````
358
+
359
+ ``````text
360
+ **IMPORTANT: Write this code to a file**
361
+
362
+ File path: `backend/src/models/product.ts`
363
+
364
+ After writing the file:
365
+ 1. Export from `backend/src/models/index.ts`: `export * from "./product";`
366
+
367
+ ---
368
+
369
+ import mongoose from "mongoose";
370
+ import { addDefaultPlugins } from "@terreno/api";
371
+ …
372
+ const productSchema = new mongoose.Schema<ProductDocument, ProductModel>(
373
+ {
374
+ title: { type: String, required: true, trim: true },
375
+ ownerId: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true },
288
376
  },
289
- "queryFields": ["active", "category"],
290
- "ownerFiltered": true,
291
- "sort": "-created"
292
- }
377
+ …
293
378
  ``````
294
379
 
295
- **Returns:**
380
+ #### terreno_generate_route
296
381
 
297
- - Router setup code with modelRouter configuration
298
- - Permission mapping
299
- - Lifecycle hooks structure
300
- - Instructions for registering route
382
+ Generate a `modelRouter` route. Target path: `backend/src/api/<model>.ts`.
301
383
 
302
- ### terreno_generate_screen
384
+ | Parameter | Type | Required | Description |
385
+ |-----------|------|----------|-------------|
386
+ | `modelName` | string | yes | Model name, PascalCase |
387
+ | `routePath` | string | yes | API path, e.g. `/products` |
388
+ | `permissions` | object | no | `{create, list, read, update, delete}`, each `admin`, `any`, `authenticated`, `authOrReadOnly`, or `owner`. A missing or unknown value becomes `Permissions.IsAuthenticated`. |
389
+ | `queryFields` | string[] | no | Allowed query fields |
390
+ | `sort` | string | no | Default sort, e.g. `-created` |
391
+ | `ownerFiltered` | boolean | no | Add `OwnerQueryFilter` and an `ownerId`-setting `preCreate` |
303
392
 
304
- Generate React Native screen component with Terreno UI components.
393
+ ``````json
394
+ {"name":"terreno_generate_route","arguments":{"modelName":"Product","routePath":"/products","permissions":{"list":"authenticated"}}}
395
+ ``````
305
396
 
306
- **Parameters:**
397
+ ``````text
398
+ **IMPORTANT: Write this code to a file**
307
399
 
308
- ``````typescript
309
- {
310
- name: string; // Screen name (PascalCase)
311
- type: "list" | "detail" | "form" | "empty";
312
- modelName?: string; // Model name for CRUD screens
313
- fields?: string[]; // Fields to display/edit
314
- hasSearch?: boolean; // Add search bar (list screens)
315
- hasPagination?: boolean; // Add pagination (list screens)
316
- }
400
+ File path: `backend/src/api/product.ts`
401
+
402
+ After writing the file:
403
+ 1. Export from `backend/src/api/index.ts`: `export * from "./product";`
404
+ 2. Register in server setup: `addProductRoutes(router, options);`
405
+ 3. Regenerate frontend SDK: `bun run sdk` in the frontend directory
406
+ …
317
407
  ``````
318
408
 
319
- **Example:**
409
+ #### terreno_generate_screen
410
+
411
+ Generate a React Native screen with `@terreno/ui` components and generated RTK Query hooks. Target
412
+ path: `frontend/src/screens/<name>Screen.tsx`.
413
+
414
+ | Parameter | Type | Required | Description |
415
+ |-----------|------|----------|-------------|
416
+ | `name` | string | yes | Screen name, PascalCase |
417
+ | `type` | `list` \| `detail` \| `form` \| `empty` | yes | Screen kind |
418
+ | `modelName` | string | no | Model for data fetching |
419
+ | `fields` | string[] | no | Fields to display or edit |
320
420
 
321
421
  ``````json
322
- {
323
- "name": "ProductList",
324
- "type": "list",
325
- "modelName": "Product",
326
- "fields": ["title", "price", "active"],
327
- "hasSearch": true,
328
- "hasPagination": true
329
- }
422
+ {"name":"terreno_generate_screen","arguments":{"name":"ProductList","type":"list","modelName":"Product","fields":["title"]}}
330
423
  ``````
331
424
 
332
- **Returns:**
425
+ ``````text
426
+ **IMPORTANT: Write this code to a file**
333
427
 
334
- - React Native functional component
335
- - RTK Query hooks integration
336
- - @terreno/ui components (Box, Text, Button, Card, etc.)
337
- - Loading/error/empty states
428
+ File path: `frontend/src/screens/ProductListScreen.tsx`
429
+ …
430
+ import { useGetProductsQuery } from "@/store/openApiSdk";
338
431
 
339
- ### terreno_generate_form_fields
432
+ const ProductListScreen: React.FC = () => {
433
+ const { data: products, isLoading, error, refetch } = useGetProductsQuery({});
434
+ …
435
+ ``````
340
436
 
341
- Generate form field components for a model.
437
+ #### terreno_generate_form_fields
342
438
 
343
- **Parameters:**
439
+ Generate `@terreno/ui` form fields and their state declarations. Fields use the real
440
+ `@terreno/ui` props (`title`, `value`, `onChange`, `errorText`); `password` renders
441
+ `TextField type="password"`, and `number` and date values are strings. A required field adds an
442
+ `errors` state and `errorText={errors.<name>}`.
344
443
 
345
- ``````typescript
346
- {
347
- modelName: string; // Model name (PascalCase)
348
- fields: Array<{
349
- name: string;
350
- type: "text" | "number" | "boolean" | "date" | "select";
351
- required?: boolean;
352
- options?: string[]; // For select fields
353
- }>;
354
- }
444
+ | Parameter | Type | Required | Description |
445
+ |-----------|------|----------|-------------|
446
+ | `fields` | object[] | yes | Each `{name*, type*, label?, required?, options?}`. `type`: `text`, `email`, `password`, `number`, `textarea`, `select`, `boolean`, `date`, `datetime`. `options` is `[{label, value}]` for `select`. |
447
+
448
+ ``````json
449
+ {"name":"terreno_generate_form_fields","arguments":{"fields":[{"name":"title","type":"text","required":true}]}}
450
+ ``````
451
+
452
+ ``````text
453
+ **IMPORTANT: Write this code to a file**
454
+
455
+ File path: `your form component file`
456
+ …
457
+ // State declarations (add to your component):
458
+ const [title, setTitle] = useState("");
459
+ …
460
+ const [errors, setErrors] = useState<Record<string, string | undefined>>({});
461
+
462
+ // Form fields (add inside your component's return):
463
+ …
355
464
  ``````
356
465
 
357
- **Example:**
466
+ #### terreno_validate_model_schema
467
+
468
+ Check schema source text against Terreno conventions and list issues and suggestions. The
469
+ model's `<Name>Document` type may be declared in the file or imported from the app's type files
470
+ (`src/modelInterfaces.ts`, `src/types/models/`); "Missing TypeScript interfaces" means neither.
471
+
472
+ | Parameter | Type | Required | Description |
473
+ |-----------|------|----------|-------------|
474
+ | `schema` | string | yes | Schema source code |
358
475
 
359
476
  ``````json
360
- {
361
- "modelName": "Product",
362
- "fields": [
363
- {"name": "title", "type": "text", "required": true},
364
- {"name": "price", "type": "number", "required": true},
365
- {"name": "category", "type": "select", "options": ["electronics", "books", "clothing"]}
366
- ]
367
- }
477
+ {"name":"terreno_validate_model_schema","arguments":{"schema":"const s = new mongoose.Schema({title: {type: String}});"}}
368
478
  ``````
369
479
 
370
- **Returns:**
480
+ ``````text
481
+ Schema Validation Results:
371
482
 
372
- - TextField, NumberField, SelectField components
373
- - Validation logic structure
374
- - Form state management pattern
483
+ Issues Found:
484
+ - Missing strict: 'throw' in schema options
485
+ - Missing toJSON virtuals configuration
486
+ - Missing default plugins
487
+ - Missing TypeScript interfaces
375
488
 
376
- ### terreno_validate_model_schema
489
+ Suggestions:
490
+ …
491
+ ``````
377
492
 
378
- Validate a Mongoose schema against Terreno conventions.
493
+ #### terreno_validate_ui_blocks
379
494
 
380
- **Parameters:**
495
+ Validate one whole-reply YAML or JSON block document. The text is the report
496
+ `terreno-blocks validate` prints: one line per problem, `path CODE message — fix`. A valid document
497
+ with no warnings returns an empty string.
381
498
 
382
- ``````typescript
383
- {
384
- schemaCode: string; // Full schema code to validate
385
- }
499
+ | Parameter | Type | Required | Description |
500
+ |-----------|------|----------|-------------|
501
+ | `document` | string | yes | The YAML or JSON document |
502
+
503
+ ``````json
504
+ {"name":"terreno_validate_ui_blocks","arguments":{"document":"v: 1\nblocks:\n - type: bogus\n"}}
505
+ ``````
506
+
507
+ ``````text
508
+ blocks[0].type.type INVALID_ENUM blocks[0].type is not a supported block. — Set type to heading, text, metric, …, list, or html.
509
+ ``````
510
+
511
+ #### terreno_install_admin
512
+
513
+ Generate admin panel screen files and backend/frontend setup steps for `@terreno/admin-backend` and
514
+ `@terreno/admin-frontend`.
515
+
516
+ | Parameter | Type | Required | Description |
517
+ |-----------|------|----------|-------------|
518
+ | `models` | object[] | yes | Each `{modelName*, routePath*, displayName*, listFields*}`; `listFields` is string[] |
519
+
520
+ ``````json
521
+ {"name":"terreno_install_admin","arguments":{"models":[{"modelName":"Todo","routePath":"/todos","displayName":"Todos","listFields":["title"]}]}}
386
522
  ``````
387
523
 
388
- **Returns:**
524
+ ``````markdown
525
+ # Install Admin Panel
526
+
527
+ ## Files to Create
389
528
 
390
- - List of convention violations
391
- - Recommendations for fixes
392
- - Severity levels (error, warning, info)
529
+ - `frontend/app/(tabs)/admin/index.tsx`
530
+ - `frontend/app/(tabs)/admin/[model].tsx`
531
+ - `frontend/app/(tabs)/admin/[model]/create.tsx`
532
+ - `frontend/app/(tabs)/admin/[model]/[id].tsx`
393
533
 
394
- ### terreno_validate_ui_blocks
534
+ ## Setup Instructions
395
535
 
396
- Check one whole-reply YAML or JSON document. The text is the same report `terreno-blocks validate` prints: each problem is `path CODE message — fix`. A valid document with no warnings returns an empty string.
536
+ ### Step 1: Add `@terreno/admin-backend` to backend
537
+ …
538
+ ``````
397
539
 
398
- **Parameters:**
540
+ ### Prompts
541
+
542
+ Multi-step workflow prompts, served by `prompts/list` and `prompts/get`. `terreno_bootstrap` is
543
+ defined in `src/bootstrap.ts`; the rest in `src/prompts.ts`. `*` marks a required argument. All
544
+ arguments are strings.
545
+
546
+ | Prompt | Arguments | Purpose |
547
+ |--------|-----------|---------|
548
+ | `terreno_bootstrap` | `appName*`, `appDisplayName*` | Run `bunx create-terreno-app`, then `terreno_bootstrap_ai_rules` |
549
+ | `terreno_create_crud_feature` | `name*`, `fields*` (`title:string,price:number`), `hasOwner` (`yes`/`no`) | Model, routes, and frontend screens |
550
+ | `terreno_create_api_endpoint` | `path*`, `method*`, `description*` | Custom endpoint with OpenAPI docs |
551
+ | `terreno_create_ui_component` | `name*`, `type*` (`display`, `interactive`, `form`, `layout`) | Reusable `@terreno/ui`-style component |
552
+ | `terreno_create_form_screen` | `name*`, `fields*` (`name:text,email:email`), `endpoint*` | Form screen with validation |
553
+ | `terreno_add_authentication` | `features` (`email,social,passwordReset`) | Login/signup screens and auth state |
554
+ | `terreno_style_guide` | none | Terreno code style guide |
555
+ | `terreno_migrate_to_terreno_app` | `serverFile` | Move from `setupServer` to `TerrenoApp` |
556
+ | `terreno_upgrade` | `targetVersion` (omit for latest stable) | Lockstep upgrade: read notes, bump `@terreno/*`, test, delegate Expo SDK steps to the upgrading-expo skill |
557
+
558
+ ### Resources
559
+
560
+ Read-only markdown, loaded once at startup from `<docs root>/resources/` (see
561
+ `TERRENO_MCP_DOCS_DIR`). Restart the server to pick up changes.
562
+
563
+ | URI | Name | Source |
564
+ |-----|------|--------|
565
+ | `terreno://docs/overview` | Terreno Overview | `overview.md` |
566
+ | `terreno://docs/api` | @terreno/api Documentation | `api.md` |
567
+ | `terreno://docs/ui` | @terreno/ui Documentation | `ui.md` |
568
+ | `terreno://docs/syncdb` | @terreno/syncdb Documentation | `syncdb.md` |
569
+ | `terreno://docs/rtk` | @terreno/rtk Documentation (legacy) | `rtk.md` |
570
+ | `terreno://docs/patterns` | Terreno Patterns & Best Practices | `patterns.md` |
571
+ | `terreno://docs/ui/components` | @terreno/ui Component Reference | Generated from `ui-types-documentation.json` |
572
+ | `terreno://docs/ui/components/<slug>` | `<Component> Component` | One per component |
573
+
574
+ The two component rows exist only when `ui-types-documentation.json` is bundled.
575
+
576
+ ### Rate limiting
577
+
578
+ The hosted server limits its JSON-RPC routes per client IP: `/mcp` (all methods) and `POST /` share
579
+ one bucket. The `GET /` health check is never limited. The local stdio server has no limit.
580
+
581
+ - **Default:** 120 requests per 60 seconds per client IP, a fixed window. Search, codegen, and docs
582
+ calls are cheap per call (the search index is built once at startup), and an agent session makes
583
+ a few dozen calls a minute, so the default leaves room for several agents behind one NAT while
584
+ capping one abusive client at about 2 requests per second.
585
+ - **Configure:** `TERRENO_MCP_RATE_LIMIT_MAX` (`0` turns the limit off, for example in local
586
+ development) and `TERRENO_MCP_RATE_LIMIT_WINDOW_MS`. An invalid value stops the server at startup
587
+ rather than running unlimited.
588
+ - **Per instance:** counters live in memory on each Cloud Run instance. With `N` instances, one
589
+ client can make up to `N` times the limit in a window if its requests land on different
590
+ instances.
591
+ - **Client IP:** on Cloud Run the server trusts one proxy hop, so the client IP is the address the
592
+ Google Front End appended to `X-Forwarded-For`. Entries a client writes into that header itself
593
+ are ignored, so a forged header cannot switch buckets. Off Cloud Run no proxy is trusted and the
594
+ socket address is used. Set `TERRENO_MCP_TRUST_PROXY_HOPS` when you add a proxy layer (for
595
+ example `2` behind an external HTTPS load balancer).
596
+
597
+ A request over the limit gets HTTP `429` with `Retry-After`, `RateLimit`, and `RateLimit-Policy`
598
+ headers and a JSON-RPC error body. `id` echoes the request id when the body is a single request
599
+ with a string or number id; otherwise it is `null`.
399
600
 
400
- ``````typescript
601
+ ``````json
401
602
  {
402
- document: string; // Required — the YAML or JSON document
603
+ "jsonrpc": "2.0",
604
+ "error": {
605
+ "code": -32029,
606
+ "message": "Rate limit exceeded: 120 requests per 60 seconds per client IP. Retry after 42s.",
607
+ "data": {"limit": 120, "windowMs": 60000, "retryAfterSeconds": 42}
608
+ },
609
+ "id": 7
403
610
  }
404
611
  ``````
405
612
 
406
- **Returns:**
613
+ `-32029` is in the JSON-RPC range reserved for implementation-defined server errors. Clients
614
+ should wait `retryAfterSeconds` (or `Retry-After`) before retrying.
615
+
616
+ ## Local server (`terreno-mcp-local`)
617
+
618
+ ### Running the local server
619
+
620
+ Run it from the app repository root with `bunx -p @terreno/mcp terreno-mcp-local`. The bin and the
621
+ package have different names, so `bunx` needs `-p` unless `@terreno/mcp` is already installed in
622
+ the project (as in this monorepo). The bin is
623
+ `#!/usr/bin/env bun`, so it needs Bun; the `browser` tool needs Bun 1.4 or newer. It registers tools
624
+ only.
625
+
626
+ Project root discovery (`src/local/projectRoot.ts`): `TERRENO_PROJECT_ROOT` when set. Otherwise it
627
+ walks up to 20 directories from the working directory to the first directory with a `package.json`
628
+ and both a backend workspace (`backend/` or `example-backend/`) and a frontend workspace
629
+ (`frontend/` or `example-frontend/`), or one of them plus a non-empty `workspaces` array. With no
630
+ match it uses the working directory. Bootstrap names win when both exist.
631
+
632
+ Errors: a refused or invalid call usually returns explanatory text. A thrown error (for example from
633
+ `browser`, or a database exception) becomes a JSON-RPC error with code `-32603` and the error
634
+ message. An unknown tool name returns the text `Unknown tool: <name>`.
635
+
636
+ ### Local tools
637
+
638
+ All schemas set `additionalProperties: false`. Every tool returns one text block, JSON for most
639
+ tools. The server does not validate arguments against the schema: an unknown argument, such as
640
+ `projection` on `database_query`, is ignored, not rejected. Check a result against the parameter
641
+ table when it looks unfiltered.
642
+
643
+ | Tool | Purpose | Gated by `TERRENO_MCP_EVAL` |
644
+ |------|---------|------|
645
+ | [`application_info`](#application_info) | Workspace packages and key dependency versions | no |
646
+ | [`database_schema`](#database_schema) | Mongo collections, indexes, counts, model excerpts | no |
647
+ | [`database_query`](#database_query) | Read-only Mongo queries | no |
648
+ | [`read_logs`](#read_logs) | Merged backend, browser, Metro, and app log tail | no |
649
+ | [`last_error`](#last_error) | Newest error-level log entry | no |
650
+ | [`get_client_state`](#get_client_state) | RTK Query and SyncDB state of the running app | no |
651
+ | [`get_rtk_state`](#get_rtk_state) | Deprecated alias of `get_client_state` | no |
652
+ | [`get_syncdb_state`](#get_syncdb_state) | SyncDB client state | no |
653
+ | [`syncdb_snapshot`](#syncdb_snapshot) | Capture and compare SyncDB snapshots | no |
654
+ | [`syncdb_action`](#syncdb_action) | Change SyncDB client state | yes, every action |
655
+ | [`evaluate`](#evaluate) | Run JavaScript in the app runtime | yes |
656
+ | [`navigate`](#navigate) | Navigate the app with expo-router | yes |
657
+ | [`browser`](#browser) | Drive a headless WebView | only the `evaluate` action |
658
+
659
+ Example outputs below are real tool output, shortened with `…`. Where an output needs a running
660
+ Hermes app, which the capture environment did not have, the section says so and shows only the
661
+ call or the failure output.
662
+
663
+ #### application_info
664
+
665
+ Read the root `package.json` and the first backend (`backend/`, then `example-backend/`) and frontend
666
+ (`frontend/`, then `example-frontend/`) `package.json`. It reports `@terreno/*` dependencies and
667
+ `expo`, `react`, `react-native`, `mongoose`, `bun`, and `@types/bun` as declared (ranges such as
668
+ `^57.2.0`, `catalog:`, or `workspace:*`, not resolved versions). It checks that `bun.lock` exists but
669
+ does not parse it. Call it at the start of a session. No parameters.
670
+
671
+ ``````json
672
+ {"name":"application_info","arguments":{}}
673
+ ``````
407
674
 
408
- - The CLI report, including a trailing newline when there is at least one line
409
- - An empty string when the document is valid and has no warnings
675
+ ``````markdown
676
+ # Application info
410
677
 
411
- ### terreno_bootstrap_ai_rules
678
+ - **projectRoot**: `…`
679
+ - **root package**: @terreno/root @ 1.0.0
680
+ - **bun.lock present**: yes
412
681
 
413
- Scaffold AI coding assistant rules (AGENTS.md, Cursor/Windsurf rules, Copilot instructions, rulesync config).
682
+ ## Backend workspace (example-backend/)
683
+ - **name**: @terreno/example-backend
684
+ - **version**: 1.0.0
685
+ - @terreno/admin-backend: workspace:*
686
+ …
687
+ - **mongoose**: catalog:
414
688
 
415
- **Parameters:**
689
+ ## Frontend workspace (example-frontend/)
690
+ …
691
+ - **react-native**: catalog:
416
692
 
417
- ``````typescript
693
+ ## Lockfile
694
+ Workspace package versions are recorded under `workspaces` in `bun.lock`. Prefer installed semver over `latest` in generated code.
695
+ ``````
696
+
697
+ #### database_schema
698
+
699
+ List live Mongo collections with `estimatedDocumentCount` and indexes, plus the first 80 lines of
700
+ each `backend/src/models/*.ts` file (except `index.ts`), read as text. Only `backend/` is scanned,
701
+ not `example-backend/`. Each call connects with Mongoose and disconnects at the end.
702
+
703
+ Mongo URI: `MONGO_URI` from the environment, else `MONGO_URI`, else `MONGODB_URI` parsed from
704
+ `<project root>/backend/.env`. `example-backend/.env` is not read.
705
+
706
+ | Parameter | Type | Required | Description |
707
+ |-----------|------|----------|-------------|
708
+ | `collectionFilter` | string | no | Case-insensitive substring filter on collection names |
709
+ | `summary` | boolean | no | Count model files instead of printing excerpts |
710
+
711
+ ``````json
712
+ {"name":"database_schema","arguments":{"summary":true}}
713
+ ``````
714
+
715
+ Output with no URI configured:
716
+
717
+ ``````markdown
718
+ # Database schema
719
+
720
+ ## Declared models (static scan of `backend/src/models/*.ts`)
721
+
722
+ _(No model files found.)_
723
+
724
+ ## Live MongoDB
725
+
726
+ _Unavailable: set `MONGO_URI` in `backend/.env` or export `MONGO_URI` to include live collections._
727
+ ``````
728
+
729
+ #### database_query
730
+
731
+ Run one read-only query. Each call opens its own connection and closes it afterwards; queries time
732
+ out after 10 seconds (`maxTimeMS`), and `aggregate` runs with `allowDiskUse: false`. Same URI
733
+ resolution as `database_schema`. Results are returned unredacted, so avoid collections that hold
734
+ credentials (Better Auth keeps password hashes in `account`).
735
+
736
+ | Parameter | Type | Required | Description |
737
+ |-----------|------|----------|-------------|
738
+ | `collection` | string | yes | Collection name |
739
+ | `operation` | `find` \| `aggregate` \| `countDocuments` \| `distinct` | yes | Operation |
740
+ | `filter` | object | no | Query for `find`, `countDocuments`, `distinct`. Rejected anywhere it contains `$where`, `$function`, or `$accumulator`. |
741
+ | `pipeline` | object[] | no | Stages for `aggregate`. Rejected anywhere it contains `$out`, `$merge`, or `$function`. |
742
+ | `field` | string | for `distinct` | Field to collect |
743
+ | `limit` | number | no | Rows or distinct values returned. Default 50, clamped 1–200. |
744
+
745
+ Results: `find` and `aggregate` return `{collection, count, rows}`; `countDocuments` returns
746
+ `{collection, count}`; `distinct` returns `{collection, field, truncated, values}`.
747
+
748
+ ``````json
749
+ {"name":"database_query","arguments":{"collection":"todos","operation":"aggregate","pipeline":[{"$group":{"_id":"$completed","count":{"$sum":1}}},{"$sort":{"_id":1}}]}}
750
+ ``````
751
+
752
+ ``````json
418
753
  {
419
- appName: string;
420
- appDisplayName: string;
421
- description?: string;
422
- /** Optional — which @terreno/* packages to merge into rules. Use ids like `api`, `ui`, `rtk`, `admin-backend`, `admin-frontend`, or `@terreno/api`. Omit or pass only what the app uses so admin guidelines stay out of projects without the admin panel. */
423
- packages?: string[];
754
+ "collection": "todos",
755
+ "count": 1,
756
+ "rows": [
757
+ {
758
+ "_id": false,
759
+ "count": 2
760
+ }
761
+ ]
424
762
  }
425
763
  ``````
426
764
 
427
- Guideline bodies are composed from per-package `.ai/guidelines/core.md` files, copied into this package at build time (`bun run sync-package-guidelines` in `mcp-server/`).
765
+ ``````json
766
+ {"name":"database_query","arguments":{"collection":"todos","operation":"aggregate","pipeline":[{"$out":"x"}]}}
767
+ ``````
428
768
 
429
- **Returns:** Rules files and instructions for installing/syncing with rulesync.
769
+ ``````text
770
+ Pipeline rejected: `$out`, `$merge`, and `$function` are not allowed.
771
+ ``````
430
772
 
431
- ### terreno_install_admin
773
+ ``````json
774
+ {"name":"database_query","arguments":{"collection":"todos","operation":"find","filter":{"$where":"true"}}}
775
+ ``````
432
776
 
433
- Generate admin panel integration for `@terreno/admin-backend` and `@terreno/admin-frontend`.
777
+ ``````text
778
+ Filter rejected: `$where`, `$function`, and `$accumulator` are not allowed.
779
+ ``````
434
780
 
435
- **Parameters:** Model configurations with `modelName`, `routePath`, `displayName`, `listFields`, etc.
781
+ #### read_logs
436
782
 
437
- **Returns:** Frontend screen files and backend/frontend setup snippets.
783
+ Merge log entries from up to four sources, sort them by timestamp, and return the newest.
438
784
 
439
- ## Prompts
785
+ | Source | Reads | Written by | Notes |
786
+ |--------|-------|-----------|-------|
787
+ | `backend` | `app.log` in each existing `<root>/backend/.terreno/logs`, `<root>/example-backend/.terreno/logs`, `<root>/.terreno/logs` | `@terreno/api` logger JSONL transport (see `TERRENO_LOG_FILE`) at `<backend cwd>/.terreno/logs/app.log` | No `stack` field: `logger.error` stacks are inside `message` |
788
+ | `browser` | `browser.log` in the same directories | [`POST /__terreno/browser-logs`](api.md#development-browser-log-ingestion), fed by `installTerrenoDevConsoleLogger` from `@terreno/syncdb` | `console.error` entries carry no stack; uncaught errors carry `stack`. A render error caught by an error boundary is a `console.error` entry: no `stack`, unformatted `%o`/`%s` placeholders, and the component name in `message` |
789
+ | `metro` | In-memory ring (2000) from Metro's `ws://<metro>/events` | Metro dev server | Always empty with Expo CLI 57 (see [Metro and CDP connection](#metro-and-cdp-connection)) |
790
+ | `app` | In-memory ring (2000) of Hermes `Runtime.consoleAPICalled` over CDP | The running Hermes app | Needs a Hermes target; `stack` is a JSON string of the first 8 call frames; timestamps are receipt time |
440
791
 
441
- Multi-step workflow prompts that guide AI assistants through complex tasks.
792
+ The `metro` and `app` rings start filling on the first call that requests those sources and are
793
+ held for the life of the MCP process. Earlier output is not captured.
442
794
 
443
- ### terreno_bootstrap
795
+ | Parameter | Type | Required | Description |
796
+ |-----------|------|----------|-------------|
797
+ | `sources` | string[] | no | Any of `backend`, `browser`, `metro`, `app`. Default all four. |
798
+ | `entries` | number | no | Default 200, clamped 1–2000. Limits lines read per file and the merged result. |
799
+ | `level` | string | no | Exact, case-insensitive level match, e.g. `error` |
800
+ | `since` | string | no | ISO-8601 instant. Drops entries before it; entries without a parseable timestamp are kept. |
444
801
 
445
- Workflow prompt for scaffolding a new Terreno app. Instructs the assistant to run `bunx create-terreno-app`, then `terreno_bootstrap_ai_rules`.
802
+ | Response field | Content |
803
+ |----------------|---------|
804
+ | `connection` | Always the CDP and Metro events status (see [Metro and CDP connection](#metro-and-cdp-connection)), even when `metro` and `app` were not requested |
805
+ | `entries` | Each `{level, message, raw, source, timestamp}`; `raw` is the line as written |
806
+ | `metroHint` | Connection details for the `metro` and `app` sources requested; absent when neither was requested |
807
+ | `projectRoot` | Resolved project root |
808
+
809
+ ``````json
810
+ {"name":"read_logs","arguments":{"level":"error","entries":3}}
811
+ ``````
812
+
813
+ ``````json
814
+ {
815
+ "connection": "CDP: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?); Metro events: connected",
816
+ "entries": [
817
+ …,
818
+ {
819
+ "level": "error",
820
+ "message": "Uncaught TypeError: Cannot read properties of undefined (reading 'profile')",
821
+ "raw": "{\"level\":\"error\",\"message\":\"Uncaught TypeError: Cannot read properties of undefined (reading 'profile')\",\"source\":\"browser\",\"stack\":\"TypeError: Cannot read properties of undefined (reading 'profile')\\n at brokenHandler (eval at evaluate (:303:30), <anonymous>:4:19)\\n at eval (eval at evaluate (:303:30), <anonymous>:6:22)\",\"timestamp\":\"2026-10-12T00:24:58.867+00:00\"}",
822
+ "source": "browser",
823
+ "timestamp": "2026-10-12T00:24:58.867+00:00"
824
+ },
825
+ {
826
+ "level": "error",
827
+ "message": "Fallthrough error: SyntaxError: JSON Parse error: Unexpected identifier \"broken\"\nSyntaxError: JSON Parse error: Unexpected identifier \"broken\"\n at <parse> (:0)\n…",
828
+ "raw": "{\"level\":\"error\",\"message\":\"Fallthrough error: SyntaxError: …\",\"requestId\":\"2edcea24-66a8-40b6-821c-a62f57af91fc\",\"terrenoRequestLog\":{\"requestId\":\"2edcea24-66a8-40b6-821c-a62f57af91fc\",\"userId\":null},\"timestamp\":\"2026-10-12T00:25:13.380Z\"}",
829
+ "source": "backend",
830
+ "timestamp": "2026-10-12T00:25:13.380Z"
831
+ }
832
+ ],
833
+ "metroHint": "connected | Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?)",
834
+ "projectRoot": "/workspace"
835
+ }
836
+ ``````
446
837
 
447
- **Arguments:**
838
+ `app` source output, captured against a test device that speaks Metro's device protocol (not a real
839
+ Hermes runtime):
448
840
 
449
- - `appName` (string) — Application name in kebab-case
450
- - `appDisplayName` (string) — Human-readable display name
841
+ ``````json
842
+ {
843
+ "connection": "CDP: connected; Metro events: not connected",
844
+ "entries": [
845
+ …,
846
+ {"level": "info", "message": "fake app tick #94", "source": "app", "timestamp": "2026-10-12T00:47:43.238Z", "raw": "…"},
847
+ {"level": "error", "message": "fake app error #95", "source": "app", "timestamp": "2026-10-12T00:47:44.239Z", "raw": "{\"level\":\"error\",\"message\":\"fake app error #95\",\"source\":\"app\",\"stack\":\"[{\\\"columnNumber\\\":10,\\\"functionName\\\":\\\"tick\\\",\\\"lineNumber\\\":42,\\\"scriptId\\\":\\\"1\\\",\\\"url\\\":\\\"http://localhost:8082/index.bundle\\\"}]\",\"timestamp\":\"2026-10-12T00:47:44.239Z\"}"},
848
+ …
849
+ ],
850
+ "metroHint": "connected",
851
+ "projectRoot": "/workspace"
852
+ }
853
+ ``````
451
854
 
452
- ### terreno_upgrade
855
+ #### last_error
453
856
 
454
- Lockstep Terreno upgrade workflow: read bundled upgrade notes, bump `@terreno/*` packages, run tests, and delegate Expo SDK steps to the official upgrading-expo skill.
857
+ Return the newest `error`-level entry across the selected sources. It reads the last 500 lines of
858
+ each file plus the `metro` and `app` rings. On a hit it returns the entry's `raw` line only: backend
859
+ lines have no `source` field, and no connection status is appended.
455
860
 
456
- **Arguments:** `targetVersion` (optional) — target `@terreno/*` semver; omit to mean latest stable.
861
+ | Parameter | Type | Required | Description |
862
+ |-----------|------|----------|-------------|
863
+ | `sources` | string[] | no | Any of `backend`, `browser`, `metro`, `app`. Default all four. |
457
864
 
458
- ### terreno_create_crud_feature
865
+ ``````json
866
+ {"name":"last_error","arguments":{"sources":["browser"]}}
867
+ ``````
459
868
 
460
- Generate complete CRUD feature: backend model + routes + frontend screens.
869
+ ``````text
870
+ {"level":"error","message":"Uncaught TypeError: Cannot read properties of undefined (reading 'profile')","source":"browser","stack":"TypeError: Cannot read properties of undefined (reading 'profile')\n at brokenHandler (eval at evaluate (:303:30), <anonymous>:4:19)\n at eval (eval at evaluate (:303:30), <anonymous>:6:22)","timestamp":"2026-10-12T00:24:58.867+00:00"}
871
+ ``````
461
872
 
462
- **Arguments:**
873
+ With no error-level entry:
463
874
 
464
- - `name` (string) — Feature name (e.g., "Product")
465
- - `fields` (string) — Comma-separated fields: `title:string,price:number,active:boolean`
466
- - `hasOwner` (string) — "yes" or "no" (default: "no")
875
+ ``````text
876
+ No recent error-level entries in selected sources (app). CDP: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?); Metro events: connected
877
+ ``````
467
878
 
468
- **Workflow:**
879
+ #### get_client_state
469
880
 
470
- 1. Generate Mongoose model with type definitions
471
- 2. Generate API routes with permissions
472
- 3. Generate list screen with DataTable
473
- 4. Generate detail screen
474
- 5. Generate form screen with validation
475
- 6. Provide instructions for:
476
- - Registering routes in `server.ts`
477
- - Regenerating SDK: `bun run sdk`
478
- - Adding navigation
881
+ Detect and read the running app's client data layer:
479
882
 
480
- ### terreno_create_api_endpoint
883
+ - RTK Query: the Redux store registered by `registerTerrenoDevStore(store)` from `@terreno/rtk`
884
+ (`__DEV__` only) as `globalThis.__TERRENO_STORE__`.
885
+ - SyncDB: clients created with `createSyncDb({debug: true})`, registered on
886
+ `globalThis.__TERRENO_SYNCDB__`.
481
887
 
482
- Generate custom (non-CRUD) API endpoint with OpenAPI documentation.
888
+ It reads the in-process registries when the MCP process shares the app's heap; otherwise it runs a
889
+ fixed read-only expression in the app through CDP `Runtime.evaluate` (not gated, because the caller
890
+ cannot change the code). Keys matching `password`, `token`, `secret`, `authorization`, or `cookie`
891
+ (case-insensitive) are returned as `[REDACTED]`.
483
892
 
484
- **Arguments:**
893
+ | Parameter | Type | Required | Description |
894
+ |-----------|------|----------|-------------|
895
+ | `slice` | string | no | RTK: `auth` (Better Auth slice, else legacy `auth`), `betterAuth`, `rtk` or `terreno-rtk`, or any top-level state key. Omit for auth plus the RTK Query cache summary. |
896
+ | `query` | string | no | RTK: keep cache rows whose endpoint name or serialized args contain this substring (case-insensitive) |
897
+ | `name` | string | no | SyncDB client name. Default the first registered client. |
898
+ | `collection` | string | no | SyncDB collection filter |
899
+ | `entityId` | string | no | SyncDB entity id filter |
900
+ | `includeDebugEvents` | boolean | no | SyncDB: `false` returns debug stats without event bodies |
901
+ | `limit` | number | no | SyncDB entities per collection. Default 500, max 2000. |
485
902
 
486
- - `path` (string) — Endpoint path (e.g., "/stats/summary")
487
- - `method` (string) — HTTP method ("get", "post", "patch", "delete")
488
- - `description` (string) — What the endpoint does
903
+ Response: `{layer, rtk?, syncdb?}`. `layer` is `rtk`, `syncdb`, `syncdb+rtk` (both payloads
904
+ returned), or `none`. `rtk` holds `{auth, terrenoRtk: {queries, mutations}}` (rows are
905
+ `{args, endpoint, status}`) or the requested slice. `syncdb` holds the SyncDB inspect output. With
906
+ `layer: "none"` the response has `connection`, `hint`, and per-layer `reasons` instead.
489
907
 
490
- **Workflow:**
908
+ ``````json
909
+ {"name":"get_client_state","arguments":{}}
910
+ ``````
491
911
 
492
- 1. Generate route handler with asyncHandler
493
- 2. Generate OpenAPI builder configuration
494
- 3. Generate response types
495
- 4. Provide authentication setup instructions
496
- 5. Provide SDK regeneration instructions
912
+ Output with Metro running and no Hermes app attached (a populated response needs a running Hermes
913
+ app and is not shown):
497
914
 
498
- ### terreno_create_ui_component
915
+ ``````json
916
+ {
917
+ "connection": "CDP: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?); Metro events: not connected",
918
+ "hint": "No client data layer found. For RTK Query, call `registerTerrenoDevStore(store)` from @terreno/rtk in __DEV__. For SyncDB, create the client with `createSyncDb({debug: true})`. Keep the app running; when the MCP process cannot see the app heap, start Metro so CDP can reach Hermes (see `connection`).",
919
+ "layer": "none",
920
+ "reasons": {
921
+ "rtk": "Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?)",
922
+ "syncdb": "Error: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?)\nCDP: …"
923
+ }
924
+ }
925
+ ``````
499
926
 
500
- Generate reusable UI component following @terreno/ui patterns.
927
+ #### get_rtk_state
501
928
 
502
- **Arguments:**
929
+ Deprecated alias of `get_client_state`, kept for one release. Same schema, same handler, same
930
+ `{layer, rtk?, syncdb?}` response.
503
931
 
504
- - `name` (string) — Component name (PascalCase)
505
- - `type` (string) — "display" | "interactive" | "form" | "layout"
506
- - `description` (string) — Component purpose
932
+ #### get_syncdb_state
507
933
 
508
- **Workflow:**
934
+ Read a SyncDB client registered by `createSyncDb({debug: true})`: status, entities (including
935
+ tombstones and pending metadata), decoded outbox, conflicts, cursors, streams, repair markers,
936
+ values, and debugger events. Same access path and redaction as `get_client_state`. Not gated.
509
937
 
510
- 1. Generate component structure with TypeScript types
511
- 2. Include @terreno/ui imports (Box, Text, Button, etc.)
512
- 3. Add prop definitions with JSDoc
513
- 4. Include usage example
514
- 5. Provide testing setup
938
+ | Parameter | Type | Required | Description |
939
+ |-----------|------|----------|-------------|
940
+ | `name` | string | no | Client name. Default the first registered client. |
941
+ | `collection` | string | no | Collection filter |
942
+ | `entityId` | string | no | Entity id filter |
943
+ | `includeDebugEvents` | boolean | no | `false` returns debug stats without event bodies |
944
+ | `limit` | number | no | Entities per collection. Default 500, max 2000. |
515
945
 
516
- ### terreno_create_form_screen
946
+ ``````json
947
+ {"name":"get_syncdb_state","arguments":{"collection":"todos"}}
948
+ ``````
517
949
 
518
- Generate form screen with validation and error handling.
950
+ On success the text is the redacted inspect output as JSON. With no reachable client it is the
951
+ reason (shown without a Hermes app; a populated response is not shown):
519
952
 
520
- **Arguments:**
953
+ ``````text
954
+ Error: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?)
955
+ CDP: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?); Metro events: not connected
956
+ ``````
521
957
 
522
- - `name` (string) — Screen name (e.g., "CreateProduct")
523
- - `modelName` (string) — Model being created/edited
524
- - `fields` (string) — Comma-separated: `title:text,price:number,active:boolean`
958
+ #### syncdb_snapshot
525
959
 
526
- **Workflow:**
960
+ Capture, list, read, compare, or delete SyncDB snapshots. Snapshots live in the MCP process memory
961
+ and are lost when it exits. A capture keeps the full mergeable TinyBase content privately for a later
962
+ `syncdb_action` `mergeSnapshot`; returned state is redacted. Not gated.
527
963
 
528
- 1. Generate screen component with Page layout
529
- 2. Add form fields from @terreno/ui
530
- 3. Include validation logic
531
- 4. Add RTK mutation hooks
532
- 5. Add loading/error/success states
533
- 6. Provide navigation setup
964
+ | Parameter | Type | Required | Description |
965
+ |-----------|------|----------|-------------|
966
+ | `action` | `capture` \| `list` \| `get` \| `compare` \| `delete` | yes | Operation |
967
+ | `name`, `collection`, `entityId` | string | no | Capture filters, as in `get_syncdb_state` |
968
+ | `includeDebugEvents` | boolean | no | Capture option |
969
+ | `limit` | number | no | Capture option |
970
+ | `snapshotId` | string | for `get`, `compare`, `delete` | Snapshot id from `capture` or `list` |
971
+ | `otherSnapshotId` | string | for `compare` | Second snapshot. `compare` returns up to 200 path diffs. |
534
972
 
535
- ### terreno_add_authentication
973
+ ``````json
974
+ {"name":"syncdb_snapshot","arguments":{"action":"list"}}
975
+ ``````
536
976
 
537
- Generate authentication setup for new projects.
977
+ ``````json
978
+ []
979
+ ``````
538
980
 
539
- **Arguments:**
981
+ #### syncdb_action
982
+
983
+ Change a live SyncDB client, then return `{action, client, result, state}` with the redacted state
984
+ after the change. Every action requires `TERRENO_MCP_EVAL`.
985
+
986
+ | Parameter | Type | Required | Description |
987
+ |-----------|------|----------|-------------|
988
+ | `action` | enum | yes | `mutate`, `setLocalEntity`, `deleteLocalEntity`, `flush`, `reconcile`, `forceResync`, `resolveConflict`, `retryFailed`, `goOffline`, `goOnline`, `clearDebug`, `mergeSnapshot` |
989
+ | `name` | string | no | Client name |
990
+ | `collection`, `id` | string | no | Target row for `mutate`, `setLocalEntity`, `deleteLocalEntity` |
991
+ | `operation` | `create` \| `update` \| `delete` | no | For `mutate` |
992
+ | `data` | object | no | Row data for `mutate`, `setLocalEntity` |
993
+ | `maxAttempts` | number | no | For `mutate` |
994
+ | `deleted` | boolean | no | For `setLocalEntity` |
995
+ | `seq` | number | no | For `setLocalEntity` |
996
+ | `stream` | string | no | For `setLocalEntity` |
997
+ | `mutationId` | string | no | For `resolveConflict` |
998
+ | `strategy` | `useServer` \| `keepMine` | no | For `resolveConflict` |
999
+ | `entityId` | string | no | For `retryFailed` |
1000
+ | `snapshotId` | string | no | For `mergeSnapshot`; must belong to the same client |
540
1001
 
541
- - `strategies` (string) — Comma-separated: "email", "github", "google"
542
- - `includeRefreshToken` (string) — "yes" or "no" (default: "yes")
1002
+ ``````json
1003
+ {"name":"syncdb_action","arguments":{"action":"flush"}}
1004
+ ``````
543
1005
 
544
- **Workflow:**
1006
+ Without the opt-in:
545
1007
 
546
- 1. Configure User model with passport-local-mongoose
547
- 2. Set up auth routes in backend
548
- 3. Configure Redux auth slice in frontend
549
- 4. Generate login screen
550
- 5. Generate signup screen
551
- 6. Set up token storage
552
- 7. Provide environment variable list
1008
+ ``````text
1009
+ Refused: set `TERRENO_MCP_EVAL=1` to opt in to SyncDB state changes.
1010
+ ``````
553
1011
 
554
- ### terreno_migrate_to_terreno_app
1012
+ #### evaluate
555
1013
 
556
- Guide for migrating from `setupServer` to the `TerrenoApp` fluent API pattern.
1014
+ Run a JavaScript expression in the app's Hermes runtime with CDP `Runtime.evaluate`
1015
+ (`awaitPromise`, `returnByValue`). Returns `{result, status}` or `{error, status}`, where `status` is
1016
+ the connection status. Requires `TERRENO_MCP_EVAL`.
557
1017
 
558
- **Arguments:**
1018
+ | Parameter | Type | Required | Description |
1019
+ |-----------|------|----------|-------------|
1020
+ | `code` | string | yes | Expression to evaluate |
559
1021
 
560
- - `serverFile` (string, optional) — Path to server file to migrate (e.g., `src/server.ts`)
1022
+ ``````json
1023
+ {"name":"evaluate","arguments":{"code":"1 + 1"}}
1024
+ ``````
561
1025
 
562
- ### terreno_style_guide
1026
+ Without the opt-in (a successful call needs a Hermes app and is not shown):
563
1027
 
564
- Returns comprehensive code style guide from project documentation.
1028
+ ``````text
1029
+ Refused: set `TERRENO_MCP_EVAL=1` to opt in to `Runtime.evaluate` (arbitrary JS in the app runtime).
1030
+ ``````
565
1031
 
566
- **No arguments required.**
1032
+ #### navigate
567
1033
 
568
- **Returns:**
1034
+ Run a fixed snippet in the app that calls `require("expo-router").router.navigate(path)`, or
1035
+ `push(path)` when `navigate` is missing. Returns `{result, status}` with `result` like
1036
+ `{ok, method}`, or `{error, status}`. Requires `TERRENO_MCP_EVAL`.
569
1037
 
570
- - TypeScript conventions
571
- - React/React Native patterns
572
- - Backend API conventions
573
- - Testing practices
574
- - Logging guidelines
1038
+ | Parameter | Type | Required | Description |
1039
+ |-----------|------|----------|-------------|
1040
+ | `path` | string | yes | expo-router path |
575
1041
 
576
- ## Environment Variables
1042
+ ``````json
1043
+ {"name":"navigate","arguments":{"path":"/settings"}}
1044
+ ``````
577
1045
 
578
- ### Server Configuration
1046
+ Without the opt-in (a successful call needs a Hermes app and is not shown):
579
1047
 
580
- | Variable | Default | Description |
581
- |----------|---------|-------------|
582
- | `PORT` | `8080` | HTTP server port |
583
- | `MCP_HOST` or `HOST` | `0.0.0.0` | Server host address |
584
- | `TERRENO_MCP_DOCS_DIR` | `../docs` | Path to documentation directory (relative to dist/) |
585
- | `TERRENO_PROJECT_ROOT` | nearest Terreno project root | Override local package, database, and log discovery |
586
- | `TERRENO_METRO_URL` | Frontend script port or `http://localhost:8082` | Metro origin used for `/events` and CDP discovery |
587
- | `TERRENO_MCP_EVAL` | Disabled | Set to `1` to enable local MCP `evaluate`, `navigate`, and SyncDB state-changing actions |
588
- | `TERRENO_BROWSER_LOGS` | Enabled only when `NODE_ENV=development` | Set to `true` to opt in in another non-production environment or `false` to disable; production rejects the route, and non-loopback clients must authenticate |
1048
+ ``````text
1049
+ Refused: set `TERRENO_MCP_EVAL=1` to opt in to CDP-driven navigation (runs a fixed expo-router snippet in the app).
1050
+ ``````
589
1051
 
590
- **Example:**
1052
+ #### browser
1053
+
1054
+ Drive a headless page with Bun's built-in `Bun.WebView`: system WebKit on macOS; on Linux and
1055
+ Windows, Chrome, Chromium, or Edge over the Chrome DevTools Protocol (set `BUN_CHROME_PATH` when Bun
1056
+ cannot find one). One session per MCP process; calls are serialized. Use it to exercise a changed web
1057
+ flow, then `snapshot` and `screenshot` as proof, then `close`.
1058
+
1059
+ | Action | Uses | Notes |
1060
+ |--------|------|-------|
1061
+ | `open` | `url` (required), `width` (1280), `height` (720), `dataDir` | `http:`/`https:` only. Closes any open session. Storage is ephemeral unless `dataDir`, which must be under the project root. |
1062
+ | `click` | `selector` (required), `timeout` | `timeout` is the selector wait in ms |
1063
+ | `type` | `text`, `selector`, `timeout` | Clicks `selector` first when given |
1064
+ | `press` | `key` (required), `modifiers` | `modifiers`: `Shift`, `Control`, `Alt`, `Meta` |
1065
+ | `scroll` | `selector` or `x`/`y` | Scrolls to `selector`, else by the pixel delta |
1066
+ | `evaluate` | `code` (required) | Gated by `TERRENO_MCP_EVAL`; without it the call fails with JSON-RPC error `-32603` ``Refused: set `TERRENO_MCP_EVAL=1` to enable arbitrary JavaScript in the WebView.`` |
1067
+ | `snapshot` | none | Up to 200 visible interactive elements with a usable `selector`, plus page text (20 000 chars). Password input values are `[REDACTED]`. |
1068
+ | `screenshot` | `output` (required), `quality` | `.png`, `.jpg`/`.jpeg`, or `.webp` (other extensions write PNG). Path must be under the project root or `/opt/cursor/artifacts`. |
1069
+ | `back`, `forward`, `reload` | none | History navigation |
1070
+ | `wait` | `timeout` | Sleeps `timeout` ms (default 1000) |
1071
+ | `close` | none | Ends the session |
1072
+
1073
+ Results are JSON with `action`, `ok`, and the page `title` and `url`, plus action-specific fields.
1074
+ Calling any page action before `open` fails with `No browser session. Run the open action first.`
591
1075
 
592
- ``````bash
593
- PORT=3001 HOST=localhost bun run start
1076
+ ``````json
1077
+ {"name":"browser","arguments":{"action":"open","url":"http://localhost:8082","width":1280,"height":720}}
1078
+ {"name":"browser","arguments":{"action":"snapshot"}}
1079
+ {"name":"browser","arguments":{"action":"screenshot","output":"artifacts/app.png"}}
1080
+ {"name":"browser","arguments":{"action":"close"}}
594
1081
  ``````
595
1082
 
596
- ## Development
1083
+ ``````json
1084
+ {
1085
+ "action": "open",
1086
+ "ok": true,
1087
+ "title": "",
1088
+ "url": "http://localhost:8082/"
1089
+ }
1090
+ ``````
597
1091
 
598
- ``````bash
599
- # Install dependencies
600
- bun install
1092
+ ``````json
1093
+ {
1094
+ "action": "snapshot",
1095
+ "ok": true,
1096
+ "snapshot": {
1097
+ "elements": [
1098
+ …,
1099
+ {
1100
+ "ariaLabel": "Text input field",
1101
+ "disabled": false,
1102
+ "name": null,
1103
+ "placeholder": "Password",
1104
+ "role": "input",
1105
+ "selector": "#login-screen-password-input",
1106
+ "text": "[REDACTED]",
1107
+ "type": "password"
1108
+ },
1109
+ …,
1110
+ {
1111
+ "ariaLabel": "Log In",
1112
+ "disabled": true,
1113
+ "name": null,
1114
+ "placeholder": null,
1115
+ "role": "button",
1116
+ "selector": "[data-testid=\"login-screen-submit-button\"]",
1117
+ "text": "Log In",
1118
+ "type": "button"
1119
+ },
1120
+ …
1121
+ ],
1122
+ "text": "Welcome Back\nEmail\nPassword\n\nLog In\nForgot password?\n…",
1123
+ "title": "",
1124
+ "url": "http://localhost:8082/login"
1125
+ }
1126
+ }
1127
+ ``````
601
1128
 
602
- # Build
603
- bun run build
1129
+ ``````json
1130
+ {
1131
+ "action": "screenshot",
1132
+ "ok": true,
1133
+ "output": "…/artifacts/app.png",
1134
+ "title": "",
1135
+ "url": "http://localhost:8082/"
1136
+ }
1137
+ ``````
604
1138
 
605
- # (Build runs sync-ui-docs, sync-versioned-docs, and sync-package-guidelines before tsc.)
606
- # To refresh only bundled package AI guidelines from the monorepo:
607
- # bun run sync-package-guidelines
1139
+ ### `TERRENO_MCP_EVAL` gate
1140
+
1141
+ `evaluate`, `navigate`, `syncdb_action`, and the `browser` `evaluate` action run only when the local
1142
+ server's environment has `TERRENO_MCP_EVAL=1` or `TERRENO_MCP_EVAL=true`. The variable is read on
1143
+ every call; set it in the editor's server entry (`"env": {"TERRENO_MCP_EVAL": "1"}`).
1144
+
1145
+ | Gated | What it allows |
1146
+ |-------|----------------|
1147
+ | `evaluate` | Any JavaScript in the app's Hermes runtime |
1148
+ | `navigate` | A fixed expo-router snippet in the app runtime |
1149
+ | `syncdb_action` (all actions) | Writes to the app's local SyncDB store, outbox, and sync state |
1150
+ | `browser` `evaluate` | Any JavaScript in the WebView page |
1151
+
1152
+ The gate exists because these calls run code with the app's full privileges: its session tokens and
1153
+ secure storage, its network access as the signed-in user, and its local data. An agent can be steered
1154
+ by text it reads, including log lines and database rows, so code execution in the app is a per-process
1155
+ opt-in rather than a default.
1156
+
1157
+ Not gated: `get_client_state`, `get_rtk_state`, `get_syncdb_state`, and `syncdb_snapshot` also use
1158
+ `Runtime.evaluate` when the app is in another process, but only with fixed read expressions built by
1159
+ the server (caller values are JSON-encoded), and their output is redacted. `database_query` is
1160
+ read-only by operation allowlist and operator checks. The other `browser` actions only interact with
1161
+ the page as a user would.
1162
+
1163
+ ### Metro and CDP connection
1164
+
1165
+ `read_logs` (`metro`, `app`), `last_error`, `get_client_state`, `get_syncdb_state`,
1166
+ `syncdb_snapshot`, `syncdb_action`, `evaluate`, and `navigate` share one module-level session
1167
+ (`src/local/metro/metroDevSession.ts`):
1168
+
1169
+ - **Metro URL:** `TERRENO_METRO_URL`, else `--port N` from the frontend workspace's `scripts.start`
1170
+ (or `scripts.web`), else `http://localhost:8082`.
1171
+ - **Target:** `GET <metro>/json/list` (4 s timeout); the first target whose title contains `hermes`,
1172
+ else whose description contains `react native`, else the first with a `webSocketDebuggerUrl`. A
1173
+ web browser tab is not a target, so the `app` source and the CDP paths need a Hermes app (dev build
1174
+ or Expo Go) attached to Metro.
1175
+ - **Origin:** the debugger socket sends `Origin: <scheme>//127.0.0.1:<Metro port>`.
1176
+ `@react-native/dev-middleware` rejects a socket without an Origin, and Expo CLI closes one whose
1177
+ host is not `127.0.0.1:<Metro port>`. A base URL that reaches Metro through a port-remapping tunnel,
1178
+ or a Metro whose server URL is overridden by `REACT_NATIVE_PACKAGER_HOSTNAME` or
1179
+ `EXPO_PACKAGER_PROXY_URL`, still fails Expo's check.
1180
+ - **Lifetime:** connect lazily on first use, send `Runtime.enable`, then keep the socket for the life
1181
+ of the MCP process. Each CDP call times out after 8 s. After a close, the next call that needs the
1182
+ app reconnects, with no backoff.
1183
+
1184
+ The status appears as `CDP: <cdp status>; Metro events: <events status>` in `read_logs`
1185
+ `connection`, in `last_error` misses, and in `get_client_state` `layer: "none"` responses.
1186
+
1187
+ | CDP status | Meaning |
1188
+ |------------|---------|
1189
+ | `not connected` | No call has needed the app yet |
1190
+ | `connected to <title>` | This call opened a new socket |
1191
+ | `connected` | This call reused the open socket |
1192
+ | `Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?)` | Metro answered, but no Hermes app is attached |
1193
+ | `TypeError: Unable to connect. Is the computer able to access the url?` | Metro is not reachable (Bun's fetch error; the Metro URL is not included) |
1194
+ | `Error: CDP websocket error: …` | The debugger socket upgrade failed |
1195
+ | `CDP disconnected (code <code>: <reason>)` | Metro closed the socket; a `[NEW_DEBUGGER_OPENED]` reason adds a hint (see below) |
1196
+ | `CDP socket not open after connect` | The socket closed during connect |
1197
+
1198
+ | Metro events status | Meaning |
1199
+ |---------------------|---------|
1200
+ | `not connected` | No call has requested the `metro` source |
1201
+ | `connected ws://<host>/events` / `connected` | New / reused socket |
1202
+ | `Error: Metro /events unreachable at ws://<host>/events` | Metro is not reachable |
1203
+
1204
+ The events status is not updated when the socket later closes, so a call that skips `metro` can
1205
+ print a stale `connected`.
1206
+
1207
+ **`metro` source under Expo CLI 57:** the `/events` socket opens and reports `connected`, but
1208
+ receives nothing. Expo CLI 57.0.24 (`createEventsSocket`) registers its connection handler on one
1209
+ WebSocket server and serves `/events` from a second one with no handler, so no client is ever counted
1210
+ and no event is broadcast. The `metro` source works only with a Metro whose `/events` broadcasts,
1211
+ such as the React Native community CLI. Read bundler errors from the Metro terminal instead.
1212
+
1213
+ ### React Native DevTools and the `app` source
1214
+
1215
+ React Native 0.86 (Terreno 57) reports `supportsMultipleDebuggers` for each app page. On a normal
1216
+ `expo start`, React Native DevTools and `terreno-mcp-local` attach to the app at the same time, and
1217
+ opening DevTools does not affect the `app` source.
1218
+
1219
+ When Metro allows one debugger per app (older React Native, or Expo running headless with
1220
+ `EXPO_UNSTABLE_HEADLESS=1` or in a webcontainer), opening DevTools (`j` in Metro) disconnects the
1221
+ MCP. A call that does not read the app then shows, verbatim:
1222
+
1223
+ ``````text
1224
+ CDP: CDP disconnected (code 1000: [NEW_DEBUGGER_OPENED] New debugger opened for the same app instance); another debugger (such as React Native DevTools) took the session, and the next call that reads the app reconnects and disconnects it
1225
+ ``````
608
1226
 
609
- # Watch mode (rebuilds on changes)
610
- bun run dev
1227
+ The next call that reads the app (`read_logs` or `last_error` with `app`, or any CDP tool)
1228
+ reconnects automatically without an error, reports `CDP: connected to <app> (<device>)`, and
1229
+ disconnects DevTools, which shows `[NEW_DEBUGGER_OPENED] New debugger opened for the same app
1230
+ instance`. App console output logged while DevTools held the connection is not in `read_logs`; the
1231
+ first call after recovery may return only entries from before the gap.
1232
+
1233
+ Workaround: use one or the other. Close DevTools, then call `read_logs` with `sources: ["app"]`
1234
+ twice: the first call reconnects, the second shows new entries. To keep DevTools open, stop calling
1235
+ tools that read the app, or each call takes the connection back.
1236
+
1237
+ ## What the local server can and cannot see
1238
+
1239
+ The local server inspects the project from the outside. It never imports or runs consumer code in its
1240
+ own process: model files are read as text, and the app's runtime is reached only over Metro and CDP.
1241
+
1242
+ **Can see:**
1243
+
1244
+ | Surface | How |
1245
+ |---------|-----|
1246
+ | Package manifests | Root and backend/frontend workspace `package.json` files (declared ranges); `bun.lock` presence only |
1247
+ | MongoDB | `MONGO_URI` from the environment or `backend/.env`; collections, indexes, counts, and read-only queries |
1248
+ | Model source | First 80 lines of each `backend/src/models/*.ts`, as text |
1249
+ | Backend and browser logs | `app.log` and `browser.log` JSONL under `.terreno/logs/` in `backend/`, `example-backend/`, or the root |
1250
+ | Metro | `/json/list` targets and the `/events` socket |
1251
+ | Running Hermes app | Console output, client state, SyncDB state over CDP; code execution and navigation with `TERRENO_MCP_EVAL` |
1252
+ | Web app | Rendered page through the `browser` tool's own headless WebView |
1253
+
1254
+ **Cannot see:**
1255
+
1256
+ - Production or deployed environments: no remote logs, databases, or apps. Production backends do
1257
+ not write the JSONL log or accept browser logs.
1258
+ - The web app's JavaScript heap or console through CDP. A browser tab is not a Metro debug target; use
1259
+ `browser`, `browser.log`, and the backend log instead.
1260
+ - Native crash reports, device logs, or a device that is not attached to the dev server's Metro.
1261
+ - App console output from before the first `app` read, or while another debugger held a
1262
+ single-debugger session.
1263
+ - Metro bundler events under Expo CLI 57 (the `metro` source is empty).
1264
+ - The resolved dependency tree: `application_info` reports declared ranges, not installed versions.
1265
+ - `example-backend/.env` and `example-backend/src/models/` (database discovery reads `backend/` only).
1266
+
1267
+ ## Environment variables
1268
+
1269
+ ### Hosted server variables
1270
+
1271
+ | Variable | Default | Effect |
1272
+ |----------|---------|--------|
1273
+ | `PORT` | `8080` | HTTP listen port. Parsed with `parseInt`, so leading digits win (`80abc` is port 80); a value with no leading digits stops startup with `Invalid PORT value`. |
1274
+ | `MCP_HOST`, then `HOST` | `0.0.0.0` | Listen host |
1275
+ | `NODE_ENV` | unset | `production` disables console colors and warns at startup when `SENTRY_DSN` is unset |
1276
+ | `SENTRY_DSN` | unset (Sentry disabled) | Sentry DSN |
1277
+ | `APP_ENV` | `development` | Sentry `environment` |
1278
+ | `SENTRY_TRACES_SAMPLE_RATE` | `0.1` | Sentry trace sample rate (float) |
1279
+ | `TERRENO_MCP_DOCS_DIR` | `<module dir>/docs` (`dist/docs`), else `docs/` next to the executable | Root for bundled resources, prompts' markdown, the search index, and upgrade notes |
1280
+ | `TERRENO_HELP_API_URL` | unset | Terreno backend help API for announcements in the three update-note tools. Unset means bundled upgrade notes only. |
1281
+ | `TERRENO_HELP_API_TOKEN` | unset | Bearer token for `TERRENO_HELP_API_URL` |
1282
+ | `TERRENO_MCP_RATE_LIMIT_MAX` | `120` | JSON-RPC requests per client IP per window; `0` disables. Invalid values stop startup. See [Rate limiting](#rate-limiting). |
1283
+ | `TERRENO_MCP_RATE_LIMIT_WINDOW_MS` | `60000` | Rate-limit window in milliseconds (minimum 1) |
1284
+ | `TERRENO_MCP_TRUST_PROXY_HOPS` | `1` when `K_SERVICE` is set, else `0` | Reverse-proxy hops trusted for the client IP in `X-Forwarded-For` |
1285
+ | `K_SERVICE` | set by Cloud Run | Read only to choose the `TERRENO_MCP_TRUST_PROXY_HOPS` default |
1286
+
1287
+ ### Local server variables
1288
+
1289
+ | Variable | Default | Effect |
1290
+ |----------|---------|--------|
1291
+ | `TERRENO_PROJECT_ROOT` | Discovered root (see [Running the local server](#running-the-local-server)) | Root for packages, database, logs, Metro URL, and browser paths |
1292
+ | `TERRENO_METRO_URL` | `--port` from the frontend `start`/`web` script, else `http://localhost:8082` | Metro HTTP origin for `/json/list`, `/events`, and the debugger Origin's port |
1293
+ | `TERRENO_MCP_EVAL` | disabled | `1` or `true` enables `evaluate`, `navigate`, `syncdb_action`, and `browser` `evaluate`. See [`TERRENO_MCP_EVAL` gate](#terreno_mcp_eval-gate). |
1294
+ | `MONGO_URI` | `MONGO_URI`, then `MONGODB_URI`, from `<root>/backend/.env` | MongoDB for `database_schema` and `database_query` |
1295
+ | `BUN_CHROME_PATH` | Bun auto-discovery | Chrome, Chromium, or Edge binary for `browser` on Linux and Windows (read by Bun, not Terreno) |
1296
+
1297
+ ### App-side variables that feed the local server
1298
+
1299
+ | Variable | Read by | Default | Effect |
1300
+ |----------|---------|---------|--------|
1301
+ | `TERRENO_LOG_FILE` | `@terreno/api` logger | on when `NODE_ENV !== "production"` | Backend JSONL transport to `<cwd>/.terreno/logs/app.log` (5 MB × 3 files, level `debug`). `false`/`0` forces off, `true`/`1` forces on. A toggle, not a path. |
1302
+ | `TERRENO_BROWSER_LOGS` | `@terreno/api` `TerrenoApp` | on only when `NODE_ENV === "development"` | Registers `POST /__terreno/browser-logs`, which appends to `<cwd>/.terreno/logs/browser.log`. `true`/`1` opts in outside development, `false`/`0` disables; production never registers it. See [Development browser-log ingestion](api.md#development-browser-log-ingestion). |
1303
+ | `NODE_ENV` | `@terreno/api` | unset | Drives both defaults above |
1304
+ | `TERRENO_DEV_CONSOLE_LOGGER_TEST` | `@terreno/syncdb` `installTerrenoDevConsoleLogger` | unset | Test-only: `true` lets the console logger install outside `__DEV__` |
1305
+
1306
+ The full cross-package list is in [Environment variables](environment-variables.md).
611
1307
 
612
- # Start server
613
- bun run start
1308
+ ## Development
614
1309
 
615
- # Lint
1310
+ ``````bash
1311
+ # From the monorepo root
1312
+ bun install
1313
+ bun run mcp:build # sync docs + tsc + copy docs into dist/
1314
+
1315
+ # From mcp-server/
1316
+ bun run compile # type-check only
1317
+ bun run build # full artifact (sync-ui-docs, sync-versioned-docs, sync-package-guidelines, tsc, copy docs)
1318
+ bun run dev # hosted server with watch
1319
+ bun run start # hosted server from dist/ (run build first)
1320
+ bun run test # bun test src/
616
1321
  bun run lint
617
-
618
- # Fix lint issues
619
- bun run lint:fix
620
1322
  ``````
621
1323
 
1324
+ `bun run sync-package-guidelines` refreshes only the bundled package AI guidelines.
1325
+
622
1326
  ### Docker
623
1327
 
624
- ``````bash
625
- # Build image
626
- docker build -t terreno-mcp-server ./mcp-server
1328
+ Build from the repository root, as `scripts/ci/gcp-deploy.sh mcp-prod` does. The image needs the
1329
+ monorepo (`api/`, `blocks/`, `ui/`, `docs/`, `website/versioned_docs/`):
627
1330
 
628
- # Run container
1331
+ ``````bash
1332
+ docker build --file mcp-server/Dockerfile -t terreno-mcp-server .
629
1333
  docker run --rm -p 8080:8080 terreno-mcp-server
630
1334
  ``````
631
1335
 
1336
+ `mcp-server/src/docs/ui-types-documentation.json` and `mcp-server/src/docs/versioned/` are
1337
+ git-ignored, so the builder stage generates them. `bun run --filter "@terreno/ui" types` writes the
1338
+ TypeDoc JSON that `terreno_get_component_docs` reads. `sync-versioned-docs` and
1339
+ `sync-package-guidelines` fill the `terreno_search_docs` corpus. Without that step the image served
1340
+ the one-component test fixture (`No component named "Badge". Examples: Button`) and searched only
1341
+ `resources/*.md`.
1342
+
632
1343
  ### Testing with MCP Inspector
633
1344
 
634
1345
  ``````bash
635
- # Start server
636
- bun run start
1346
+ # Local server over stdio
1347
+ bunx @modelcontextprotocol/inspector bun ./mcp-server/dist/local/index.js
637
1348
 
638
- # In another terminal, use MCP inspector
639
- npx @modelcontextprotocol/inspector bun run ./mcp-server/dist/index.js
1349
+ # Hosted server: start it, then connect the Inspector with Streamable HTTP to http://localhost:8080/mcp
1350
+ TERRENO_MCP_RATE_LIMIT_MAX=0 bun ./mcp-server/dist/index.js
1351
+ bunx @modelcontextprotocol/inspector
640
1352
  ``````
641
1353
 
642
1354
  ## Architecture
@@ -644,29 +1356,34 @@ npx @modelcontextprotocol/inspector bun run ./mcp-server/dist/index.js
644
1356
  ``````
645
1357
  mcp-server/
646
1358
  ├── src/
647
- │ ├── index.ts # Express server + JSON-RPC handlers
648
- │ ├── resources.ts # Documentation resource loader
649
- │ ├── tools.ts # Code generation tools
650
- │ ├── prompts.ts # Workflow prompts
651
- │ └── docs/ # Inline documentation content
652
- ├── dist/ # Compiled output
653
- └── Dockerfile # Container image
1359
+ │ ├── index.ts # Hosted entry: reads env, starts the HTTP server
1360
+ │ ├── httpApp.ts # Express app: JSON-RPC handlers, routes, health check
1361
+ │ ├── rateLimit.ts # Per-IP JSON-RPC rate limit and trust-proxy config
1362
+ │ ├── instrument.ts # Sentry init
1363
+ │ ├── tools.ts # Hosted tools and dispatch
1364
+ │ ├── bootstrap.ts # terreno_bootstrap_ai_rules tool and terreno_bootstrap prompt
1365
+ │ ├── prompts.ts # Workflow prompts
1366
+ │ ├── resources.ts # Documentation resources
1367
+ │ ├── docsRoot.ts # Bundled docs root (TERRENO_MCP_DOCS_DIR)
1368
+ │ ├── search/ # Docs search index
1369
+ │ ├── help/ # Update-note search and help API client
1370
+ │ ├── docs/ # Bundled markdown (resources, upgrades, guidelines)
1371
+ │ └── local/
1372
+ │ ├── index.ts # Local entry (stdio)
1373
+ │ ├── localServer.ts # Server and tools/list, tools/call handlers
1374
+ │ ├── localTools.ts # Local tool schemas and dispatch
1375
+ │ ├── projectRoot.ts # Project root discovery
1376
+ │ ├── logPaths.ts # .terreno/logs discovery
1377
+ │ ├── metro/ # Metro /events and CDP session
1378
+ │ └── tools/ # Tool implementations
1379
+ ├── dist/ # Compiled output
1380
+ └── Dockerfile # Hosted server image
654
1381
  ``````
655
1382
 
656
- **JSON-RPC 2.0 handlers:**
657
-
658
- - `resources/list` — List available documentation
659
- - `resources/read` — Read documentation content
660
- - `tools/list` — List available tools
661
- - `tools/call` — Execute a tool
662
- - `prompts/list` — List available prompts
663
- - `prompts/get` — Get prompt details
664
-
665
1383
  ## Deployment
666
1384
 
667
- See [mcp-server/README.md](../../mcp-server/README.md#cicd) for:
668
-
669
- - GitHub Actions workflows
670
- - Google Cloud Run deployment
671
- - Required secrets configuration
1385
+ The hosted server runs as the `terreno-mcp` Cloud Run service. CircleCI's continuous-delivery
1386
+ pipeline (`.circleci/config.yml`, triggered by changes under `mcp-server/`) builds and rolls the
1387
+ image; Terraform owns the service's structure, and its hostname cutover is in
1388
+ [infra/flourish/README.md](../../infra/flourish/README.md#hosted-mcp-hostname).
672
1389
  - Workload Identity Federation setup