@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.
- package/CHANGELOG.md +393 -0
- package/README.md +32 -269
- package/dist/bootstrap.d.ts +12 -0
- package/dist/bootstrap.d.ts.map +1 -1
- package/dist/bootstrap.js +77 -122
- package/dist/bootstrap.js.map +1 -1
- package/dist/docs/guidelines/ui/guidelines/core.md +1 -1
- package/dist/docs/resources/overview.md +4 -2
- package/dist/docs/resources/patterns.md +237 -176
- package/dist/docs/resources/ui.md +48 -17
- package/dist/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/dist/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/dist/docs/upgrades/57.20.0.md +238 -0
- package/dist/docs/versioned/next/approval-rubric.md +30 -12
- package/dist/docs/versioned/next/explanation/README.md +5 -0
- package/dist/docs/versioned/next/explanation/account-linking.md +169 -0
- package/dist/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/dist/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/dist/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/dist/docs/versioned/next/explanation/authentication.md +45 -1
- package/dist/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/dist/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/dist/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/dist/docs/versioned/next/explanation/organizations.md +37 -0
- package/dist/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/dist/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/dist/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/dist/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/dist/docs/versioned/next/how-to/README.md +7 -2
- package/dist/docs/versioned/next/how-to/audit-log.md +22 -3
- package/dist/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/dist/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/dist/docs/versioned/next/how-to/circleci.md +15 -5
- package/dist/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/dist/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/dist/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/dist/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/dist/docs/versioned/next/how-to/password-reset.md +1 -1
- package/dist/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/dist/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/dist/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/dist/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/dist/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/dist/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/dist/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/dist/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/dist/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/dist/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/dist/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/dist/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/dist/docs/versioned/next/reference/README.md +1 -0
- package/dist/docs/versioned/next/reference/account-security.md +700 -0
- package/dist/docs/versioned/next/reference/admin-backend.md +3 -1
- package/dist/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/dist/docs/versioned/next/reference/api.md +461 -35
- package/dist/docs/versioned/next/reference/cli.md +1 -1
- package/dist/docs/versioned/next/reference/comms.md +8 -0
- package/dist/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/dist/docs/versioned/next/reference/environment-variables.md +9 -3
- package/dist/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/dist/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/dist/docs/versioned/next/reference/syncdb.md +8 -1
- package/dist/docs/versioned/next/reference/ui.md +1271 -27
- package/dist/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- package/dist/docs/versioned/next/tutorials/README.md +4 -0
- package/dist/httpApp.d.ts +14 -0
- package/dist/httpApp.d.ts.map +1 -0
- package/dist/httpApp.js +99 -0
- package/dist/httpApp.js.map +1 -0
- package/dist/index.js +8 -90
- package/dist/index.js.map +1 -1
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +44 -14
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/metro/metroDevSession.d.ts +18 -0
- package/dist/local/metro/metroDevSession.d.ts.map +1 -1
- package/dist/local/metro/metroDevSession.js +42 -3
- package/dist/local/metro/metroDevSession.js.map +1 -1
- package/dist/local/tools/databaseQuery.d.ts.map +1 -1
- package/dist/local/tools/databaseQuery.js +3 -13
- package/dist/local/tools/databaseQuery.js.map +1 -1
- package/dist/local/tools/databaseSchema.d.ts.map +1 -1
- package/dist/local/tools/databaseSchema.js +3 -13
- package/dist/local/tools/databaseSchema.js.map +1 -1
- package/dist/local/tools/localMongo.d.ts +10 -0
- package/dist/local/tools/localMongo.d.ts.map +1 -0
- package/dist/local/tools/localMongo.js +30 -0
- package/dist/local/tools/localMongo.js.map +1 -0
- package/dist/local/tools/runtime.d.ts +18 -2
- package/dist/local/tools/runtime.d.ts.map +1 -1
- package/dist/local/tools/runtime.js +75 -20
- package/dist/local/tools/runtime.js.map +1 -1
- package/dist/local/tools/syncDb.d.ts +11 -0
- package/dist/local/tools/syncDb.d.ts.map +1 -1
- package/dist/local/tools/syncDb.js +15 -3
- package/dist/local/tools/syncDb.js.map +1 -1
- package/dist/rateLimit.d.ts +35 -0
- package/dist/rateLimit.d.ts.map +1 -0
- package/dist/rateLimit.js +112 -0
- package/dist/rateLimit.js.map +1 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +42 -60
- package/dist/tools.js.map +1 -1
- package/package.json +4 -3
- package/src/docs/guidelines/ui/guidelines/core.md +1 -1
- package/src/docs/resources/overview.md +4 -2
- package/src/docs/resources/patterns.md +237 -176
- package/src/docs/resources/ui.md +48 -17
- package/src/docs/templates/bootstrap/frontend/app/(tabs)/profile.template.tsx +0 -26
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +3 -2
- package/src/docs/templates/bootstrap/frontend/app/login.template.tsx +1 -1
- package/src/docs/upgrades/57.20.0.md +238 -0
- package/src/docs/versioned/next/approval-rubric.md +30 -12
- package/src/docs/versioned/next/explanation/README.md +5 -0
- package/src/docs/versioned/next/explanation/account-linking.md +169 -0
- package/src/docs/versioned/next/explanation/admin-interface.md +1 -1
- package/src/docs/versioned/next/explanation/agent-guidelines.md +173 -0
- package/src/docs/versioned/next/explanation/ai-development-loop.md +196 -0
- package/src/docs/versioned/next/explanation/authentication.md +45 -1
- package/src/docs/versioned/next/explanation/consent-forms.md +105 -0
- package/src/docs/versioned/next/explanation/example-coverage.md +1 -0
- package/src/docs/versioned/next/explanation/local-first-data.md +1 -1
- package/src/docs/versioned/next/explanation/organizations.md +37 -0
- package/src/docs/versioned/next/explanation/planning-plugins.md +4 -0
- package/src/docs/versioned/next/explanation/responsive-design.md +143 -0
- package/src/docs/versioned/next/explanation/roadmap-seed-issues.md +11 -0
- package/src/docs/versioned/next/explanation/schema-metadata.md +5 -0
- package/src/docs/versioned/next/how-to/README.md +7 -2
- package/src/docs/versioned/next/how-to/audit-log.md +22 -3
- package/src/docs/versioned/next/how-to/automate-terreno-upgrades.md +169 -0
- package/src/docs/versioned/next/how-to/build-list-detail-screens.md +160 -0
- package/src/docs/versioned/next/how-to/circleci.md +15 -5
- package/src/docs/versioned/next/how-to/configure-better-auth.md +673 -53
- package/src/docs/versioned/next/how-to/create-a-terreno-app.md +8 -5
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +393 -23
- package/src/docs/versioned/next/how-to/enable-mcp-oauth.md +54 -3
- package/src/docs/versioned/next/how-to/migrate-rtk-to-syncdb.md +9 -0
- package/src/docs/versioned/next/how-to/password-reset.md +1 -1
- package/src/docs/versioned/next/how-to/recover-locked-out-user.md +112 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +37 -8
- package/src/docs/versioned/next/how-to/set-up-social-login.md +424 -0
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +241 -36
- package/src/docs/versioned/next/how-to/sign-consent-on-behalf.md +139 -0
- package/src/docs/versioned/next/how-to/upgrade-terreno.md +4 -0
- package/src/docs/versioned/next/implementationPlans/ai-dev-loop-boost.md +47 -32
- package/src/docs/versioned/next/implementationPlans/auth-experience.md +952 -0
- package/src/docs/versioned/next/implementationPlans/consent-forms.md +8 -0
- package/src/docs/versioned/next/implementationPlans/list-detail-layout.md +297 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/HANDOFF.md +211 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/AnotherWay.html +121 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/BackupCode.html +72 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Cancelled.html +87 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ContinueEmail.html +64 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/FirstVisit.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/Main.html +88 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/ManageAccounts.html +77 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/NotLinked.html +92 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/SigningIn.html +60 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactor.html +76 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/TwoFactorError.html +81 -0
- package/src/docs/versioned/next/implementationPlans/login-redesign/boards/WrongPassword.html +69 -0
- package/src/docs/versioned/next/implementationPlans/mcp-boost-parity.md +1 -1
- package/src/docs/versioned/next/plans/2026-10-23-provisional-consent.md +297 -0
- package/src/docs/versioned/next/plans/2026-10-29-fix-mcp-oauth-native-and-machine.md +68 -0
- package/src/docs/versioned/next/reference/README.md +1 -0
- package/src/docs/versioned/next/reference/account-security.md +700 -0
- package/src/docs/versioned/next/reference/admin-backend.md +3 -1
- package/src/docs/versioned/next/reference/admin-frontend.md +225 -1
- package/src/docs/versioned/next/reference/api.md +461 -35
- package/src/docs/versioned/next/reference/cli.md +1 -1
- package/src/docs/versioned/next/reference/comms.md +8 -0
- package/src/docs/versioned/next/reference/create-terreno-app.md +3 -1
- package/src/docs/versioned/next/reference/environment-variables.md +9 -3
- package/src/docs/versioned/next/reference/legacy/rtk.md +19 -3
- package/src/docs/versioned/next/reference/lifecycle-plugin.md +6 -0
- package/src/docs/versioned/next/reference/mcp-server.md +1158 -441
- package/src/docs/versioned/next/reference/syncdb.md +8 -1
- package/src/docs/versioned/next/reference/ui.md +1271 -27
- package/src/docs/versioned/next/tasks/ai-dev-loop-boost.md +28 -16
- 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)
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
- [
|
|
23
|
-
- [
|
|
24
|
-
- [
|
|
25
|
-
- [
|
|
26
|
-
- [
|
|
27
|
-
- [
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
74
|
+
Raw HTTP call against a local build (`PORT=8799 bun dist/index.js`):
|
|
62
75
|
|
|
63
|
-
|
|
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
|
-
``````
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
87
|
+
JSON-RPC methods: `tools/list`, `tools/call`, `prompts/list`, `prompts/get`, `resources/list`,
|
|
88
|
+
`resources/read`.
|
|
77
89
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
|
|
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
|
-
|
|
132
|
+
``````markdown
|
|
133
|
+
# Bootstrap AI Rules for My App
|
|
134
|
+
|
|
135
|
+
## Files to Create
|
|
89
136
|
|
|
90
|
-
|
|
137
|
+
The following AI coding assistant configuration files need to be created:
|
|
91
138
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
146
|
+
## Instructions
|
|
101
147
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
-
|
|
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
|
-
**
|
|
152
|
+
4. **Delete the stale `.cursorrules`** that `create-terreno-app` wrote. …
|
|
108
153
|
|
|
109
|
-
|
|
154
|
+
5. **Generate the editor files with rulesync:**
|
|
155
|
+
```bash
|
|
156
|
+
bunx rulesync@9 generate
|
|
157
|
+
```
|
|
158
|
+
…
|
|
159
|
+
``````
|
|
110
160
|
|
|
111
|
-
|
|
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
|
-
|
|
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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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
|
-
|
|
124
|
-
|
|
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":"
|
|
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
|
-
|
|
136
|
-
|
|
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
|
-
|
|
190
|
+
Queries: "instanceActions"
|
|
191
|
+
Docs version: next
|
|
192
|
+
Package filter: api
|
|
143
193
|
|
|
144
|
-
|
|
145
|
-
`
|
|
146
|
-
|
|
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
|
-
|
|
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
|
-
|
|
155
|
-
|
|
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
|
-
|
|
160
|
-
|
|
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
|
-
|
|
210
|
+
``````json
|
|
211
|
+
{"name":"terreno_get_component_docs","arguments":{"component":"Badge"}}
|
|
212
|
+
``````
|
|
163
213
|
|
|
164
|
-
|
|
214
|
+
``````markdown
|
|
215
|
+
# Badge
|
|
165
216
|
|
|
166
|
-
**
|
|
217
|
+
**Interface:** `BadgeProps`
|
|
167
218
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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
|
-
|
|
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
|
-
|
|
230
|
+
#### terreno_get_upgrade_guide
|
|
180
231
|
|
|
181
|
-
|
|
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
|
-
|
|
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
|
-
``````
|
|
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
|
-
|
|
245
|
+
``````markdown
|
|
246
|
+
Recorded notes in 57.13.0 → 57.14.0: 57.14.0
|
|
193
247
|
|
|
194
|
-
|
|
248
|
+
---
|
|
195
249
|
|
|
196
|
-
|
|
250
|
+
# Upgrade to 57.14.0
|
|
197
251
|
|
|
198
|
-
|
|
252
|
+
# Upgrading to 57.14.0
|
|
199
253
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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
|
-
|
|
259
|
+
#### terreno_search_update_notes
|
|
208
260
|
|
|
209
|
-
|
|
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
|
-
|
|
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
|
-
``````
|
|
272
|
+
``````json
|
|
273
|
+
{"name":"terreno_search_update_notes","arguments":{"queries":["rate limit"],"limit":2}}
|
|
274
|
+
``````
|
|
275
|
+
|
|
276
|
+
``````json
|
|
214
277
|
{
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
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
|
-
|
|
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
|
-
"
|
|
235
|
-
"
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
|
|
317
|
+
#### terreno_ask_update_help
|
|
246
318
|
|
|
247
|
-
-
|
|
248
|
-
|
|
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
|
-
|
|
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
|
-
|
|
328
|
+
``````json
|
|
329
|
+
{"name":"terreno_ask_update_help","arguments":{"question":"How do I enable rate limiting?","limit":2}}
|
|
330
|
+
``````
|
|
255
331
|
|
|
256
|
-
|
|
332
|
+
``````text
|
|
333
|
+
Question: How do I enable rate limiting?
|
|
334
|
+
Sources: bundled-upgrades
|
|
257
335
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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
|
-
|
|
290
|
-
"ownerFiltered": true,
|
|
291
|
-
"sort": "-created"
|
|
292
|
-
}
|
|
377
|
+
…
|
|
293
378
|
``````
|
|
294
379
|
|
|
295
|
-
|
|
380
|
+
#### terreno_generate_route
|
|
296
381
|
|
|
297
|
-
|
|
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
|
-
|
|
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
|
-
|
|
393
|
+
``````json
|
|
394
|
+
{"name":"terreno_generate_route","arguments":{"modelName":"Product","routePath":"/products","permissions":{"list":"authenticated"}}}
|
|
395
|
+
``````
|
|
305
396
|
|
|
306
|
-
|
|
397
|
+
``````text
|
|
398
|
+
**IMPORTANT: Write this code to a file**
|
|
307
399
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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
|
-
|
|
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
|
-
|
|
425
|
+
``````text
|
|
426
|
+
**IMPORTANT: Write this code to a file**
|
|
333
427
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
- Loading/error/empty states
|
|
428
|
+
File path: `frontend/src/screens/ProductListScreen.tsx`
|
|
429
|
+
…
|
|
430
|
+
import { useGetProductsQuery } from "@/store/openApiSdk";
|
|
338
431
|
|
|
339
|
-
|
|
432
|
+
const ProductListScreen: React.FC = () => {
|
|
433
|
+
const { data: products, isLoading, error, refetch } = useGetProductsQuery({});
|
|
434
|
+
…
|
|
435
|
+
``````
|
|
340
436
|
|
|
341
|
-
|
|
437
|
+
#### terreno_generate_form_fields
|
|
342
438
|
|
|
343
|
-
|
|
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
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
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
|
-
|
|
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
|
-
|
|
480
|
+
``````text
|
|
481
|
+
Schema Validation Results:
|
|
371
482
|
|
|
372
|
-
|
|
373
|
-
-
|
|
374
|
-
-
|
|
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
|
-
|
|
489
|
+
Suggestions:
|
|
490
|
+
…
|
|
491
|
+
``````
|
|
377
492
|
|
|
378
|
-
|
|
493
|
+
#### terreno_validate_ui_blocks
|
|
379
494
|
|
|
380
|
-
|
|
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
|
-
|
|
383
|
-
|
|
384
|
-
|
|
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
|
-
|
|
524
|
+
``````markdown
|
|
525
|
+
# Install Admin Panel
|
|
526
|
+
|
|
527
|
+
## Files to Create
|
|
389
528
|
|
|
390
|
-
-
|
|
391
|
-
-
|
|
392
|
-
-
|
|
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
|
-
|
|
534
|
+
## Setup Instructions
|
|
395
535
|
|
|
396
|
-
|
|
536
|
+
### Step 1: Add `@terreno/admin-backend` to backend
|
|
537
|
+
…
|
|
538
|
+
``````
|
|
397
539
|
|
|
398
|
-
|
|
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
|
-
``````
|
|
601
|
+
``````json
|
|
401
602
|
{
|
|
402
|
-
|
|
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
|
-
|
|
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
|
-
|
|
409
|
-
|
|
675
|
+
``````markdown
|
|
676
|
+
# Application info
|
|
410
677
|
|
|
411
|
-
|
|
678
|
+
- **projectRoot**: `…`
|
|
679
|
+
- **root package**: @terreno/root @ 1.0.0
|
|
680
|
+
- **bun.lock present**: yes
|
|
412
681
|
|
|
413
|
-
|
|
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
|
-
|
|
689
|
+
## Frontend workspace (example-frontend/)
|
|
690
|
+
…
|
|
691
|
+
- **react-native**: catalog:
|
|
416
692
|
|
|
417
|
-
|
|
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
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
754
|
+
"collection": "todos",
|
|
755
|
+
"count": 1,
|
|
756
|
+
"rows": [
|
|
757
|
+
{
|
|
758
|
+
"_id": false,
|
|
759
|
+
"count": 2
|
|
760
|
+
}
|
|
761
|
+
]
|
|
424
762
|
}
|
|
425
763
|
``````
|
|
426
764
|
|
|
427
|
-
|
|
765
|
+
``````json
|
|
766
|
+
{"name":"database_query","arguments":{"collection":"todos","operation":"aggregate","pipeline":[{"$out":"x"}]}}
|
|
767
|
+
``````
|
|
428
768
|
|
|
429
|
-
|
|
769
|
+
``````text
|
|
770
|
+
Pipeline rejected: `$out`, `$merge`, and `$function` are not allowed.
|
|
771
|
+
``````
|
|
430
772
|
|
|
431
|
-
|
|
773
|
+
``````json
|
|
774
|
+
{"name":"database_query","arguments":{"collection":"todos","operation":"find","filter":{"$where":"true"}}}
|
|
775
|
+
``````
|
|
432
776
|
|
|
433
|
-
|
|
777
|
+
``````text
|
|
778
|
+
Filter rejected: `$where`, `$function`, and `$accumulator` are not allowed.
|
|
779
|
+
``````
|
|
434
780
|
|
|
435
|
-
|
|
781
|
+
#### read_logs
|
|
436
782
|
|
|
437
|
-
|
|
783
|
+
Merge log entries from up to four sources, sort them by timestamp, and return the newest.
|
|
438
784
|
|
|
439
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
838
|
+
`app` source output, captured against a test device that speaks Metro's device protocol (not a real
|
|
839
|
+
Hermes runtime):
|
|
448
840
|
|
|
449
|
-
|
|
450
|
-
|
|
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
|
-
|
|
855
|
+
#### last_error
|
|
453
856
|
|
|
454
|
-
|
|
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
|
-
|
|
861
|
+
| Parameter | Type | Required | Description |
|
|
862
|
+
|-----------|------|----------|-------------|
|
|
863
|
+
| `sources` | string[] | no | Any of `backend`, `browser`, `metro`, `app`. Default all four. |
|
|
457
864
|
|
|
458
|
-
|
|
865
|
+
``````json
|
|
866
|
+
{"name":"last_error","arguments":{"sources":["browser"]}}
|
|
867
|
+
``````
|
|
459
868
|
|
|
460
|
-
|
|
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
|
-
|
|
873
|
+
With no error-level entry:
|
|
463
874
|
|
|
464
|
-
|
|
465
|
-
-
|
|
466
|
-
|
|
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
|
-
|
|
879
|
+
#### get_client_state
|
|
469
880
|
|
|
470
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
-
|
|
908
|
+
``````json
|
|
909
|
+
{"name":"get_client_state","arguments":{}}
|
|
910
|
+
``````
|
|
491
911
|
|
|
492
|
-
|
|
493
|
-
|
|
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
|
-
|
|
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
|
-
|
|
927
|
+
#### get_rtk_state
|
|
501
928
|
|
|
502
|
-
|
|
929
|
+
Deprecated alias of `get_client_state`, kept for one release. Same schema, same handler, same
|
|
930
|
+
`{layer, rtk?, syncdb?}` response.
|
|
503
931
|
|
|
504
|
-
|
|
505
|
-
- `type` (string) — "display" | "interactive" | "form" | "layout"
|
|
506
|
-
- `description` (string) — Component purpose
|
|
932
|
+
#### get_syncdb_state
|
|
507
933
|
|
|
508
|
-
|
|
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
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
946
|
+
``````json
|
|
947
|
+
{"name":"get_syncdb_state","arguments":{"collection":"todos"}}
|
|
948
|
+
``````
|
|
517
949
|
|
|
518
|
-
|
|
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
|
-
|
|
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
|
-
|
|
523
|
-
- `modelName` (string) — Model being created/edited
|
|
524
|
-
- `fields` (string) — Comma-separated: `title:text,price:number,active:boolean`
|
|
958
|
+
#### syncdb_snapshot
|
|
525
959
|
|
|
526
|
-
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
973
|
+
``````json
|
|
974
|
+
{"name":"syncdb_snapshot","arguments":{"action":"list"}}
|
|
975
|
+
``````
|
|
536
976
|
|
|
537
|
-
|
|
977
|
+
``````json
|
|
978
|
+
[]
|
|
979
|
+
``````
|
|
538
980
|
|
|
539
|
-
|
|
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
|
-
|
|
542
|
-
|
|
1002
|
+
``````json
|
|
1003
|
+
{"name":"syncdb_action","arguments":{"action":"flush"}}
|
|
1004
|
+
``````
|
|
543
1005
|
|
|
544
|
-
|
|
1006
|
+
Without the opt-in:
|
|
545
1007
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
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
|
-
|
|
1012
|
+
#### evaluate
|
|
555
1013
|
|
|
556
|
-
|
|
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
|
-
|
|
1018
|
+
| Parameter | Type | Required | Description |
|
|
1019
|
+
|-----------|------|----------|-------------|
|
|
1020
|
+
| `code` | string | yes | Expression to evaluate |
|
|
559
1021
|
|
|
560
|
-
|
|
1022
|
+
``````json
|
|
1023
|
+
{"name":"evaluate","arguments":{"code":"1 + 1"}}
|
|
1024
|
+
``````
|
|
561
1025
|
|
|
562
|
-
|
|
1026
|
+
Without the opt-in (a successful call needs a Hermes app and is not shown):
|
|
563
1027
|
|
|
564
|
-
|
|
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
|
-
|
|
1032
|
+
#### navigate
|
|
567
1033
|
|
|
568
|
-
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
-
|
|
573
|
-
- Testing practices
|
|
574
|
-
- Logging guidelines
|
|
1038
|
+
| Parameter | Type | Required | Description |
|
|
1039
|
+
|-----------|------|----------|-------------|
|
|
1040
|
+
| `path` | string | yes | expo-router path |
|
|
575
1041
|
|
|
576
|
-
|
|
1042
|
+
``````json
|
|
1043
|
+
{"name":"navigate","arguments":{"path":"/settings"}}
|
|
1044
|
+
``````
|
|
577
1045
|
|
|
578
|
-
|
|
1046
|
+
Without the opt-in (a successful call needs a Hermes app and is not shown):
|
|
579
1047
|
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
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
|
-
|
|
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
|
-
``````
|
|
593
|
-
|
|
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
|
-
|
|
1083
|
+
``````json
|
|
1084
|
+
{
|
|
1085
|
+
"action": "open",
|
|
1086
|
+
"ok": true,
|
|
1087
|
+
"title": "",
|
|
1088
|
+
"url": "http://localhost:8082/"
|
|
1089
|
+
}
|
|
1090
|
+
``````
|
|
597
1091
|
|
|
598
|
-
``````
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
|
|
603
|
-
|
|
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
|
-
|
|
606
|
-
|
|
607
|
-
|
|
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
|
-
|
|
610
|
-
|
|
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
|
-
|
|
613
|
-
bun run start
|
|
1308
|
+
## Development
|
|
614
1309
|
|
|
615
|
-
|
|
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
|
-
|
|
625
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
636
|
-
bun
|
|
1346
|
+
# Local server over stdio
|
|
1347
|
+
bunx @modelcontextprotocol/inspector bun ./mcp-server/dist/local/index.js
|
|
637
1348
|
|
|
638
|
-
#
|
|
639
|
-
|
|
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
|
|
648
|
-
│ ├──
|
|
649
|
-
│ ├──
|
|
650
|
-
│ ├──
|
|
651
|
-
│
|
|
652
|
-
├──
|
|
653
|
-
|
|
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
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
-
|
|
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
|