@terreno/mcp 58.0.2 → 58.0.4
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 +31 -0
- package/README.md +6 -4
- package/dist/docs/templates/bootstrap/frontend/app/_layout.template.tsx +2 -0
- package/dist/docs/upgrades/58.0.3.md +28 -0
- package/dist/docs/versioned/next/explanation/ai-development-loop.md +10 -7
- package/dist/docs/versioned/next/explanation/test-isolation.md +2 -1
- package/dist/docs/versioned/next/explanation/test-sharding.md +113 -0
- package/dist/docs/versioned/next/how-to/README.md +1 -0
- package/dist/docs/versioned/next/how-to/circleci.md +12 -4
- package/dist/docs/versioned/next/how-to/debug-with-mcp.md +10 -1
- package/dist/docs/versioned/next/how-to/fix-code-scanning-alerts.md +43 -0
- package/dist/docs/versioned/next/how-to/run-tests-locally.md +5 -0
- package/dist/docs/versioned/next/how-to/set-up-terreno-mcp.md +3 -1
- package/dist/docs/versioned/next/implementationPlans/mcp-agent-debugger-gaps.md +257 -0
- package/dist/docs/versioned/next/reference/environment-variables.md +2 -0
- package/dist/docs/versioned/next/reference/mcp-server.md +12 -0
- package/dist/docs/versioned/next/reference/test.md +4 -1
- package/dist/docs/versioned/next/tasks/mcp-agent-debugger-gaps.md +151 -0
- package/dist/local/localTools.d.ts.map +1 -1
- package/dist/local/localTools.js +94 -1
- package/dist/local/localTools.js.map +1 -1
- package/dist/local/metro/metroDevSession.d.ts +1 -0
- package/dist/local/metro/metroDevSession.d.ts.map +1 -1
- package/dist/local/metro/metroDevSession.js +104 -1
- package/dist/local/metro/metroDevSession.js.map +1 -1
- package/dist/local/native/nativeLogs.d.ts +12 -0
- package/dist/local/native/nativeLogs.d.ts.map +1 -0
- package/dist/local/native/nativeLogs.js +103 -0
- package/dist/local/native/nativeLogs.js.map +1 -0
- package/dist/local/tools/clientDev.d.ts +22 -0
- package/dist/local/tools/clientDev.d.ts.map +1 -0
- package/dist/local/tools/clientDev.isolated.d.ts +2 -0
- package/dist/local/tools/clientDev.isolated.d.ts.map +1 -0
- package/dist/local/tools/clientDev.isolated.js +55 -0
- package/dist/local/tools/clientDev.isolated.js.map +1 -0
- package/dist/local/tools/clientDev.js +224 -0
- package/dist/local/tools/clientDev.js.map +1 -0
- package/dist/local/tools/readLogs.d.ts.map +1 -1
- package/dist/local/tools/readLogs.js +22 -3
- package/dist/local/tools/readLogs.js.map +1 -1
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +3 -1
- package/dist/tools.js.map +1 -1
- package/package.json +7 -7
- package/src/docs/templates/bootstrap/frontend/app/_layout.template.tsx +2 -0
- package/src/docs/upgrades/58.0.3.md +28 -0
- package/src/docs/versioned/next/explanation/ai-development-loop.md +10 -7
- package/src/docs/versioned/next/explanation/test-isolation.md +2 -1
- package/src/docs/versioned/next/explanation/test-sharding.md +113 -0
- package/src/docs/versioned/next/how-to/README.md +1 -0
- package/src/docs/versioned/next/how-to/circleci.md +12 -4
- package/src/docs/versioned/next/how-to/debug-with-mcp.md +10 -1
- package/src/docs/versioned/next/how-to/fix-code-scanning-alerts.md +43 -0
- package/src/docs/versioned/next/how-to/run-tests-locally.md +5 -0
- package/src/docs/versioned/next/how-to/set-up-terreno-mcp.md +3 -1
- package/src/docs/versioned/next/implementationPlans/mcp-agent-debugger-gaps.md +257 -0
- package/src/docs/versioned/next/reference/environment-variables.md +2 -0
- package/src/docs/versioned/next/reference/mcp-server.md +12 -0
- package/src/docs/versioned/next/reference/test.md +4 -1
- package/src/docs/versioned/next/tasks/mcp-agent-debugger-gaps.md +151 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# Test sharding in Bun suites
|
|
2
|
+
|
|
3
|
+
Every package runs its tests through `scripts/test-shards.ts`. The runner starts several
|
|
4
|
+
`bun test --shard=<i>/<n>` processes at once, one per shard, and fails when any of them
|
|
5
|
+
fails. Bun 1.4.4 or newer is required: older versions have no `--shard`.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
cd api
|
|
9
|
+
bun run test:ci # shards chosen from the machine's cores
|
|
10
|
+
TERRENO_TEST_SHARDS=1 bun run test:ci # one unsplit process
|
|
11
|
+
TERRENO_TEST_SHARDS=3 bun run test:ci # exactly three shards
|
|
12
|
+
bun test ./src/auth.test.ts # one file, no runner
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Why shards and not `bun test --parallel`
|
|
16
|
+
|
|
17
|
+
A shard is an ordinary `bun test` process. It loads the preload once, keeps one module
|
|
18
|
+
registry, and starts one in-memory Mongo, exactly as an unsplit run does. A test file sees
|
|
19
|
+
the same world either way. Only the set of files that share its process changes.
|
|
20
|
+
|
|
21
|
+
`bun test --parallel` gives each file a fresh global. That re-runs the preload's
|
|
22
|
+
`beforeAll` and `afterAll` for every file, so a backend package starts and stops Mongo once
|
|
23
|
+
per file and reloads every module. Measured on `@terreno/api` (169 files, 4 cores): 648 s
|
|
24
|
+
unsplit, 459 s with `--parallel`, 408 s with two shards.
|
|
25
|
+
|
|
26
|
+
## How many shards
|
|
27
|
+
|
|
28
|
+
| Source | Shards |
|
|
29
|
+
| --- | --- |
|
|
30
|
+
| `TERRENO_TEST_SHARDS` set | That number |
|
|
31
|
+
| Not set | Half the cores, at least 1, at most 4 |
|
|
32
|
+
|
|
33
|
+
A backend shard keeps a Bun process and a `mongod` busy, about two cores. More shards than
|
|
34
|
+
half the cores slows every shard down and makes timing-sensitive tests fail.
|
|
35
|
+
|
|
36
|
+
CircleCI jobs set `TERRENO_TEST_SHARDS` themselves, because a container reports the host's
|
|
37
|
+
cores and not its own CPU limit. Medium jobs (2 vCPU) run 1 shard. Backend jobs run 1 shard
|
|
38
|
+
even on `large`: CircleCI gives a job one sidecar `mongod`, and every shard connects to the
|
|
39
|
+
same `mongodb://127.0.0.1/terreno` database, so concurrent shards clear each other's
|
|
40
|
+
collections mid-test. `api-ci` ran 2 shards until that broke its tests.
|
|
41
|
+
|
|
42
|
+
Root `bun run test` and `bun run test:agent` set `TERRENO_TEST_SHARDS=1`: they already run
|
|
43
|
+
every package at the same time.
|
|
44
|
+
|
|
45
|
+
## Measured speedup
|
|
46
|
+
|
|
47
|
+
One 4-core machine, every package's `test:ci` run one after another:
|
|
48
|
+
|
|
49
|
+
| Package | Unsplit | 2 shards |
|
|
50
|
+
| --- | --- | --- |
|
|
51
|
+
| `api` | 648 s | 408 s |
|
|
52
|
+
| `ai` | 314 s | 187 s |
|
|
53
|
+
| `example-backend` | 253 s | 135 s |
|
|
54
|
+
| `admin-frontend` | 142 s | 75 s |
|
|
55
|
+
| `admin-backend` | 124 s | 83 s |
|
|
56
|
+
| `ui` | 95 s | 65 s |
|
|
57
|
+
| All 21 packages | 1849 s | 1163 s |
|
|
58
|
+
|
|
59
|
+
Packages that finish in a few seconds gain nothing and lose nothing.
|
|
60
|
+
|
|
61
|
+
## Isolated files
|
|
62
|
+
|
|
63
|
+
`ai` and `admin-frontend` pass `--isolated` to the runner. After the shards pass, each
|
|
64
|
+
`src/isolated/*.isolated.ts(x)` file runs in a process of its own, as many at a time as there
|
|
65
|
+
are shards. See [Test isolation](test-isolation.md).
|
|
66
|
+
|
|
67
|
+
## Coverage
|
|
68
|
+
|
|
69
|
+
`bun run test:coverage` (`scripts/check-coverage.ts`) uses the same shards. Each shard writes
|
|
70
|
+
its own LCOV report. The script merges them, writes `coverage/lcov.info`, and gates the
|
|
71
|
+
merged numbers at 95%.
|
|
72
|
+
|
|
73
|
+
Line coverage merges exactly: a line counts when any shard ran it. The sharded line figure
|
|
74
|
+
equals the unsplit one.
|
|
75
|
+
|
|
76
|
+
Function coverage cannot merge exactly. Bun's LCOV has no per-function records, only a found
|
|
77
|
+
and hit count per file, and Bun counts a nested function only after its parent ran, so shards
|
|
78
|
+
disagree on how many functions a file has. The script takes the largest found count and the
|
|
79
|
+
largest hit count for each file. That estimate usually reads low, because functions hit in
|
|
80
|
+
different shards are not added together. It can read high when one shard runs most of a file
|
|
81
|
+
and another shard finds nested functions that nothing calls.
|
|
82
|
+
|
|
83
|
+
A sharded run that misses 95% is run again as one unsplit process, and the gate uses that
|
|
84
|
+
exact result. A package whose function coverage is near 95% therefore pays for a second pass;
|
|
85
|
+
set `TERRENO_TEST_SHARDS=1` to skip the first. CircleCI gates every package unsplit except
|
|
86
|
+
`api` and `ai`, which run 2 shards.
|
|
87
|
+
|
|
88
|
+
While shards run, their output is held and printed one shard at a time. The runner prints a
|
|
89
|
+
`[shards] still running` line once a minute so CI does not see a silent job.
|
|
90
|
+
|
|
91
|
+
## When a test fails only in a shard
|
|
92
|
+
|
|
93
|
+
Sharding changes which files share a process. A test that passed only because an earlier
|
|
94
|
+
file left something behind now fails. Run the file alone to confirm:
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
bun test ./src/models/configuration.test.ts
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Fix the file so it passes alone. The usual causes:
|
|
101
|
+
|
|
102
|
+
1. **A missing import.** The test reads state that another file's import registered. Import
|
|
103
|
+
the module in the test.
|
|
104
|
+
2. **A module mock from another file.** A snapshot or assertion matches another file's
|
|
105
|
+
`mock.module` shape. Register the mock once in the package preload.
|
|
106
|
+
3. **A timing budget.** Shards load the machine. Freeze the clock with `setSystemTime` or give
|
|
107
|
+
a slow test its own timeout; do not rely on wall-clock windows.
|
|
108
|
+
|
|
109
|
+
To reproduce the exact process a shard ran, pass the shard to Bun directly:
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
bun test --shard=2/3
|
|
113
|
+
```
|
|
@@ -16,6 +16,7 @@ Problem-oriented, practical steps. Use these when you know what you want to do.
|
|
|
16
16
|
|
|
17
17
|
- [Create a Terreno app](create-a-terreno-app.md) — Scaffold a full-stack app with `create-terreno-app` (Bun or npm)
|
|
18
18
|
- [Update dependencies](update-dependencies.md) — Daily rolling PR, exercise test, Expo fingerprint freeze
|
|
19
|
+
- [Fix code scanning alerts](fix-code-scanning-alerts.md) — One PR per sweep; fixes close on rescan, reviewed false positives are dismissed after merge
|
|
19
20
|
- [CircleCI](circleci.md) — Dual-run CI on CircleCI (package CI / policy / e2e; deploys later)
|
|
20
21
|
- [Release automation](release-automation.md) — Let agents merge release notes and roadmap updates; publish only merged notes
|
|
21
22
|
- [Set up tests in a Terreno app](set-up-app-tests.md) — Bun backend and frontend tests, Playwright web E2E, Maestro on iOS/Android
|
|
@@ -49,10 +49,18 @@ list pipelines on the slug above.
|
|
|
49
49
|
|
|
50
50
|
## Bun version is pinned
|
|
51
51
|
|
|
52
|
-
`install_bun_and_deps` pins Bun with `node/install-bun` `version
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
`bun run test:coverage` in each package first
|
|
52
|
+
`install_bun_and_deps` pins Bun with `node/install-bun` `version`, currently 1.4.4, the
|
|
53
|
+
same version as the local toolchain and the GitHub workflows. Package tests run through
|
|
54
|
+
`bun test --shard`, which needs 1.4.4 or newer. Bump the pin together with the local
|
|
55
|
+
toolchain and re-run `bun run test:coverage` in each package first: coverage numbers move
|
|
56
|
+
between Bun releases.
|
|
57
|
+
|
|
58
|
+
## Test shards
|
|
59
|
+
|
|
60
|
+
Each executor sets `TERRENO_TEST_SHARDS`, the number of concurrent `bun test --shard`
|
|
61
|
+
processes a package's tests use. Medium executors set 1. `api-ci`, the longest test
|
|
62
|
+
job, overrides it to 2 and runs on `large`. See
|
|
63
|
+
[Test sharding](../explanation/test-sharding.md).
|
|
56
64
|
|
|
57
65
|
## Bun install is uncached
|
|
58
66
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Debug a Terreno app with MCP
|
|
2
2
|
|
|
3
|
+
**This is the standard agent workflow** for finding and fixing issues on Terreno apps: run the
|
|
4
|
+
dev stack, call **`terreno-local`** MCP tools to observe and drive the app, edit code, then
|
|
5
|
+
observe again. Installable skill: **`debug-with-mcp`**. SyncDB-only depth: **`debug-syncdb-with-mcp`**.
|
|
6
|
+
|
|
3
7
|
Use the local MCP server (`terreno-mcp-local`) to go from a symptom to a verified fix without
|
|
4
8
|
pasting logs into the chat. This guide follows one real failure from start to finish, then lists
|
|
5
9
|
what each tool does. Every tool output below was copied unchanged from a real run against
|
|
@@ -12,6 +16,11 @@ what each tool does. Every tool output below was copied unchanged from a real ru
|
|
|
12
16
|
| API returns 500 | `last_error` with `sources: ["backend"]`. The stack is inside `message`. | `read_logs` with `sources: ["backend"]` and `since` to see the requests around it. |
|
|
13
17
|
| API returns 403 (or another 4xx) | `read_logs` with `sources: ["backend"]` and `level: "warn"`. Every `APIError` under 500 is logged at `warn` as `ForbiddenError(403): <title> — <detail>`, so `last_error` does not show it. | `database_query` to compare the document's `ownerId` with the user in the log line. |
|
|
14
18
|
| App crashes on a screen | Web: `last_error` with `sources: ["browser"]`. An uncaught error carries a separate `stack` field. A render error caught by an error boundary has no `stack`; its `message` names the component instead (see [A screen crashes on web](#a-screen-crashes-on-web)). Native: `last_error` with `sources: ["app"]`. | Native: `navigate` to the screen and call `last_error` again. Web: the `browser` tool. |
|
|
19
|
+
| Wrong API URL or status on device | `read_logs` with `sources: ["network"]` after reproducing the tap. | `database_query` or backend logs to compare server truth. |
|
|
20
|
+
| Auth/session oddities (token in storage) | `read_local_storage` with `prefix: "persist:"` or known key names. | `write_local_storage` only with `TERRENO_MCP_EVAL=1` to fix dev keys; prefer fixing app code for prod. |
|
|
21
|
+
| Native-only crash (no Hermes console) | `read_logs` with `sources: ["native"]` (needs `adb` or iOS Simulator). | `last_error` with `sources: ["app"]` when Hermes is attached. |
|
|
22
|
+
| Need a one-off dev action in the app | `list_dev_commands`, then `invoke_dev_command` with `TERRENO_MCP_EVAL=1`. | Register handlers with `registerTerrenoDevCommand` in app code. |
|
|
23
|
+
| Redux state wrong but SyncDB OK | `get_client_state` or `dispatch_client_action` with a known action type. | `syncdb_action` only for collection CRUD, not Redux. |
|
|
15
24
|
| Data looks wrong | `database_query` to read what the server stored. | `get_client_state` (RTK cache) or `get_syncdb_state` (SyncDB) to compare the client's copy. |
|
|
16
25
|
| Screen renders empty | `get_client_state` or `get_syncdb_state` on native. On web, `browser` with `snapshot`. | `read_logs` with `sources: ["backend"]` to check that the list request ran and returned 200. |
|
|
17
26
|
| Bundler fails | The terminal that runs `expo start`. The `metro` source is always empty under Expo CLI 57: the `/events` socket connects but Expo never sends on it. | On native, `read_logs` with `sources: ["app"]` once the app loads again. |
|
|
@@ -161,7 +170,7 @@ On web this returns `layer: "none"`:
|
|
|
161
170
|
```json
|
|
162
171
|
{
|
|
163
172
|
"connection": "CDP: Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?); Metro events: not connected",
|
|
164
|
-
"hint": "No client data layer found. For
|
|
173
|
+
"hint": "No client data layer found. For SyncDB, create the client with `createSyncDb({debug: true})`. For custom client actions, call `registerTerrenoDevClientDispatch` from `@terreno/syncdb` in __DEV__. Keep the app running; when the MCP process cannot see the app heap, start Metro so CDP can reach Hermes (see `connection`).",
|
|
165
174
|
"layer": "none",
|
|
166
175
|
"reasons": {
|
|
167
176
|
"rtk": "Error: No debuggable Hermes/React Native target with webSocketDebuggerUrl (is the app running?)",
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# Fix code scanning alerts
|
|
2
|
+
|
|
3
|
+
Run `/fix-code-scanning-alerts` to clear every open CodeQL alert on one PR. When that PR merges, each alert closes: fixes close when CodeQL rescans `master`, and reviewed false positives are dismissed by a workflow.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
gh api "repos/{owner}/{repo}/code-scanning/alerts?state=open&per_page=100" --paginate \
|
|
7
|
+
| jq -s 'add | group_by(.rule.id) | map({rule: .[0].rule.id, count: length})'
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## How alerts close
|
|
11
|
+
|
|
12
|
+
| Lane | In the PR | After merge |
|
|
13
|
+
| --- | --- | --- |
|
|
14
|
+
| Fix | Code change and a regression test | The CodeQL push scan on `master` no longer reports it, so it closes as **fixed** |
|
|
15
|
+
| Dismiss | An entry in `.github/code-scanning/dismissals.json` | `code-scanning-dismissals.yml` dismisses it with the entry's reason and comment |
|
|
16
|
+
|
|
17
|
+
Dismissals wait for the merge so a reviewer approves each one, and the Security tab never shows an alert closed by an unmerged branch.
|
|
18
|
+
|
|
19
|
+
## Pieces
|
|
20
|
+
|
|
21
|
+
- `.github/codeql/codeql-config.yml`: CodeQL configuration. Test files are ignored, so their alerts close on the next `master` scan.
|
|
22
|
+
- `.github/workflows/codeql-analysis.yml`: also runs on pull requests (diff-informed: only changed lines) and on manual dispatch. Dispatch it on a branch for a full scan that proves each fix clears its alert before merge.
|
|
23
|
+
- `.github/code-scanning/dismissals.json`: the dismissal ledger. Each entry has `alert`, `rule`, `path`, `reason` (`false positive`, `won't fix`, or `used in tests`), and a `comment` of 280 characters or fewer.
|
|
24
|
+
- `.github/workflows/code-scanning-dismissals.yml`: runs on `master` when the ledger changes, and on manual dispatch.
|
|
25
|
+
- `scripts/ci/codeScanning/applyDismissals.ts`: applies the ledger. It dismisses an entry only when the live alert is still open and its rule and path still match. A mismatch fails the job.
|
|
26
|
+
|
|
27
|
+
## Check a ledger locally
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
bun test scripts/ci/codeScanning/
|
|
31
|
+
GITHUB_TOKEN="$(gh auth token)" GITHUB_REPOSITORY=TerrenoLabs/terreno \
|
|
32
|
+
bun scripts/ci/codeScanning/applyDismissals.ts --dry-run
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The dry run must report `mismatched 0`.
|
|
36
|
+
|
|
37
|
+
## Rate limiting alerts
|
|
38
|
+
|
|
39
|
+
`js/missing-rate-limiting` flags every Express route because CodeQL only recognizes known limiter libraries. `TerrenoApp` mounts its own limiter before all plugins (see [Rate limiting](rate-limiting.md)), so these alerts go in the ledger as false positives. A route mounted outside `TerrenoApp` has no limiter and needs a real fix.
|
|
40
|
+
|
|
41
|
+
## Dependabot
|
|
42
|
+
|
|
43
|
+
Dependabot opens **security PRs only**. Routine upgrades go through [Update dependencies](update-dependencies.md). Every workspace package directory is listed in `.github/dependabot.yml`. Add a new package's directory there when you create it.
|
|
@@ -52,6 +52,11 @@ bun run test # every workspace `test:ci`
|
|
|
52
52
|
bun run test:agent # same suites; passing cases suppressed
|
|
53
53
|
```
|
|
54
54
|
|
|
55
|
+
Each package's `test`, `test:ci`, `test:agent`, and `test:coverage` scripts split the suite
|
|
56
|
+
into concurrent `bun test --shard` processes, half the cores by default. Set
|
|
57
|
+
`TERRENO_TEST_SHARDS=1` for one unsplit process. See
|
|
58
|
+
[Test sharding](../explanation/test-sharding.md).
|
|
59
|
+
|
|
55
60
|
Closest package during a red/green cycle:
|
|
56
61
|
|
|
57
62
|
```bash
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
# Set up Terreno MCP
|
|
2
2
|
|
|
3
3
|
Configure both Terreno servers: the hosted server supplies documentation and generators; the local
|
|
4
|
-
stdio server inspects the app, database, logs, Metro, and
|
|
4
|
+
stdio server inspects the app, database, logs, Metro, SyncDB state, web and native navigation.
|
|
5
|
+
After setup, invoke **`debug-with-mcp`** (and when applicable **`debug-syncdb-with-mcp`**) for the
|
|
6
|
+
standard diagnostic workflow.
|
|
5
7
|
- **`terreno`** (hosted, Streamable HTTP) supplies documentation search and code generators.
|
|
6
8
|
- **`terreno-local`** (stdio, runs in your repository) reads the app's packages, database, logs,
|
|
7
9
|
Metro, and client state.
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
# Implementation Plan: MCP Agent Debugger Gaps (Network, Dispatch, Storage, Native Logs, Dev Commands)
|
|
2
|
+
|
|
3
|
+
**Status:** Complete — Pick landed 2026-11-06
|
|
4
|
+
**Parent IP:** [mcp-boost-parity.md](mcp-boost-parity.md) (extends the local stdio companion; does not repeat hosted docs/codegen work)
|
|
5
|
+
**Roadmap:** Area=`mcp`, Target=`Next`, Impact=`Feature`
|
|
6
|
+
**Created:** 2026-11-05
|
|
7
|
+
|
|
8
|
+
## Goal
|
|
9
|
+
|
|
10
|
+
Close the remaining agent-debugging gaps compared to integrated RN debuggers (for example
|
|
11
|
+
Xerlock): **network traffic**, **structured Redux dispatch**, **local key-value storage
|
|
12
|
+
read/write**, **native platform logs**, and **app-registered dev commands**. Agents already get
|
|
13
|
+
merged console/backend logs, Mongo, SyncDB/RTK state, navigation, and web driving via
|
|
14
|
+
`terreno-mcp-local`; this plan adds the missing observe/act surfaces without building a
|
|
15
|
+
human debugger UI.
|
|
16
|
+
|
|
17
|
+
## Non-Goals
|
|
18
|
+
|
|
19
|
+
- On-device **SQLite** inspection outside SyncDB/TinyBase (partial overlap with
|
|
20
|
+
`get_syncdb_state`; raw `expo-sqlite` is out of scope).
|
|
21
|
+
- **Push notification** simulation, **network throttle**, and **React component tree /
|
|
22
|
+
profiler** MCP tools (human DevTools + screenshots + `get_client_state` remain the path).
|
|
23
|
+
- **Zustand** as a first-class framework integration (apps may expose Zustand via
|
|
24
|
+
`registerTerrenoDevCommand` only).
|
|
25
|
+
- Taking **`metro-mcp`** as a dependency (implement on the existing CDP session in
|
|
26
|
+
`metroDevSession.ts`).
|
|
27
|
+
- New **hosted** (`terreno-mcp`) tools — all work is **`terreno-mcp-local`** plus small
|
|
28
|
+
**`@terreno/syncdb`** / bootstrap dev hooks.
|
|
29
|
+
|
|
30
|
+
## Decisions
|
|
31
|
+
|
|
32
|
+
| # | Question | Decision |
|
|
33
|
+
|---|----------|----------|
|
|
34
|
+
| D1 | Separate `read_network` tool vs extend `read_logs`? | **Extend `read_logs`** with source `network` (chronological merge with other sources when omitted). |
|
|
35
|
+
| D2 | Depend on `metro-mcp` vs in-house? | **In-house** on `mcp-server/src/local/metro/metroDevSession.ts` (same Hermes CDP connection as `app` / `evaluate`). |
|
|
36
|
+
| D3 | Writable local storage in v1? | **Yes** — `write_local_storage` requires `TERRENO_MCP_EVAL=1`; reads are always allowed. |
|
|
37
|
+
| D4 | Build order | **Network → dispatch + storage → native logs → dev commands → docs**. |
|
|
38
|
+
| D5 | React tree snapshot | **Defer** — not in this plan. |
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
### Tool surface (after this plan)
|
|
43
|
+
|
|
44
|
+
| Tool | Server | Purpose |
|
|
45
|
+
|------|--------|---------|
|
|
46
|
+
| `read_logs` | local | Existing sources `backend`, `browser`, `metro`, `app`; **add** `network`, `native`. |
|
|
47
|
+
| `last_error` | local | Unchanged; may surface errors from new sources when level is `error`. |
|
|
48
|
+
| `dispatch_client_action` | local | Dispatch a Redux action on `globalThis.__TERRENO_STORE__` via CDP (EVAL-gated). |
|
|
49
|
+
| `read_local_storage` | local | Read AsyncStorage keys (and opt-in SecureStore key names) via dev global adapter. |
|
|
50
|
+
| `write_local_storage` | local | Write one key (EVAL-gated); never writes SecureStore unless app registers an explicit allowlist entry. |
|
|
51
|
+
| `list_dev_commands` | local | List `{name, description, inputSchema}` from `globalThis.__TERRENO_DEV_COMMANDS__`. |
|
|
52
|
+
| `invoke_dev_command` | local | Run a registered handler by name (EVAL-gated). |
|
|
53
|
+
|
|
54
|
+
Existing tools (`get_client_state`, `syncdb_*`, `evaluate`, `navigate`, `browser`, …) are
|
|
55
|
+
unchanged.
|
|
56
|
+
|
|
57
|
+
### CDP network capture (D1, D2)
|
|
58
|
+
|
|
59
|
+
Extend `metroDevSession.ts`:
|
|
60
|
+
|
|
61
|
+
1. After attaching to the debug target, enable CDP domains **`Runtime`** (existing) and
|
|
62
|
+
**`Network`**.
|
|
63
|
+
2. Maintain a ring buffer (same cap as console, default 2000) of normalized entries:
|
|
64
|
+
- `timestamp`, `method`, `url`, `status`, `mimeType`, `durationMs` when available
|
|
65
|
+
- Request/response **header maps** with **`Authorization`**, **`Cookie`**, and
|
|
66
|
+
**`set-auth-token`** redacted
|
|
67
|
+
- Optional truncated **response body** (text only, max 4 KiB per entry; skip binary)
|
|
68
|
+
3. `read_logs` with `sources: ["network"]` (or default merge) maps the ring into the same
|
|
69
|
+
JSON shape as other sources, with `source: "network"`.
|
|
70
|
+
4. `connection` metadata reuses the existing CDP status block; document that enabling
|
|
71
|
+
Network shares the single Hermes debugger slot with DevTools contention rules already
|
|
72
|
+
documented for `app`.
|
|
73
|
+
|
|
74
|
+
No separate MCP tool name.
|
|
75
|
+
|
|
76
|
+
### Redux dispatch (D3, D4 phase 2)
|
|
77
|
+
|
|
78
|
+
`dispatch_client_action`:
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
// Input (strict JSON schema)
|
|
82
|
+
{
|
|
83
|
+
type: string; // required Redux action type
|
|
84
|
+
payload?: unknown; // optional JSON-serializable payload
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Implementation:
|
|
89
|
+
|
|
90
|
+
1. Refuse unless `TERRENO_MCP_EVAL=1` (same posture as `evaluate` / `navigate`).
|
|
91
|
+
2. Prefer in-process read of `globalThis.__TERRENO_STORE__` when the MCP process shares the
|
|
92
|
+
app heap (tests); otherwise CDP `Runtime.evaluate` with a fixed snippet that calls
|
|
93
|
+
`globalThis.__TERRENO_STORE__?.dispatch({type, payload})` and returns `{ok, error?}`.
|
|
94
|
+
3. Validate `type` is a non-empty string; reject non-JSON-serializable payloads before evaluate.
|
|
95
|
+
4. Tool description tells agents to use `get_client_state` first, then dispatch, then
|
|
96
|
+
`read_logs` / `get_client_state` again.
|
|
97
|
+
|
|
98
|
+
Requires `registerTerrenoDevStore(store)` in consumer apps (already in bootstrap /
|
|
99
|
+
`create-terreno-app`).
|
|
100
|
+
|
|
101
|
+
### Local storage (D3, D4 phase 2)
|
|
102
|
+
|
|
103
|
+
Dev-only adapter on the app (new **`@terreno/syncdb`** export, wired in bootstrap template and
|
|
104
|
+
`example-frontend`):
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
registerTerrenoDevStorage({
|
|
108
|
+
asyncStorage: true, // default: wire AsyncStorage getAllKeys/getItem/setItem
|
|
109
|
+
secureStoreKeyAllowlist?: string[]; // optional SecureStore keys readable/writable via MCP
|
|
110
|
+
});
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Sets `globalThis.__TERRENO_DEV_STORAGE__` with `{listKeys, get, set}` in `__DEV__` only.
|
|
114
|
+
|
|
115
|
+
MCP tools:
|
|
116
|
+
|
|
117
|
+
- **`read_local_storage`**: `{keys?: string[], prefix?: string}` — returns key/value pairs;
|
|
118
|
+
omit keys to return all keys from `listKeys()` (cap 200 keys, 8 KiB per value in output).
|
|
119
|
+
- **`write_local_storage`**: `{key, value}` — EVAL-gated; rejects keys not allowed (SecureStore
|
|
120
|
+
keys not on allowlist).
|
|
121
|
+
|
|
122
|
+
MMKV and custom stores: apps register via `registerTerrenoDevCommand` or extend the adapter in
|
|
123
|
+
app code (document pattern; no MMKV in framework).
|
|
124
|
+
|
|
125
|
+
### Native logs (D4 phase 3)
|
|
126
|
+
|
|
127
|
+
Extend `read_logs` with source **`native`**:
|
|
128
|
+
|
|
129
|
+
| Platform | Mechanism | When unavailable |
|
|
130
|
+
|----------|-----------|------------------|
|
|
131
|
+
| Android emulator/device | `adb logcat -d -t <N>` subprocess; optional `TERRENO_ADB_SERIAL` | Clear status: `adb` not on PATH or no device |
|
|
132
|
+
| iOS Simulator | `xcrun simctl spawn booted log stream --style compact` with timeout/read cap | No booted sim or Xcode tools missing |
|
|
133
|
+
|
|
134
|
+
Entries use `source: "native"`, plain-text `raw`, parsed `level` when log line matches common
|
|
135
|
+
patterns. **Not** merged into `last_error` unless line matches error heuristics (document
|
|
136
|
+
behavior). Opt-in env **`TERRENO_NATIVE_LOGS=0`** disables subprocess collection (default on).
|
|
137
|
+
|
|
138
|
+
Web and Metro-only sessions: `native` source returns empty with status note.
|
|
139
|
+
|
|
140
|
+
### Dev commands (D4 phase 4)
|
|
141
|
+
|
|
142
|
+
App API ( **`@terreno/syncdb`** ):
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
registerTerrenoDevCommand({
|
|
146
|
+
name: "seedTodos",
|
|
147
|
+
description: "Insert three fixture todos for the signed-in user",
|
|
148
|
+
inputSchema: { type: "object", properties: { count: { type: "number" } } },
|
|
149
|
+
handler: async ({count}) => ({created: count ?? 3}),
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Registry: `globalThis.__TERRENO_DEV_COMMANDS__` — map of name → `{description, inputSchema,
|
|
154
|
+
handler}` (handlers run only inside the app via CDP evaluate, same as dispatch).
|
|
155
|
+
|
|
156
|
+
MCP:
|
|
157
|
+
|
|
158
|
+
- **`list_dev_commands`** — no args; JSON list of `{name, description, inputSchema}`.
|
|
159
|
+
- **`invoke_dev_command`** — `{name, arguments?}`; EVAL-gated; returns JSON-serializable
|
|
160
|
+
result or structured error.
|
|
161
|
+
|
|
162
|
+
Example: register one command in `example-frontend` that toggles a feature flag in AsyncStorage
|
|
163
|
+
to prove the loop.
|
|
164
|
+
|
|
165
|
+
## Notifications
|
|
166
|
+
|
|
167
|
+
None.
|
|
168
|
+
|
|
169
|
+
## UI
|
|
170
|
+
|
|
171
|
+
No user-visible UI. Dev hooks are `__DEV__` only and no-op in production builds.
|
|
172
|
+
|
|
173
|
+
## Phases
|
|
174
|
+
|
|
175
|
+
| Phase | Scope | Depends on |
|
|
176
|
+
|-------|-------|------------|
|
|
177
|
+
| 1. Network via CDP | `metroDevSession` Network domain + `read_logs` `network` source | — |
|
|
178
|
+
| 2. Dispatch + storage | `dispatch_client_action`, storage adapter, read/write tools, bootstrap + example | 1 (shares CDP) |
|
|
179
|
+
| 3. Native logs | `read_logs` `native` source, adb/simctl collectors | — (parallel with 2 after 1) |
|
|
180
|
+
| 4. Dev commands | Registry API, `list_dev_commands`, `invoke_dev_command`, example command | 2 (same evaluate path) |
|
|
181
|
+
| 5. Documentation | Reference, how-to, explainer updates; real captured outputs | 1–4 |
|
|
182
|
+
|
|
183
|
+
## Feature Flags & Migrations
|
|
184
|
+
|
|
185
|
+
- No data migrations.
|
|
186
|
+
- All new app hooks are stripped or no-op when `!__DEV__`.
|
|
187
|
+
- `TERRENO_MCP_EVAL=1` remains opt-in; bootstrap must not enable it by default.
|
|
188
|
+
- New env vars: `TERRENO_NATIVE_LOGS` (default on), `TERRENO_ADB_SERIAL` (optional).
|
|
189
|
+
|
|
190
|
+
## Risks
|
|
191
|
+
|
|
192
|
+
- **CDP load**: Enabling Network increases traffic on the single Hermes session; keep ring caps
|
|
193
|
+
and body truncation strict.
|
|
194
|
+
- **Secret leakage**: Redact auth headers; never log request bodies that look like JWTs (regex
|
|
195
|
+
heuristic in normalizer).
|
|
196
|
+
- **Storage writes**: Mis-invoked `write_local_storage` can corrupt auth tokens; tool description
|
|
197
|
+
and docs warn to prefer `syncdb_action` for synced data.
|
|
198
|
+
- **Native log volume**: Cap lines per `read_logs` call; default `entries` applies per source.
|
|
199
|
+
- **`invoke_dev_command`**: Arbitrary app code — same trust model as `evaluate`; dev-only registry.
|
|
200
|
+
|
|
201
|
+
## Not Included / Future Work
|
|
202
|
+
|
|
203
|
+
- SQLite file browser, notification dispatcher, network throttling, React tree/profiler MCP
|
|
204
|
+
tools, first-party MMKV adapter.
|
|
205
|
+
|
|
206
|
+
## Files to Create / Modify
|
|
207
|
+
|
|
208
|
+
| Area | Files |
|
|
209
|
+
|------|--------|
|
|
210
|
+
| CDP | `mcp-server/src/local/metro/metroDevSession.ts`, tests |
|
|
211
|
+
| Logs | `mcp-server/src/local/tools/readLogs.ts`, tests |
|
|
212
|
+
| Runtime tools | `mcp-server/src/local/tools/runtime.ts` or new `clientDispatch.ts`, `localStorage.ts`, `devCommands.ts` |
|
|
213
|
+
| Registry | `syncdb/src/devMcpRegistry.ts` (or split files), `syncdb/src/index.ts`, tests |
|
|
214
|
+
| Bootstrap | `mcp-server/src/docs/templates/bootstrap/frontend/**`, `create-terreno-app` store bootstrap |
|
|
215
|
+
| Example | `example-frontend/app/_layout.tsx`, optional `devCommands.ts` |
|
|
216
|
+
| Docs | `docs/reference/mcp-server.md`, `docs/how-to/debug-with-mcp.md`, `docs/explanation/ai-development-loop.md`, `docs/reference/environment-variables.md`, `mcp-server/README.md` |
|
|
217
|
+
|
|
218
|
+
## Task List
|
|
219
|
+
|
|
220
|
+
See [`docs/tasks/mcp-agent-debugger-gaps.md`](../tasks/mcp-agent-debugger-gaps.md).
|
|
221
|
+
|
|
222
|
+
## E2E tests
|
|
223
|
+
|
|
224
|
+
| ID | Spec or flow | Layer | Proves | Written in |
|
|
225
|
+
| --- | --- | --- | --- | --- |
|
|
226
|
+
| E1 | `mcp-server/src/local/localServer.e2e.test.ts` (extend) | process | `read_logs` returns at least one `network` entry after CDP mock emits `Network.responseReceived` | Task 1.3 |
|
|
227
|
+
| E2 | `example-frontend/e2e/mcp-network-debug.spec.ts` | browser | A failing API call produces a 403/404 visible via agent workflow documented in how-to (page triggers fetch; test asserts backend log + optional network source when Metro attached in CI) | Task 5.2 |
|
|
228
|
+
|
|
229
|
+
If CI cannot attach Metro CDP reliably, Task 5.2 records `None — Metro CDP not in CI` and E2
|
|
230
|
+
becomes a manual evidence step in Task 5.1 only; unit/E1 coverage remains required.
|
|
231
|
+
|
|
232
|
+
**Pick note (2026-11-06):** E2 browser spec not added — Metro CDP is not reliable in CI. E1
|
|
233
|
+
(`localServer.e2e.test.ts`) plus unit tests satisfy automated proof.
|
|
234
|
+
|
|
235
|
+
## Regression cases
|
|
236
|
+
|
|
237
|
+
None — no hard-to-automate change
|
|
238
|
+
|
|
239
|
+
## User guide
|
|
240
|
+
|
|
241
|
+
None — operator/agent documentation only; no end-user workflow change
|
|
242
|
+
|
|
243
|
+
## Acceptance Criteria
|
|
244
|
+
|
|
245
|
+
- [x] `read_logs` documents and returns entries with `source: "network"` when the app performs
|
|
246
|
+
fetch/XHR over CDP, with auth headers redacted in captured output.
|
|
247
|
+
- [x] `read_logs` documents and returns entries with `source: "native"` on Android (adb) and
|
|
248
|
+
iOS Simulator when tools are available, with clear status when not.
|
|
249
|
+
- [x] `dispatch_client_action` dispatches a known action in `example-frontend` under
|
|
250
|
+
`TERRENO_MCP_EVAL=1` and `get_client_state` reflects the new state.
|
|
251
|
+
- [x] `read_local_storage` / `write_local_storage` round-trip a key in `example-frontend` dev
|
|
252
|
+
build; write refused without `TERRENO_MCP_EVAL=1`.
|
|
253
|
+
- [x] `list_dev_commands` / `invoke_dev_command` list and run the example-frontend registered
|
|
254
|
+
command with EVAL gate on invoke only.
|
|
255
|
+
- [x] Reference and how-to pages list every new tool and source with real output captured in
|
|
256
|
+
Task 5.1.
|
|
257
|
+
- [x] `bun run mcp:build`, affected package tests, and `bun run lint` pass.
|
|
@@ -121,6 +121,8 @@ Hosting platforms scope variables per environment. Vercel has **Production**, **
|
|
|
121
121
|
| `TERRENO_PROJECT_ROOT` | `terreno-mcp-local`, `terreno` CLI | ❌ | nearest Terreno project root | No | tooling |
|
|
122
122
|
| `TERRENO_METRO_URL` | `terreno-mcp-local` | ❌ | frontend script port or `http://localhost:8082` | No | tooling |
|
|
123
123
|
| `TERRENO_MCP_EVAL` | `terreno-mcp-local` | ❌ | disabled | No | tooling |
|
|
124
|
+
| `TERRENO_NATIVE_LOGS` | `terreno-mcp-local` `read_logs` source `native` | ❌ | enabled (`0` disables) | No | tooling |
|
|
125
|
+
| `TERRENO_ADB_SERIAL` | `terreno-mcp-local` native Android logs | ❌ | default `adb` device | No | tooling |
|
|
124
126
|
| `TERRENO_WEB_URL` | `terreno` CLI | ❌ | `http://localhost:8082` | No | tooling |
|
|
125
127
|
| `BUN_CHROME_PATH` | Bun WebView | ❌ | browser auto-discovery | No | tooling |
|
|
126
128
|
| `TERRENO_OPENAPI` | `terreno` CLI | ❌ | — | No | tooling |
|
|
@@ -159,6 +159,18 @@ privately and are not returned. Start the local MCP with
|
|
|
159
159
|
See [Debug a Terreno app with MCP](../how-to/debug-with-mcp.md) for examples and
|
|
160
160
|
safety guidance.
|
|
161
161
|
|
|
162
|
+
### Dev client hooks (`@terreno/syncdb`)
|
|
163
|
+
|
|
164
|
+
| Tool | EVAL | Registration |
|
|
165
|
+
| --- | --- | --- |
|
|
166
|
+
| `read_logs` / `last_error` | no | sources include **network** and **native** |
|
|
167
|
+
| `dispatch_client_action` | yes | `registerTerrenoDevClientDispatch` |
|
|
168
|
+
| `read_local_storage` / `write_local_storage` | write | `registerTerrenoDevStorage` (reads redact auth-like keys) |
|
|
169
|
+
| `list_dev_commands` / `invoke_dev_command` | invoke | `registerTerrenoDevCommand` |
|
|
170
|
+
|
|
171
|
+
Network capture redacts `Authorization`, `Cookie`, `Set-Cookie`, and `Set-Auth-Token`.
|
|
172
|
+
Failed requests include the URL from the matching `requestWillBeSent` `requestId`.
|
|
173
|
+
|
|
162
174
|
### terreno_search_docs
|
|
163
175
|
|
|
164
176
|
BM25-style keyword search over markdown bundled with the MCP server: `docs/resources/*.md`, synced Diátaxis docs under `docs/versioned/`, and per-component excerpts derived from `ui-types-documentation.json`. **Prefer this tool before guessing** Terreno APIs (same posture as Laravel Boost `search-docs`).
|
|
@@ -361,7 +361,10 @@ exist or has no `smoke` script is an error.
|
|
|
361
361
|
## Coverage gates
|
|
362
362
|
|
|
363
363
|
Package CI uses `scripts/check-coverage.ts` (`bun run test:coverage`, default 95%
|
|
364
|
-
functions and lines) as the live gate.
|
|
364
|
+
functions and lines) as the live gate. It runs the suite as concurrent
|
|
365
|
+
`bun test --shard` processes and gates the merged LCOV; a sharded run below the
|
|
366
|
+
threshold is confirmed with one unsplit pass
|
|
367
|
+
([Test sharding](../explanation/test-sharding.md)). Bun 1.4.2+ may exit 1 on bunfig
|
|
365
368
|
`coverageThreshold` before isolated LCOV merges; the script continues when tests
|
|
366
369
|
reported `0 fail` and then enforces 95% on the merged report. The blocks
|
|
367
370
|
coverage pass sets `--max-concurrency=1`, because Bun's default 20-way run
|