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.
Files changed (59) hide show
  1. package/AGENTS.md +10 -4
  2. package/README.md +58 -15
  3. package/agents/project/AGENTS.md +53 -10
  4. package/agents/project/DOCUMENTATION.md +1326 -0
  5. package/agents/project/app-root/AGENTS.md +2 -2
  6. package/agents/project/diagnostics.md +12 -7
  7. package/agents/project/optimizations.md +1 -0
  8. package/agents/project/root/AGENTS.md +24 -9
  9. package/agents/project/tests/AGENTS.md +7 -0
  10. package/agents/project/tests/e2e/AGENTS.md +13 -0
  11. package/agents/project/tests/e2e/REAL_WORLD_JOURNEY_TESTS.md +192 -0
  12. package/cli/commands/connect.ts +40 -4
  13. package/cli/commands/dev.ts +148 -25
  14. package/cli/commands/diagnose.ts +138 -5
  15. package/cli/commands/doctor.ts +24 -4
  16. package/cli/commands/explain.ts +134 -6
  17. package/cli/commands/mcp.ts +133 -0
  18. package/cli/commands/orient.ts +93 -3
  19. package/cli/commands/perf.ts +118 -13
  20. package/cli/commands/runtime.ts +234 -0
  21. package/cli/commands/trace.ts +116 -21
  22. package/cli/mcp/router.ts +1010 -0
  23. package/cli/presentation/commands.ts +93 -26
  24. package/cli/presentation/devSession.ts +2 -0
  25. package/cli/presentation/help.ts +1 -1
  26. package/cli/runtime/commands.ts +215 -24
  27. package/cli/runtime/devSessions.ts +328 -2
  28. package/cli/runtime/mcpDaemon.ts +288 -0
  29. package/cli/runtime/ports.ts +151 -0
  30. package/cli/utils/agentOutput.ts +46 -0
  31. package/cli/utils/agents.ts +194 -51
  32. package/cli/utils/appRoots.ts +232 -0
  33. package/common/dev/diagnostics.ts +1 -1
  34. package/common/dev/inspection.ts +22 -7
  35. package/common/dev/mcpPayloads.ts +1150 -0
  36. package/common/dev/mcpServer.ts +287 -0
  37. package/docs/agent-routing.md +137 -0
  38. package/docs/dev-commands.md +2 -0
  39. package/docs/dev-sessions.md +4 -1
  40. package/docs/diagnostics.md +70 -24
  41. package/docs/mcp.md +206 -0
  42. package/docs/migrate-from-2.1.3.md +14 -6
  43. package/docs/request-tracing.md +12 -6
  44. package/package.json +11 -3
  45. package/server/app/devMcp.ts +204 -0
  46. package/server/services/router/http/cache.ts +116 -0
  47. package/server/services/router/http/index.ts +94 -35
  48. package/server/services/router/index.ts +8 -11
  49. package/server/services/router/request/ip.test.cjs +0 -1
  50. package/tests/agents-utils.test.cjs +92 -14
  51. package/tests/cli-mcp-command.test.cjs +262 -0
  52. package/tests/codex-mcp-usage.test.cjs +307 -0
  53. package/tests/dev-sessions.test.cjs +113 -0
  54. package/tests/dev-transpile-watch.test.cjs +117 -9
  55. package/tests/eslint-rules.test.cjs +0 -1
  56. package/tests/inspection.test.cjs +66 -0
  57. package/tests/mcp.test.cjs +873 -0
  58. package/tests/router-cache-config.test.cjs +73 -0
  59. 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 `--json` for automation
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 explain --json`
315
- - `npx proteum doctor --json`
316
- - `npx proteum connect --json`
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 your local workflow starts multiple dev servers, this is the current supported model.
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
  ```
@@ -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 `__proteum/perf` HTTP endpoints
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
- - read the default port from `PORT` or `./.proteum/manifest.json`
36
- - check whether a dev server is already running on that port
37
- - if it is, inspect `proteum trace requests`, `proteum trace latest`, and `proteum trace show <requestId>` first so you can capture past errors and their context
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>` only when the lower-level event stream is still needed.
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.2.9",
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": "^16.9.1",
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
+ };