proteum 2.2.9 → 2.4.1
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/AGENTS.md +10 -4
- package/README.md +58 -15
- package/agents/project/AGENTS.md +53 -10
- package/agents/project/DOCUMENTATION.md +1326 -0
- package/agents/project/app-root/AGENTS.md +2 -2
- package/agents/project/diagnostics.md +12 -7
- package/agents/project/optimizations.md +1 -0
- package/agents/project/root/AGENTS.md +24 -9
- package/agents/project/tests/AGENTS.md +7 -0
- package/agents/project/tests/e2e/AGENTS.md +13 -0
- package/agents/project/tests/e2e/REAL_WORLD_JOURNEY_TESTS.md +192 -0
- package/cli/commands/connect.ts +40 -4
- package/cli/commands/dev.ts +148 -25
- package/cli/commands/diagnose.ts +138 -5
- package/cli/commands/doctor.ts +24 -4
- package/cli/commands/explain.ts +134 -6
- package/cli/commands/mcp.ts +133 -0
- package/cli/commands/orient.ts +93 -3
- package/cli/commands/perf.ts +118 -13
- package/cli/commands/runtime.ts +234 -0
- package/cli/commands/trace.ts +116 -21
- package/cli/mcp/router.ts +1010 -0
- package/cli/presentation/commands.ts +93 -26
- package/cli/presentation/devSession.ts +2 -0
- package/cli/presentation/help.ts +1 -1
- package/cli/runtime/commands.ts +215 -24
- package/cli/runtime/devSessions.ts +328 -2
- package/cli/runtime/mcpDaemon.ts +288 -0
- package/cli/runtime/ports.ts +151 -0
- package/cli/utils/agentOutput.ts +46 -0
- package/cli/utils/agents.ts +194 -51
- package/cli/utils/appRoots.ts +232 -0
- package/common/dev/diagnostics.ts +1 -1
- package/common/dev/inspection.ts +22 -7
- package/common/dev/mcpPayloads.ts +1150 -0
- package/common/dev/mcpServer.ts +287 -0
- package/docs/agent-routing.md +137 -0
- package/docs/dev-commands.md +2 -0
- package/docs/dev-sessions.md +4 -1
- package/docs/diagnostics.md +70 -24
- package/docs/mcp.md +206 -0
- package/docs/migrate-from-2.1.3.md +14 -6
- package/docs/request-tracing.md +12 -6
- package/package.json +11 -3
- package/server/app/devMcp.ts +204 -0
- package/server/services/router/http/cache.ts +116 -0
- package/server/services/router/http/index.ts +94 -35
- package/server/services/router/index.ts +8 -11
- package/server/services/router/request/ip.test.cjs +0 -1
- package/tests/agents-utils.test.cjs +92 -14
- package/tests/cli-mcp-command.test.cjs +262 -0
- package/tests/codex-mcp-usage.test.cjs +307 -0
- package/tests/dev-sessions.test.cjs +113 -0
- package/tests/dev-transpile-watch.test.cjs +117 -9
- package/tests/eslint-rules.test.cjs +0 -1
- package/tests/inspection.test.cjs +66 -0
- package/tests/mcp.test.cjs +873 -0
- package/tests/router-cache-config.test.cjs +73 -0
- package/vitest.config.mjs +9 -0
package/docs/mcp.md
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# Proteum MCP
|
|
2
|
+
|
|
3
|
+
Proteum exposes MCP through two coordinated surfaces:
|
|
4
|
+
|
|
5
|
+
- `proteum mcp`: one machine-scope router for live Proteum dev projects.
|
|
6
|
+
- `proteum dev`: one app-root runtime endpoint at `http://localhost:<port>/__proteum/mcp`.
|
|
7
|
+
|
|
8
|
+
Agents should normally connect to `proteum mcp`. The router discovers live `proteum dev` sessions from the machine registry, can resolve offline Proteum app roots from a supplied `cwd`, returns stable `projectId` values for live projects, and forwards app-bound reads to the selected dev-hosted endpoint.
|
|
9
|
+
|
|
10
|
+
## Machine Router
|
|
11
|
+
|
|
12
|
+
Start the router from any directory:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
proteum mcp
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
When run from a terminal, `proteum mcp` starts or reuses the managed local daemon at `http://127.0.0.1:3769/mcp`. When an MCP client launches it over pipes, use stdio:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
proteum mcp --stdio
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`proteum dev` ensures the managed machine MCP daemon is running before the dev loop starts. Only one managed daemon may run at a time. Stale daemon records are cleaned automatically.
|
|
25
|
+
|
|
26
|
+
The router is read-only. It does not start or stop dev servers, mutate files, refresh generated code, run migrations, or execute commands.
|
|
27
|
+
|
|
28
|
+
Use this flow:
|
|
29
|
+
|
|
30
|
+
1. Call MCP `workflow_start` with `cwd` or a known `projectId`.
|
|
31
|
+
2. If the result is ambiguous or returns offline app candidates, call `project_resolve { cwd }`, pick the intended app root, start exactly one `proteum dev` server from that app root when needed, then retry `workflow_start`.
|
|
32
|
+
3. Pass the returned live `projectId` to every follow-up app-bound MCP call.
|
|
33
|
+
4. After an MCP read succeeds, do not run the equivalent CLI command or broad source search for the same state; keep CLI for fallback, validation, and final terminal evidence.
|
|
34
|
+
|
|
35
|
+
Example tool calls:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{"tool":"workflow_start","arguments":{"cwd":"/repo/apps/product","task":"read-only runtime health pass","route":"/dashboard"}}
|
|
39
|
+
{"tool":"projects_list","arguments":{}}
|
|
40
|
+
{"tool":"project_resolve","arguments":{"cwd":"/repo/apps/product/client/pages"}}
|
|
41
|
+
{"tool":"workflow_start","arguments":{"projectId":"prj_0123abcd4567","route":"/dashboard"}}
|
|
42
|
+
{"tool":"runtime_status","arguments":{"projectId":"prj_0123abcd4567"}}
|
|
43
|
+
{"tool":"orient","arguments":{"projectId":"prj_0123abcd4567","query":"/dashboard"}}
|
|
44
|
+
{"tool":"route_candidates","arguments":{"projectId":"prj_0123abcd4567","query":"dashboard","limit":8}}
|
|
45
|
+
{"tool":"explain_summary","arguments":{"projectId":"prj_0123abcd4567","query":"/dashboard"}}
|
|
46
|
+
{"tool":"diagnose","arguments":{"projectId":"prj_0123abcd4567","path":"/dashboard"}}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`workflow_start` is the only app-bound bootstrap tool that may resolve from `cwd` when `projectId` is not known. It may return offline app candidates when no matching dev server is running yet. Other app-bound tools require a live `projectId`; if they omit it, the router returns a compact error that tells the agent to call `projects_list` or `project_resolve`. There is no single-project fallback, because wrong-project reads are worse than an explicit routing retry.
|
|
50
|
+
|
|
51
|
+
## Dev Runtime Endpoint
|
|
52
|
+
|
|
53
|
+
During `proteum dev`, the app exposes the same app-level MCP contract through the official streamable HTTP transport:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
POST /__proteum/mcp
|
|
57
|
+
GET /__proteum/mcp
|
|
58
|
+
DELETE /__proteum/mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This endpoint is dev-only and local-tooling-only. It is already rooted to the running app, so its tools do not require `projectId` or `cwd`. The machine router strips routing fields before forwarding a call here.
|
|
62
|
+
|
|
63
|
+
The dev session UI and ready banner print:
|
|
64
|
+
|
|
65
|
+
```text
|
|
66
|
+
mcp http://localhost:<port>/__proteum/mcp
|
|
67
|
+
MCP: http://localhost:<port>/__proteum/mcp
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`proteum dev` also writes a machine registry record under `~/.proteum/dev-sessions/`. The stable `projectId` is derived from the canonical app root, so it remains stable across port or session-file changes.
|
|
71
|
+
|
|
72
|
+
## Discovery And Recovery
|
|
73
|
+
|
|
74
|
+
If machine MCP routing fails:
|
|
75
|
+
|
|
76
|
+
1. Run `proteum mcp status`.
|
|
77
|
+
2. Run `proteum runtime status` from the intended app root. If you are in a monorepo wrapper, use the returned app candidates and exact next action instead of starting dev from the wrapper.
|
|
78
|
+
3. If no live app session exists, use the exact Start Dev next action returned by runtime status. It checks the configured router/HMR ports and suggests an alternate free port when the manifest default is occupied.
|
|
79
|
+
4. If a live session exists but runtime/MCP is unreachable, stop the listed session file with `proteum dev stop --session-file <path>`, then start dev again.
|
|
80
|
+
5. Retry MCP `workflow_start` and use the returned `projectId`.
|
|
81
|
+
|
|
82
|
+
Offline `project_resolve` and `workflow_start` candidates also inspect configured router/HMR ports before returning `nextAction`. If the configured port already serves the same app but no live machine project is registered, the next action is runtime tracking repair, not starting a second dev server.
|
|
83
|
+
|
|
84
|
+
`proteum runtime status` refreshes the machine registry for live tracked sessions, so this recovery path also repairs missing router records after an upgrade.
|
|
85
|
+
|
|
86
|
+
Do not start a second `proteum dev` server in the same worktree. `proteum dev` fails fast when another live tracked session already exists for the same app root.
|
|
87
|
+
Do not start a second managed `proteum mcp` daemon. `proteum mcp` reuses the live daemon or reports its current URL.
|
|
88
|
+
Do not call `diagnose`, `trace_*`, or `perf_*` while runtime health is unreachable; repair or start dev first.
|
|
89
|
+
Do not `curl` normal page routes to identify port ownership; use `proteum runtime status` or Proteum dev-only `/__proteum/*` endpoints so wrong-app HTML is never dumped into agent context.
|
|
90
|
+
|
|
91
|
+
## Output Contract
|
|
92
|
+
|
|
93
|
+
MCP tool payloads are compact single-line JSON strings in this shape:
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
{"ok":true,"format":"proteum-mcp-v1","summary":"...","data":{},"nextActions":[],"omitted":[]}
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Outputs are capped by default:
|
|
100
|
+
|
|
101
|
+
- trace output shows counts, failed calls, error events, hot calls, and hot SQL first
|
|
102
|
+
- logs are limited and truncated
|
|
103
|
+
- diagnostics and perf rows are capped
|
|
104
|
+
- full trace detail is paginated with `detail: "full"`, `limit`, and `offset`
|
|
105
|
+
|
|
106
|
+
Do not make MCP tools return pretty-printed JSON or raw trace/log dumps by default. Pretty output belongs to human CLI/UI surfaces; MCP output is optimized for agent context.
|
|
107
|
+
|
|
108
|
+
## Tools
|
|
109
|
+
|
|
110
|
+
Machine-only tools:
|
|
111
|
+
|
|
112
|
+
| Tool | Purpose |
|
|
113
|
+
| --- | --- |
|
|
114
|
+
| `projects_list` | List live Proteum dev projects and stable `projectId` values |
|
|
115
|
+
| `project_resolve` | Resolve a live project or offline app candidate by `projectId`, `cwd`, app root, or app-root substring |
|
|
116
|
+
|
|
117
|
+
App-bound tools require `projectId` when called through `proteum mcp`:
|
|
118
|
+
|
|
119
|
+
| Tool | Purpose |
|
|
120
|
+
| --- | --- |
|
|
121
|
+
| `workflow_start` | One-call bootstrap with resolved project, runtime, selected instruction previews, owner summary, doctor summaries, duplicate-avoidance rules, and next actions |
|
|
122
|
+
| `runtime_status` | Manifest summary, selected runtime, tracked sessions, health, and MCP URL |
|
|
123
|
+
| `orient` | Owner, instruction routing, connected boundaries, and next actions |
|
|
124
|
+
| `instructions_resolve` | Selected instruction files for a query, with short previews and full-read policy |
|
|
125
|
+
| `route_candidates` | Compact route/controller/page matches for a query without dumping the raw route table |
|
|
126
|
+
| `explain_summary` | Compact manifest summary or owner lookup |
|
|
127
|
+
| `doctor` | Compact manifest and optional contract diagnostics |
|
|
128
|
+
| `diagnose` | Composite diagnosis for an existing route, query, or request trace |
|
|
129
|
+
| `trace_latest` | Compact latest trace summary, with optional paginated detail |
|
|
130
|
+
| `trace_show` | Compact or paginated detail for a specific request trace |
|
|
131
|
+
| `perf_top` | Hot-path perf rollup |
|
|
132
|
+
| `perf_request` | One-request waterfall and attribution |
|
|
133
|
+
| `logs_tail` | Capped recent server logs |
|
|
134
|
+
|
|
135
|
+
## CLI Boundary
|
|
136
|
+
|
|
137
|
+
Use CLI commands when the result must be reproducible as a terminal step, CI-like validation, or human-shareable command output:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
proteum dev --session-file var/run/proteum/dev/agents/task.json --port 3101
|
|
141
|
+
proteum build --prod
|
|
142
|
+
proteum check
|
|
143
|
+
proteum refresh
|
|
144
|
+
proteum diagnose /dashboard --port 3101
|
|
145
|
+
proteum verify request /dashboard --port 3101
|
|
146
|
+
proteum trace show <requestId> --events --port 3101
|
|
147
|
+
proteum explain owner /dashboard
|
|
148
|
+
proteum explain --routes --controllers --full # only when the raw route/controller arrays are required
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Use MCP when an agent is asking a running app for repeated state:
|
|
152
|
+
|
|
153
|
+
```text
|
|
154
|
+
workflow_start { cwd, task, route? }
|
|
155
|
+
runtime_status { projectId }
|
|
156
|
+
instructions_resolve { projectId, query }
|
|
157
|
+
orient { projectId, query }
|
|
158
|
+
route_candidates { projectId, query }
|
|
159
|
+
explain_summary { projectId, query }
|
|
160
|
+
doctor { projectId }
|
|
161
|
+
diagnose { projectId, path }
|
|
162
|
+
trace_show { projectId, requestId }
|
|
163
|
+
trace_latest { projectId }
|
|
164
|
+
perf_request { projectId, query }
|
|
165
|
+
logs_tail { projectId }
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
After an MCP read succeeds, do not run the equivalent CLI command for the same state, and do not run broad source searches for ownership that MCP already returned. CLI output is for fallback, validation, command evidence, and human-shareable reproductions.
|
|
169
|
+
|
|
170
|
+
## Benchmark
|
|
171
|
+
|
|
172
|
+
The Product `/domains` diagnostic loop measured on May 7, 2026 used `ceil(UTF-8 bytes / 4)` as an output-token estimate:
|
|
173
|
+
|
|
174
|
+
| Workflow | Approx output tokens | Elapsed |
|
|
175
|
+
| --- | ---: | ---: |
|
|
176
|
+
| Compact CLI single loop | 6,286 | 4,809 ms |
|
|
177
|
+
| Dev-hosted HTTP MCP single loop | 5,211 | 232 ms |
|
|
178
|
+
| Compact CLI repeated reads x3 | 11,660 | 9,572 ms |
|
|
179
|
+
| Dev-hosted HTTP MCP repeated reads x3 | 10,537 | 214 ms |
|
|
180
|
+
|
|
181
|
+
Machine routing adds one lightweight `projects_list` lookup but keeps repeated app reads on the dev-hosted runtime endpoint. The practical rule is: use CLI for reproducible checks and final evidence, then use MCP with `projectId` for repeated reads against the same app/runtime.
|
|
182
|
+
|
|
183
|
+
## Codex Usage Test
|
|
184
|
+
|
|
185
|
+
Proteum core uses Vitest for framework tests. The live Codex MCP usage test is opt-in because it runs the real Codex CLI, may spend model tokens, and depends on the developer machine's Codex auth plus MCP registration.
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
PROTEUM_CODEX_MCP_USAGE_CWD=/absolute/path/to/proteum/app npm run test:codex-mcp
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
The test sends a read-only runtime health prompt to `codex exec --json`, stores the JSONL transcript, stderr, last message, and `summary.json`, then asserts:
|
|
192
|
+
|
|
193
|
+
- token usage was reported and quantified
|
|
194
|
+
- at least one Proteum MCP `workflow_start` call happened
|
|
195
|
+
- total Proteum MCP calls meet `PROTEUM_CODEX_MCP_MIN_MCP_CALLS` (`4` by default)
|
|
196
|
+
- Proteum CLI fallback calls stay under `PROTEUM_CODEX_MCP_MAX_CLI_CALLS` (`4` by default)
|
|
197
|
+
|
|
198
|
+
Useful optional variables:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
CODEX_CLI=/path/to/codex
|
|
202
|
+
PROTEUM_CODEX_MCP_USAGE_OUTPUT_DIR=/tmp/proteum-codex-mcp-usage
|
|
203
|
+
PROTEUM_CODEX_MCP_USAGE_TIMEOUT_MS=1200000
|
|
204
|
+
PROTEUM_CODEX_MCP_MIN_MCP_CALLS=4
|
|
205
|
+
PROTEUM_CODEX_MCP_MAX_CLI_CALLS=4
|
|
206
|
+
```
|
|
@@ -307,13 +307,18 @@ This is usually only needed for workspace or hoisted installs. Standalone apps w
|
|
|
307
307
|
|
|
308
308
|
These are worth updating even though they are not core app-code migrations.
|
|
309
309
|
|
|
310
|
-
### Use
|
|
310
|
+
### Use compact CLI output for automation
|
|
311
311
|
|
|
312
|
-
Only bare `proteum build` and bare `proteum dev` runs print a banner. For scripts, CI helpers, or editor tooling, prefer:
|
|
312
|
+
Only bare `proteum build` and bare `proteum dev` runs print a banner. Other CLI diagnostics are optimized for agents and return compact machine-readable output by default. For scripts, CI helpers, or editor tooling, prefer:
|
|
313
313
|
|
|
314
|
-
- `npx proteum
|
|
315
|
-
- `npx proteum
|
|
316
|
-
- `npx proteum
|
|
314
|
+
- `npx proteum orient <query>`
|
|
315
|
+
- `npx proteum runtime status`
|
|
316
|
+
- `npx proteum explain`
|
|
317
|
+
- `npx proteum doctor`
|
|
318
|
+
- `npx proteum connect`
|
|
319
|
+
- `npx proteum mcp` as the managed machine-scope MCP router; `proteum dev` ensures one daemon is running, agents call `workflow_start` first, use offline candidates to choose the correct app root when needed, then pass the returned live `projectId` to repeated reads against a live dev app
|
|
320
|
+
|
|
321
|
+
CLI output uses compact `proteum-agent-v1` JSON for reproducible command evidence. MCP output uses compact `proteum-mcp-v1` JSON for repeated runtime reads. Use `npx proteum explain --manifest`, `npx proteum diagnose <target> --full`, or `npx proteum trace show <requestId> --events` only when the compact output is insufficient.
|
|
317
322
|
|
|
318
323
|
### Use tracked dev sessions
|
|
319
324
|
|
|
@@ -323,7 +328,7 @@ Current `proteum dev` supports tracked session management:
|
|
|
323
328
|
- `npx proteum dev list --json`
|
|
324
329
|
- `npx proteum dev stop --session-file var/run/proteum/dev/app.json`
|
|
325
330
|
|
|
326
|
-
If
|
|
331
|
+
`proteum dev` fails fast when another live tracked session already exists for the same app root. If runtime/MCP is unreachable, stop the listed session file first, then start dev again instead of launching a second server in the same worktree.
|
|
327
332
|
|
|
328
333
|
### New diagnostics are available
|
|
329
334
|
|
|
@@ -331,6 +336,8 @@ These are new capabilities, not migration requirements, but they are the fastest
|
|
|
331
336
|
|
|
332
337
|
- `npx proteum connect --strict`
|
|
333
338
|
- `npx proteum explain --connected --controllers`
|
|
339
|
+
- `npx proteum runtime status`
|
|
340
|
+
- `npx proteum mcp status` plus MCP `workflow_start`, `project_resolve`, or `projects_list`
|
|
334
341
|
- `npx proteum diagnose / --port <port>`
|
|
335
342
|
- `npx proteum perf top --port <port>`
|
|
336
343
|
- `npx proteum trace latest --port <port>`
|
|
@@ -370,6 +377,7 @@ Then boot the app and verify the live runtime:
|
|
|
370
377
|
|
|
371
378
|
```bash
|
|
372
379
|
npx proteum dev --port 3010
|
|
380
|
+
npx proteum runtime status
|
|
373
381
|
npx proteum diagnose / --port 3010
|
|
374
382
|
npx proteum trace latest --port 3010
|
|
375
383
|
```
|
package/docs/request-tracing.md
CHANGED
|
@@ -10,7 +10,7 @@ The same API and SQL instrumentation feeds both shapes. Dev trace keeps the in-m
|
|
|
10
10
|
## Scope
|
|
11
11
|
|
|
12
12
|
- retained dev tracing is available only when the app runs with `profile: dev`
|
|
13
|
-
- traces are exposed through `proteum trace`, `proteum perf`, and the dev-only `__proteum/trace` and
|
|
13
|
+
- traces are exposed through `proteum trace`, `proteum perf`, MCP `trace_*`/`perf_*` tools, and the dev-only `__proteum/trace`, `__proteum/perf`, and `/__proteum/mcp` HTTP endpoints
|
|
14
14
|
- `proteum diagnose` is a separate composite surface that reads the same framework diagnostics plus one matching request trace and buffered server logs; see [diagnostics.md](diagnostics.md)
|
|
15
15
|
- `ENABLE_PROFILER=true` enables reduced request-local profiling in any environment, including production
|
|
16
16
|
|
|
@@ -20,6 +20,7 @@ The same API and SQL instrumentation feeds both shapes. Dev trace keeps the in-m
|
|
|
20
20
|
proteum trace requests
|
|
21
21
|
proteum trace latest
|
|
22
22
|
proteum trace show <requestId>
|
|
23
|
+
proteum trace show <requestId> --events
|
|
23
24
|
proteum trace arm --capture deep
|
|
24
25
|
proteum trace export <requestId>
|
|
25
26
|
proteum trace latest --url http://127.0.0.1:3010
|
|
@@ -30,26 +31,31 @@ proteum perf compare --baseline yesterday --target today --group-by route
|
|
|
30
31
|
proteum perf memory --since 1h --group-by controller
|
|
31
32
|
```
|
|
32
33
|
|
|
34
|
+
Default trace output is compact `proteum-agent-v1` JSON with counts, failed calls, error events, hot calls, and hot SQL. Use `--events` or `--full` only when raw event details, payload summaries, or SQL text are needed.
|
|
35
|
+
|
|
36
|
+
When an MCP client is available, call `workflow_start` with `cwd` or a known `projectId`, then use the returned live `projectId` with MCP `trace_latest`, `trace_show`, `perf_top`, and `perf_request` for repeated reads against the same running app. If `workflow_start` returns offline app candidates or unreachable runtime health, start or repair exactly one `proteum dev` server from the intended app root before trace or perf reads. Keep the CLI commands for reproducible terminal evidence and final verification logs.
|
|
37
|
+
|
|
33
38
|
Before reproducing a bug or starting a new test pass:
|
|
34
39
|
|
|
35
|
-
-
|
|
36
|
-
-
|
|
37
|
-
- if
|
|
40
|
+
- run `proteum runtime status` to reuse a tracked dev session when possible and to get an exact Start Dev action when the configured router/HMR ports are occupied
|
|
41
|
+
- use MCP `runtime_status` with the selected `projectId` for repeated status checks against the same running server
|
|
42
|
+
- if a server is already running, inspect `proteum trace requests`, `proteum trace latest`, and compact `proteum diagnose <path>` first so you can capture past errors without dumping raw events
|
|
38
43
|
|
|
39
44
|
Typical debugging flow:
|
|
40
45
|
|
|
41
46
|
```bash
|
|
42
47
|
proteum orient /dashboard
|
|
48
|
+
proteum runtime status
|
|
43
49
|
proteum diagnose /dashboard --hit /dashboard --port 3103
|
|
44
50
|
proteum perf request /dashboard --port 3103
|
|
45
|
-
proteum trace show <requestId> --port 3103
|
|
51
|
+
proteum trace show <requestId> --events --port 3103
|
|
46
52
|
```
|
|
47
53
|
|
|
48
54
|
Use `--url http://host:port` when the dev server is reachable on a non-standard host and `--port` is not enough.
|
|
49
55
|
|
|
50
56
|
If the request under test is protected and login UX is not the feature under test, mint an auth cookie with `proteum session <email> --role <role>` before reproducing the request. This keeps the trace focused on the protected behavior instead of the login flow.
|
|
51
57
|
|
|
52
|
-
If you already know the failing path and want a one-shot suspect list before reading raw events, start with `proteum diagnose <path> --port <port>` and `proteum perf request <path> --port <port>` first, then drop into `proteum trace show <requestId
|
|
58
|
+
If you already know the failing path and want a one-shot suspect list before reading raw events, start with `proteum diagnose <path> --port <port>` and `proteum perf request <path> --port <port>` first, then drop into `proteum trace show <requestId> --events` only when the lower-level event stream is still needed.
|
|
53
59
|
|
|
54
60
|
Use ad hoc database queries or one-off scripts only after `orient`, `diagnose`, `perf request`, and `trace show` still leave the request chain unclear.
|
|
55
61
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proteum",
|
|
3
3
|
"description": "LLM-first Opinionated Typescript Framework for web applications.",
|
|
4
|
-
"version": "2.
|
|
4
|
+
"version": "2.4.1",
|
|
5
5
|
"author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
|
|
6
6
|
"repository": "git://github.com/gaetanlegac/proteum.git",
|
|
7
7
|
"license": "MIT",
|
|
@@ -12,6 +12,12 @@
|
|
|
12
12
|
"keywords": [
|
|
13
13
|
"framework"
|
|
14
14
|
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:codex-mcp": "PROTEUM_RUN_CODEX_MCP_USAGE_TEST=1 vitest run tests/codex-mcp-usage.test.cjs",
|
|
18
|
+
"test:integration": "vitest run tests/dev-transpile-watch.test.cjs",
|
|
19
|
+
"test:unit": "vitest run --exclude tests/dev-transpile-watch.test.cjs"
|
|
20
|
+
},
|
|
15
21
|
"bin": {
|
|
16
22
|
"proteum": "cli/bin.js"
|
|
17
23
|
},
|
|
@@ -21,6 +27,7 @@
|
|
|
21
27
|
"@babel/traverse": "^7.29.0",
|
|
22
28
|
"@babel/types": "^7.29.0",
|
|
23
29
|
"@inkjs/ui": "^2.0.0",
|
|
30
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
24
31
|
"@prisma/adapter-mariadb": "7.2.0",
|
|
25
32
|
"@prisma/client": "7.2.0",
|
|
26
33
|
"@rspack/core": "^1.7.9",
|
|
@@ -103,13 +110,14 @@
|
|
|
103
110
|
"@types/fs-extra": "^9.0.12",
|
|
104
111
|
"@types/markdown-it": "^12.2.3",
|
|
105
112
|
"@types/mime-types": "^2.1.1",
|
|
106
|
-
"@types/node": "^
|
|
113
|
+
"@types/node": "^20.19.40",
|
|
107
114
|
"@types/nodemailer": "^6.4.4",
|
|
108
115
|
"@types/pg": "^8.6.1",
|
|
109
116
|
"@types/pg-escape": "^0.2.1",
|
|
110
117
|
"@types/prompts": "^2.0.14",
|
|
111
118
|
"@types/sharp": "^0.31.1",
|
|
112
119
|
"@types/universal-analytics": "^0.4.5",
|
|
113
|
-
"schema-dts": "^1.1.2"
|
|
120
|
+
"schema-dts": "^1.1.2",
|
|
121
|
+
"vitest": "^4.1.5"
|
|
114
122
|
}
|
|
115
123
|
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { buildContractsDoctorResponse } from '@common/dev/contractsDoctor';
|
|
2
|
+
import { buildDoctorResponse } from '@common/dev/diagnostics';
|
|
3
|
+
import { buildOrientationResponse, explainOwner } from '@common/dev/inspection';
|
|
4
|
+
import {
|
|
5
|
+
buildRuntimeStatusPayload,
|
|
6
|
+
compactDiagnoseResponse,
|
|
7
|
+
compactDoctorResponse,
|
|
8
|
+
compactExplainSummary,
|
|
9
|
+
compactLogsResponse,
|
|
10
|
+
compactOrientationResponse,
|
|
11
|
+
compactPerfRequestResponse,
|
|
12
|
+
compactPerfTopResponse,
|
|
13
|
+
compactRouteCandidatesResponse,
|
|
14
|
+
compactTraceResponse,
|
|
15
|
+
compactWorkflowStartResponse,
|
|
16
|
+
resolveInstructionRouting,
|
|
17
|
+
} from '@common/dev/mcpPayloads';
|
|
18
|
+
import type { TProteumMcpProvider } from '@common/dev/mcpServer';
|
|
19
|
+
import type { TDevConsoleLogLevel } from '@common/dev/console';
|
|
20
|
+
import type { TPerfGroupBy } from '@common/dev/performance';
|
|
21
|
+
|
|
22
|
+
import type { Application } from './index';
|
|
23
|
+
|
|
24
|
+
export const createRuntimeProteumMcpProvider = ({
|
|
25
|
+
app,
|
|
26
|
+
publicUrl,
|
|
27
|
+
routerPort,
|
|
28
|
+
}: {
|
|
29
|
+
app: Application;
|
|
30
|
+
publicUrl: string;
|
|
31
|
+
routerPort: number;
|
|
32
|
+
}): TProteumMcpProvider => {
|
|
33
|
+
const diagnostics = () => app.getDevDiagnostics();
|
|
34
|
+
const readManifest = () => diagnostics().readManifest();
|
|
35
|
+
const assertTraceEnabled = () => {
|
|
36
|
+
if (!app.container.Trace.isDevTraceEnabled()) {
|
|
37
|
+
throw new Error('Proteum dev trace is not enabled for this runtime.');
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const provider: TProteumMcpProvider = {
|
|
42
|
+
async runtimeStatus(_input: Record<string, never> = {}) {
|
|
43
|
+
const manifest = readManifest();
|
|
44
|
+
const doctor = buildDoctorResponse(manifest);
|
|
45
|
+
const contracts = buildContractsDoctorResponse(manifest);
|
|
46
|
+
|
|
47
|
+
return buildRuntimeStatusPayload({
|
|
48
|
+
appRoot: app.container.path.root,
|
|
49
|
+
health: {
|
|
50
|
+
reachable: true,
|
|
51
|
+
doctor: doctor.summary,
|
|
52
|
+
contracts: contracts.summary,
|
|
53
|
+
},
|
|
54
|
+
manifest,
|
|
55
|
+
runtime: {
|
|
56
|
+
publicUrl,
|
|
57
|
+
routerPort,
|
|
58
|
+
source: 'proteum-dev-runtime',
|
|
59
|
+
mcpUrl: `${publicUrl}/__proteum/mcp`,
|
|
60
|
+
traceEnabled: app.container.Trace.isDevTraceEnabled(),
|
|
61
|
+
profilerEnabled: app.container.Trace.isProfilingEnabled(),
|
|
62
|
+
connectedProjects: Object.entries(app.connectedProjects || {}).map(([namespace, project]) => ({
|
|
63
|
+
namespace,
|
|
64
|
+
urlInternal: (project as { urlInternal?: string }).urlInternal,
|
|
65
|
+
})),
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
},
|
|
69
|
+
async workflowStart({ file, query, route, task }) {
|
|
70
|
+
const manifest = readManifest();
|
|
71
|
+
const doctor = buildDoctorResponse(manifest);
|
|
72
|
+
const contracts = buildContractsDoctorResponse(manifest);
|
|
73
|
+
const ownerQuery = [route, file, query]
|
|
74
|
+
.map((value) => value?.trim())
|
|
75
|
+
.find((value): value is string => Boolean(value));
|
|
76
|
+
|
|
77
|
+
return compactWorkflowStartResponse({
|
|
78
|
+
contracts,
|
|
79
|
+
doctor,
|
|
80
|
+
file,
|
|
81
|
+
health: {
|
|
82
|
+
reachable: true,
|
|
83
|
+
doctor: doctor.summary,
|
|
84
|
+
contracts: contracts.summary,
|
|
85
|
+
},
|
|
86
|
+
manifest,
|
|
87
|
+
owner: ownerQuery ? explainOwner(manifest, ownerQuery) : undefined,
|
|
88
|
+
query,
|
|
89
|
+
route,
|
|
90
|
+
runtime: {
|
|
91
|
+
publicUrl,
|
|
92
|
+
routerPort,
|
|
93
|
+
source: 'proteum-dev-runtime',
|
|
94
|
+
mcpUrl: `${publicUrl}/__proteum/mcp`,
|
|
95
|
+
traceEnabled: app.container.Trace.isDevTraceEnabled(),
|
|
96
|
+
profilerEnabled: app.container.Trace.isProfilingEnabled(),
|
|
97
|
+
connectedProjects: Object.entries(app.connectedProjects || {}).map(([namespace, project]) => ({
|
|
98
|
+
namespace,
|
|
99
|
+
urlInternal: (project as { urlInternal?: string }).urlInternal,
|
|
100
|
+
})),
|
|
101
|
+
},
|
|
102
|
+
task,
|
|
103
|
+
});
|
|
104
|
+
},
|
|
105
|
+
async orient({ query }) {
|
|
106
|
+
return compactOrientationResponse(buildOrientationResponse(readManifest(), query));
|
|
107
|
+
},
|
|
108
|
+
async instructionsResolve({ query }) {
|
|
109
|
+
return resolveInstructionRouting({ appRoot: app.container.path.root, query });
|
|
110
|
+
},
|
|
111
|
+
async explainSummary({ query }) {
|
|
112
|
+
const manifest = readManifest();
|
|
113
|
+
const normalizedQuery = query?.trim();
|
|
114
|
+
|
|
115
|
+
return compactExplainSummary({
|
|
116
|
+
manifest,
|
|
117
|
+
owner: normalizedQuery ? explainOwner(manifest, normalizedQuery) : undefined,
|
|
118
|
+
query: normalizedQuery,
|
|
119
|
+
});
|
|
120
|
+
},
|
|
121
|
+
async routeCandidates({ limit, query }) {
|
|
122
|
+
return compactRouteCandidatesResponse({
|
|
123
|
+
limit,
|
|
124
|
+
manifest: readManifest(),
|
|
125
|
+
query,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
async doctor({ contracts = true }) {
|
|
129
|
+
const manifest = readManifest();
|
|
130
|
+
|
|
131
|
+
return compactDoctorResponse({
|
|
132
|
+
contracts: contracts ? buildContractsDoctorResponse(manifest) : undefined,
|
|
133
|
+
doctor: buildDoctorResponse(manifest),
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
async diagnose({
|
|
137
|
+
logsLevel = 'warn',
|
|
138
|
+
logsLimit = 40,
|
|
139
|
+
path,
|
|
140
|
+
query,
|
|
141
|
+
requestId,
|
|
142
|
+
}: {
|
|
143
|
+
logsLevel?: TDevConsoleLogLevel;
|
|
144
|
+
logsLimit?: number;
|
|
145
|
+
path?: string;
|
|
146
|
+
query?: string;
|
|
147
|
+
requestId?: string;
|
|
148
|
+
}) {
|
|
149
|
+
return compactDiagnoseResponse(
|
|
150
|
+
diagnostics().diagnose({
|
|
151
|
+
logsLevel,
|
|
152
|
+
logsLimit,
|
|
153
|
+
path,
|
|
154
|
+
query,
|
|
155
|
+
requestId,
|
|
156
|
+
}),
|
|
157
|
+
);
|
|
158
|
+
},
|
|
159
|
+
async traceLatest({ detail, limit, offset }) {
|
|
160
|
+
assertTraceEnabled();
|
|
161
|
+
const request = app.container.Trace.getLatestRequest();
|
|
162
|
+
if (!request) throw new Error('No request trace is available yet.');
|
|
163
|
+
|
|
164
|
+
return compactTraceResponse({ detail, limit, offset, request });
|
|
165
|
+
},
|
|
166
|
+
async traceShow({ detail, limit, offset, requestId }) {
|
|
167
|
+
assertTraceEnabled();
|
|
168
|
+
const request = app.container.Trace.getRequest(requestId);
|
|
169
|
+
if (!request) throw new Error(`Trace ${requestId} was not found.`);
|
|
170
|
+
|
|
171
|
+
return compactTraceResponse({ detail, limit, offset, request });
|
|
172
|
+
},
|
|
173
|
+
async perfTop({ groupBy = 'path', limit = 12, since = 'today' }) {
|
|
174
|
+
return compactPerfTopResponse(
|
|
175
|
+
diagnostics().perfTop({
|
|
176
|
+
groupBy: groupBy as TPerfGroupBy,
|
|
177
|
+
limit,
|
|
178
|
+
since,
|
|
179
|
+
}),
|
|
180
|
+
);
|
|
181
|
+
},
|
|
182
|
+
async perfRequest({ query }) {
|
|
183
|
+
return compactPerfRequestResponse(diagnostics().perfRequest(query));
|
|
184
|
+
},
|
|
185
|
+
async logsTail({ level = 'warn', limit = 40 }) {
|
|
186
|
+
return compactLogsResponse({
|
|
187
|
+
level,
|
|
188
|
+
limit,
|
|
189
|
+
response: diagnostics().readLogs(limit, level),
|
|
190
|
+
});
|
|
191
|
+
},
|
|
192
|
+
async readResource(uri) {
|
|
193
|
+
if (uri === 'proteum://runtime/status') return await provider.runtimeStatus({});
|
|
194
|
+
if (uri === 'proteum://instructions/router') return await provider.instructionsResolve({});
|
|
195
|
+
if (uri === 'proteum://manifest/summary') return await provider.explainSummary({});
|
|
196
|
+
if (uri === 'proteum://trace/latest/summary') return await provider.traceLatest({});
|
|
197
|
+
if (uri === 'proteum://perf/top') return await provider.perfTop({});
|
|
198
|
+
|
|
199
|
+
throw new Error(`Unknown Proteum MCP resource: ${uri}`);
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
return provider;
|
|
204
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
export type THttpCacheHeadersConfig = {
|
|
4
|
+
cacheControl?: string;
|
|
5
|
+
surrogateControl?: string | false;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type TPublicAssetsCacheConfig = {
|
|
9
|
+
dev?: string;
|
|
10
|
+
versioned?: string;
|
|
11
|
+
unversioned?: string;
|
|
12
|
+
etag?: boolean;
|
|
13
|
+
lastModified?: boolean;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export type THttpCacheConfig = {
|
|
17
|
+
html?: {
|
|
18
|
+
dynamic?: THttpCacheHeadersConfig;
|
|
19
|
+
static?: THttpCacheHeadersConfig;
|
|
20
|
+
};
|
|
21
|
+
publicAssets?: TPublicAssetsCacheConfig;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export type TResolvedHttpCacheHeadersConfig = {
|
|
25
|
+
cacheControl: string;
|
|
26
|
+
surrogateControl: string | false;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export type TResolvedPublicAssetsCacheConfig = {
|
|
30
|
+
dev: string;
|
|
31
|
+
versioned: string;
|
|
32
|
+
unversioned: string;
|
|
33
|
+
etag?: boolean;
|
|
34
|
+
lastModified?: boolean;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type TResolvedHttpCacheConfig = {
|
|
38
|
+
html: {
|
|
39
|
+
dynamic: TResolvedHttpCacheHeadersConfig;
|
|
40
|
+
static: TResolvedHttpCacheHeadersConfig;
|
|
41
|
+
};
|
|
42
|
+
publicAssets: TResolvedPublicAssetsCacheConfig;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
export const defaultHttpCacheConfig = {
|
|
46
|
+
html: {
|
|
47
|
+
dynamic: {
|
|
48
|
+
cacheControl: 'no-store, no-cache, must-revalidate, proxy-revalidate',
|
|
49
|
+
surrogateControl: 'no-store',
|
|
50
|
+
},
|
|
51
|
+
static: {
|
|
52
|
+
cacheControl: 'public, max-age=0, must-revalidate',
|
|
53
|
+
surrogateControl: false,
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
publicAssets: {
|
|
57
|
+
dev: 'no-store',
|
|
58
|
+
versioned: 'public, max-age=31536000, immutable',
|
|
59
|
+
unversioned: 'public, max-age=0, must-revalidate',
|
|
60
|
+
},
|
|
61
|
+
} satisfies TResolvedHttpCacheConfig;
|
|
62
|
+
|
|
63
|
+
type TPublicAssetRequest = {
|
|
64
|
+
originalUrl?: string;
|
|
65
|
+
url?: string;
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
type TPublicAssetResponse = {
|
|
69
|
+
req?: TPublicAssetRequest;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const hashedPublicAssetPattern = /(^|[-_.])[a-f0-9]{6,}(?=(\.[^.]+)+$)/i;
|
|
73
|
+
|
|
74
|
+
export const resolveHttpCacheConfig = (config?: THttpCacheConfig): TResolvedHttpCacheConfig => ({
|
|
75
|
+
html: {
|
|
76
|
+
dynamic: {
|
|
77
|
+
...defaultHttpCacheConfig.html.dynamic,
|
|
78
|
+
...(config?.html?.dynamic || {}),
|
|
79
|
+
},
|
|
80
|
+
static: {
|
|
81
|
+
...defaultHttpCacheConfig.html.static,
|
|
82
|
+
...(config?.html?.static || {}),
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
publicAssets: {
|
|
86
|
+
...defaultHttpCacheConfig.publicAssets,
|
|
87
|
+
...(config?.publicAssets || {}),
|
|
88
|
+
},
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
export const isVersionedPublicAssetRequest = (
|
|
92
|
+
res: undefined | TPublicAssetResponse,
|
|
93
|
+
filePath: string,
|
|
94
|
+
) => {
|
|
95
|
+
const requestUrl = res?.req?.originalUrl || res?.req?.url || '';
|
|
96
|
+
const searchParams = new URL(requestUrl, 'http://proteum.local').searchParams;
|
|
97
|
+
if (searchParams.has('v')) return true;
|
|
98
|
+
|
|
99
|
+
return hashedPublicAssetPattern.test(path.basename(filePath));
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const resolvePublicAssetCacheControl = ({
|
|
103
|
+
res,
|
|
104
|
+
filePath,
|
|
105
|
+
profile,
|
|
106
|
+
cache,
|
|
107
|
+
}: {
|
|
108
|
+
res: undefined | TPublicAssetResponse;
|
|
109
|
+
filePath: string;
|
|
110
|
+
profile: string;
|
|
111
|
+
cache: TResolvedPublicAssetsCacheConfig;
|
|
112
|
+
}) => {
|
|
113
|
+
if (profile === 'dev') return cache.dev;
|
|
114
|
+
|
|
115
|
+
return isVersionedPublicAssetRequest(res, filePath) ? cache.versioned : cache.unversioned;
|
|
116
|
+
};
|