proteum 2.2.8 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/AGENTS.md +5 -3
  2. package/README.md +50 -12
  3. package/agents/project/AGENTS.md +47 -10
  4. package/agents/project/CODING_STYLE.md +5 -1
  5. package/agents/project/client/AGENTS.md +2 -0
  6. package/agents/project/diagnostics.md +8 -5
  7. package/agents/project/optimizations.md +1 -0
  8. package/agents/project/root/AGENTS.md +18 -10
  9. package/agents/project/tests/AGENTS.md +6 -1
  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/check.ts +21 -3
  13. package/cli/commands/configure.ts +1 -0
  14. package/cli/commands/connect.ts +40 -4
  15. package/cli/commands/diagnose.ts +136 -5
  16. package/cli/commands/doctor.ts +24 -4
  17. package/cli/commands/explain.ts +105 -6
  18. package/cli/commands/mcp.ts +16 -0
  19. package/cli/commands/orient.ts +66 -3
  20. package/cli/commands/perf.ts +118 -13
  21. package/cli/commands/runtime.ts +151 -0
  22. package/cli/commands/trace.ts +116 -21
  23. package/cli/mcp/provider.ts +365 -0
  24. package/cli/mcp/stdio.ts +16 -0
  25. package/cli/presentation/commands.ts +79 -22
  26. package/cli/presentation/devSession.ts +2 -0
  27. package/cli/runtime/commands.ts +95 -12
  28. package/cli/utils/agentOutput.ts +46 -0
  29. package/cli/utils/agents.ts +225 -48
  30. package/common/dev/inspection.ts +30 -9
  31. package/common/dev/mcpPayloads.ts +736 -0
  32. package/common/dev/mcpServer.ts +254 -0
  33. package/docs/agent-routing.md +126 -0
  34. package/docs/dev-commands.md +2 -0
  35. package/docs/dev-sessions.md +2 -1
  36. package/docs/diagnostics.md +68 -23
  37. package/docs/mcp.md +149 -0
  38. package/docs/migrate-from-2.1.3.md +15 -5
  39. package/docs/request-tracing.md +12 -6
  40. package/eslint.js +220 -0
  41. package/package.json +2 -1
  42. package/server/app/devMcp.ts +159 -0
  43. package/server/services/router/http/cache.ts +116 -0
  44. package/server/services/router/http/index.ts +94 -35
  45. package/server/services/router/index.ts +8 -11
  46. package/tests/agents-utils.test.cjs +89 -11
  47. package/tests/dev-transpile-watch.test.cjs +117 -8
  48. package/tests/eslint-rules.test.cjs +110 -0
  49. package/tests/inspection.test.cjs +67 -0
  50. package/tests/mcp.test.cjs +127 -0
  51. package/tests/router-cache-config.test.cjs +74 -0
package/docs/mcp.md ADDED
@@ -0,0 +1,149 @@
1
+ # Proteum MCP
2
+
3
+ Proteum exposes read-only MCP surfaces for agents that need repeated, compact access to project and runtime state.
4
+
5
+ There are two entrypoints:
6
+
7
+ - `proteum mcp`: a stdio MCP server launched from an app or worktree.
8
+ - `proteum dev`: a dev-hosted MCP endpoint at `/__proteum/mcp`.
9
+
10
+ Both entrypoints expose the same tool/resource contract. The CLI remains the source of truth for `dev`, `build`, `check`, `refresh`, migrations, and reproducible terminal validation. MCP is for low-token reads, runtime snapshots, trace/perf/log summaries, and progressive detail loading.
11
+
12
+ ## Stdio Server
13
+
14
+ Configure an MCP client to launch the server from the app root:
15
+
16
+ ```bash
17
+ proteum mcp
18
+ ```
19
+
20
+ Useful options:
21
+
22
+ ```bash
23
+ proteum mcp --cwd /path/to/app
24
+ proteum mcp --url http://localhost:3101
25
+ proteum mcp --session-file var/run/proteum/dev/agents/task.json
26
+ ```
27
+
28
+ The stdio server reads manifest, instruction, and tracked-session data from disk. When a live dev server is known through `--url`, a tracked session file, or the manifest router port, runtime tools read the dev endpoints directly instead of spawning CLI commands.
29
+
30
+ Use stdio MCP when the agent environment can launch a long-lived tool server but does not already have direct access to the running `proteum dev` HTTP transport.
31
+
32
+ ## Dev Runtime Endpoint
33
+
34
+ During `proteum dev`, the app exposes the same MCP contract through the official streamable HTTP transport:
35
+
36
+ ```text
37
+ POST /__proteum/mcp
38
+ GET /__proteum/mcp
39
+ DELETE /__proteum/mcp
40
+ ```
41
+
42
+ This endpoint is dev-only and local-tooling-only. It uses the running app's in-memory diagnostics, trace, perf, and log stores where possible, so runtime tools avoid process startup and avoid dumping full trace payloads by default.
43
+
44
+ The dev session UI and ready banner print:
45
+
46
+ ```text
47
+ mcp http://localhost:<port>/__proteum/mcp
48
+ MCP: http://localhost:<port>/__proteum/mcp
49
+ ```
50
+
51
+ Use dev-hosted MCP when an agent is iterating against an already running app. It is the fastest path for repeated `runtime_status`, `orient`, `diagnose`, `trace_*`, `perf_*`, and `logs_tail` reads.
52
+
53
+ ## Output Contract
54
+
55
+ MCP tool and resource payloads are compact single-line JSON strings in this shape:
56
+
57
+ ```json
58
+ {"ok":true,"format":"proteum-mcp-v1","summary":"...","data":{},"nextActions":[],"omitted":[]}
59
+ ```
60
+
61
+ Outputs are capped by default:
62
+
63
+ - trace output shows counts, failed calls, error events, hot calls, and hot SQL first
64
+ - logs are limited and truncated
65
+ - diagnostics and perf rows are capped
66
+ - full trace detail is paginated with `detail: "full"`, `limit`, and `offset`
67
+
68
+ 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.
69
+
70
+ ## Tools
71
+
72
+ The v1 tools are read-only:
73
+
74
+ | Tool | Purpose |
75
+ | --- | --- |
76
+ | `runtime_status` | Manifest summary, selected runtime, tracked sessions, health, and MCP URL |
77
+ | `orient` | Owner, instruction routing, connected boundaries, and next actions |
78
+ | `instructions_resolve` | Selected instruction files for a query, with short previews |
79
+ | `explain_summary` | Compact manifest summary or owner lookup |
80
+ | `doctor` | Compact manifest and optional contract diagnostics |
81
+ | `diagnose` | Composite diagnosis for an existing route, query, or request trace |
82
+ | `trace_latest` | Compact latest trace summary, with optional paginated detail |
83
+ | `trace_show` | Compact or paginated detail for a specific request trace |
84
+ | `perf_top` | Hot-path perf rollup |
85
+ | `perf_request` | One-request waterfall and attribution |
86
+ | `logs_tail` | Capped recent server logs |
87
+
88
+ MCP v1 intentionally does not start/stop dev servers, refresh generated files, arm traces, export traces, write files, run migrations, or execute app commands.
89
+
90
+ ## Resources
91
+
92
+ Static resources expose common compact reads:
93
+
94
+ - `proteum://runtime/status`
95
+ - `proteum://instructions/router`
96
+ - `proteum://manifest/summary`
97
+ - `proteum://trace/latest/summary`
98
+ - `proteum://perf/top`
99
+
100
+ ## CLI Boundary
101
+
102
+ Use CLI commands when the result must be reproducible as a terminal step, CI-like validation, or human-shareable command output:
103
+
104
+ ```bash
105
+ proteum dev
106
+ proteum build --prod
107
+ proteum check
108
+ proteum refresh
109
+ proteum diagnose /dashboard --port 3101
110
+ proteum verify request /dashboard --port 3101
111
+ proteum trace show <requestId> --events --port 3101
112
+ ```
113
+
114
+ Use MCP when an agent is asking the same running app for repeated state:
115
+
116
+ ```text
117
+ runtime_status
118
+ instructions_resolve
119
+ orient
120
+ diagnose
121
+ trace_latest
122
+ perf_request
123
+ logs_tail
124
+ ```
125
+
126
+ ## Routing Guidance
127
+
128
+ Use these surfaces in this order:
129
+
130
+ 1. Agent instructions for hard safety policy and routing rules.
131
+ 2. MCP for repeated reads, runtime status, instruction selection, traces, perf, and logs.
132
+ 3. Compact CLI for reproducible terminal validation and CI-like checks.
133
+ 4. Full CLI escape hatches only after compact MCP/CLI output identifies the missing detail.
134
+
135
+ ## Benchmark
136
+
137
+ The Product `/domains` diagnostic loop measured on May 7, 2026 used `ceil(UTF-8 bytes / 4)` as an output-token estimate:
138
+
139
+ | Workflow | Approx output tokens | Elapsed |
140
+ | --- | ---: | ---: |
141
+ | Compact CLI single loop | 6,286 | 4,809 ms |
142
+ | Dev-hosted HTTP MCP single loop | 5,211 | 232 ms |
143
+ | Stdio MCP single loop | 5,526 | 900 ms |
144
+ | Compact CLI repeated reads x3 | 11,660 | 9,572 ms |
145
+ | Dev-hosted HTTP MCP repeated reads x3 | 10,537 | 214 ms |
146
+
147
+ The benchmark included the routed instruction docs separately. Reading the four selected instruction files once was about 4,881 estimated output tokens; refreshing the instruction routing through MCP `instructions_resolve` was about 722 estimated output tokens.
148
+
149
+ The practical rule from the benchmark is: use CLI for the first reproducible check and validation record, then use MCP for repeated reads against the same app/runtime.
@@ -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 --url http://localhost:<port>` for repeated agent reads against a running 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
 
@@ -331,6 +336,9 @@ 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`
341
+ - `npx proteum mcp --url http://localhost:<port>`
334
342
  - `npx proteum diagnose / --port <port>`
335
343
  - `npx proteum perf top --port <port>`
336
344
  - `npx proteum trace latest --port <port>`
@@ -370,6 +378,8 @@ Then boot the app and verify the live runtime:
370
378
 
371
379
  ```bash
372
380
  npx proteum dev --port 3010
381
+ npx proteum runtime status
382
+ npx proteum mcp --url http://localhost:3010
373
383
  npx proteum diagnose / --port 3010
374
384
  npx proteum trace latest --port 3010
375
385
  ```
@@ -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, use MCP `trace_latest`, `trace_show`, `perf_top`, and `perf_request` for repeated reads against the same running app. 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
41
+ - use MCP `runtime_status` 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/eslint.js CHANGED
@@ -14,6 +14,220 @@ const defaultIgnores = [
14
14
  const createZodTypeFactorySelector = (factoryName) =>
15
15
  `CallExpression[callee.type='MemberExpression'][callee.computed=false][callee.object.type='Identifier'][callee.object.name=/^(schema|z|zod)$/][callee.property.name='${factoryName}']`;
16
16
 
17
+ const skippedTraversalKeys = new Set(['parent', 'loc', 'range', 'tokens', 'comments']);
18
+
19
+ const traverseNode = (node, visit, parent = null, parentKey = null) => {
20
+ if (!node || typeof node !== 'object') return;
21
+
22
+ visit(node, parent, parentKey);
23
+
24
+ for (const key of Object.keys(node)) {
25
+ if (skippedTraversalKeys.has(key)) continue;
26
+
27
+ const value = node[key];
28
+ if (Array.isArray(value)) {
29
+ value.forEach((child) => {
30
+ if (child && typeof child.type === 'string') traverseNode(child, visit, node, key);
31
+ });
32
+ continue;
33
+ }
34
+
35
+ if (value && typeof value.type === 'string') traverseNode(value, visit, node, key);
36
+ }
37
+ };
38
+
39
+ const collectPatternNames = (node, names = []) => {
40
+ if (!node) return names;
41
+
42
+ if (node.type === 'Identifier') {
43
+ names.push(node.name);
44
+ return names;
45
+ }
46
+
47
+ if (node.type === 'RestElement') return collectPatternNames(node.argument, names);
48
+ if (node.type === 'AssignmentPattern') return collectPatternNames(node.left, names);
49
+ if (node.type === 'TSParameterProperty') return collectPatternNames(node.parameter, names);
50
+
51
+ if (node.type === 'ArrayPattern') {
52
+ node.elements.forEach((element) => collectPatternNames(element, names));
53
+ return names;
54
+ }
55
+
56
+ if (node.type === 'ObjectPattern') {
57
+ node.properties.forEach((property) => {
58
+ if (property.type === 'Property') collectPatternNames(property.value, names);
59
+ if (property.type === 'RestElement') collectPatternNames(property.argument, names);
60
+ });
61
+ }
62
+
63
+ return names;
64
+ };
65
+
66
+ const nodeReferencesName = (node, names) => {
67
+ let references = false;
68
+
69
+ traverseNode(node, (child, parent, parentKey) => {
70
+ if (child.type !== 'Identifier' || !names.includes(child.name)) return;
71
+ if (parent?.type === 'MemberExpression' && parentKey === 'property' && parent.computed === false) return;
72
+ if (parent?.type === 'Property' && parentKey === 'key' && parent.computed === false) return;
73
+ if (parent?.type === 'MethodDefinition' && parentKey === 'key' && parent.computed === false) return;
74
+ if (parent?.type === 'PropertyDefinition' && parentKey === 'key' && parent.computed === false) return;
75
+
76
+ references = true;
77
+ });
78
+
79
+ return references;
80
+ };
81
+
82
+ const collectDerivedErrorNames = (node, baseNames) => {
83
+ const names = [...baseNames];
84
+ let changed = true;
85
+
86
+ while (changed) {
87
+ changed = false;
88
+
89
+ traverseNode(node, (child) => {
90
+ if (child.type === 'VariableDeclarator' && child.id?.type === 'Identifier' && nodeReferencesName(child.init, names)) {
91
+ if (!names.includes(child.id.name)) {
92
+ names.push(child.id.name);
93
+ changed = true;
94
+ }
95
+ }
96
+
97
+ if (
98
+ child.type === 'AssignmentExpression' &&
99
+ child.left?.type === 'Identifier' &&
100
+ nodeReferencesName(child.right, names)
101
+ ) {
102
+ if (!names.includes(child.left.name)) {
103
+ names.push(child.left.name);
104
+ changed = true;
105
+ }
106
+ }
107
+ });
108
+ }
109
+
110
+ return names;
111
+ };
112
+
113
+ const getCalleePropertyName = (callee) => {
114
+ if (!callee) return null;
115
+ if (callee.type === 'Identifier') return callee.name;
116
+ if (callee.type === 'MemberExpression') {
117
+ if (callee.property.type === 'Identifier') return callee.property.name;
118
+ if (callee.property.type === 'Literal') return String(callee.property.value);
119
+ }
120
+
121
+ return null;
122
+ };
123
+
124
+ const preservingCallNames = new Set([
125
+ 'captureError',
126
+ 'captureException',
127
+ 'consoleError',
128
+ 'handleError',
129
+ 'logError',
130
+ 'onError',
131
+ 'reject',
132
+ 'reportError',
133
+ 'setError',
134
+ 'setErrorMessage',
135
+ ]);
136
+
137
+ const preservingMemberNames = new Set(['captureException', 'error', 'handleError', 'reject', 'warn']);
138
+
139
+ const isPreservingCall = (callExpression, names) => {
140
+ const propertyName = getCalleePropertyName(callExpression.callee);
141
+ if (!propertyName) return false;
142
+
143
+ const isKnownPreserver =
144
+ preservingCallNames.has(propertyName) ||
145
+ (callExpression.callee.type === 'MemberExpression' && preservingMemberNames.has(propertyName));
146
+
147
+ return isKnownPreserver && nodeReferencesName(callExpression, names);
148
+ };
149
+
150
+ const handlerPreservesCaughtError = (node, names) => {
151
+ let preserves = false;
152
+
153
+ traverseNode(node, (child) => {
154
+ if (child.type === 'ThrowStatement' && nodeReferencesName(child.argument, names)) preserves = true;
155
+ if (child.type === 'CallExpression' && isPreservingCall(child, names)) preserves = true;
156
+ });
157
+
158
+ return preserves;
159
+ };
160
+
161
+ const directPromiseCatchHandlers = new Set([
162
+ 'captureError',
163
+ 'captureException',
164
+ 'consoleError',
165
+ 'handleError',
166
+ 'logError',
167
+ 'reportError',
168
+ ]);
169
+
170
+ const isDirectPromiseCatchHandler = (node) => {
171
+ const name = getCalleePropertyName(node);
172
+ if (name && directPromiseCatchHandlers.has(name)) return true;
173
+ return node?.type === 'MemberExpression' && node.object?.type === 'Identifier' && node.object.name === 'console';
174
+ };
175
+
176
+ const createSwallowedErrorRule = () => ({
177
+ meta: {
178
+ type: 'problem',
179
+ docs: {
180
+ description: 'Require caught errors to be preserved, reported, rethrown, or surfaced with original detail.',
181
+ },
182
+ messages: {
183
+ missingParam:
184
+ 'Caught errors must be bound and preserved. Use `catch (error)` and rethrow, report, route, or surface original details.',
185
+ unusedParam:
186
+ 'Caught error `{{name}}` is discarded. Rethrow it, report it, route it to app error handling, or surface its original details.',
187
+ unpreserved:
188
+ 'Caught error `{{name}}` is used but not preserved. Rethrow it, report it, route it, or surface original error details.',
189
+ },
190
+ schema: [],
191
+ },
192
+ create(context) {
193
+ const reportHandler = (node, params, body) => {
194
+ const names = params.flatMap((param) => collectPatternNames(param));
195
+ if (names.length === 0) {
196
+ context.report({ node, messageId: 'missingParam' });
197
+ return;
198
+ }
199
+
200
+ const referencedName = names.find((name) => nodeReferencesName(body, [name]));
201
+ if (!referencedName) {
202
+ context.report({ node, messageId: 'unusedParam', data: { name: names[0] } });
203
+ return;
204
+ }
205
+
206
+ if (!handlerPreservesCaughtError(body, collectDerivedErrorNames(body, names))) {
207
+ context.report({ node, messageId: 'unpreserved', data: { name: referencedName } });
208
+ }
209
+ };
210
+
211
+ return {
212
+ CatchClause(node) {
213
+ reportHandler(node, node.param ? [node.param] : [], node.body);
214
+ },
215
+ "CallExpression[callee.type='MemberExpression'][callee.property.name='catch']"(node) {
216
+ const [handler] = node.arguments;
217
+ if (!handler) return;
218
+ if (isDirectPromiseCatchHandler(handler)) return;
219
+
220
+ if (handler.type !== 'ArrowFunctionExpression' && handler.type !== 'FunctionExpression') {
221
+ context.report({ node: handler, messageId: 'missingParam' });
222
+ return;
223
+ }
224
+
225
+ reportHandler(handler, handler.params, handler.body);
226
+ },
227
+ };
228
+ },
229
+ });
230
+
17
231
  const createProteumEslintConfig = ({ ignores = [] } = {}) => [
18
232
  {
19
233
  ignores: [...defaultIgnores, ...ignores],
@@ -37,12 +251,18 @@ const createProteumEslintConfig = ({ ignores = [] } = {}) => [
37
251
  },
38
252
  plugins: {
39
253
  '@typescript-eslint': tseslint.plugin,
254
+ proteum: {
255
+ rules: {
256
+ 'no-swallowed-caught-error': createSwallowedErrorRule(),
257
+ },
258
+ },
40
259
  react: reactPlugin,
41
260
  'react-hooks': reactHooksPlugin,
42
261
  'jsx-a11y': jsxA11yPlugin,
43
262
  },
44
263
  rules: {
45
264
  '@typescript-eslint/no-explicit-any': 'error',
265
+ 'proteum/no-swallowed-caught-error': 'error',
46
266
  'no-restricted-syntax': [
47
267
  'error',
48
268
  {
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.8",
4
+ "version": "2.3.0",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/proteum.git",
7
7
  "license": "MIT",
@@ -21,6 +21,7 @@
21
21
  "@babel/traverse": "^7.29.0",
22
22
  "@babel/types": "^7.29.0",
23
23
  "@inkjs/ui": "^2.0.0",
24
+ "@modelcontextprotocol/sdk": "^1.29.0",
24
25
  "@prisma/adapter-mariadb": "7.2.0",
25
26
  "@prisma/client": "7.2.0",
26
27
  "@rspack/core": "^1.7.9",
@@ -0,0 +1,159 @@
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
+ compactTraceResponse,
14
+ resolveInstructionRouting,
15
+ } from '@common/dev/mcpPayloads';
16
+ import type { TProteumMcpProvider } from '@common/dev/mcpServer';
17
+ import type { TDevConsoleLogLevel } from '@common/dev/console';
18
+ import type { TPerfGroupBy } from '@common/dev/performance';
19
+
20
+ import type { Application } from './index';
21
+
22
+ export const createRuntimeProteumMcpProvider = ({
23
+ app,
24
+ publicUrl,
25
+ routerPort,
26
+ }: {
27
+ app: Application;
28
+ publicUrl: string;
29
+ routerPort: number;
30
+ }): TProteumMcpProvider => {
31
+ const diagnostics = () => app.getDevDiagnostics();
32
+ const readManifest = () => diagnostics().readManifest();
33
+ const assertTraceEnabled = () => {
34
+ if (!app.container.Trace.isDevTraceEnabled()) {
35
+ throw new Error('Proteum dev trace is not enabled for this runtime.');
36
+ }
37
+ };
38
+
39
+ const provider: TProteumMcpProvider = {
40
+ async runtimeStatus(_input: Record<string, never> = {}) {
41
+ const manifest = readManifest();
42
+ const doctor = buildDoctorResponse(manifest);
43
+ const contracts = buildContractsDoctorResponse(manifest);
44
+
45
+ return buildRuntimeStatusPayload({
46
+ appRoot: app.container.path.root,
47
+ health: {
48
+ reachable: true,
49
+ doctor: doctor.summary,
50
+ contracts: contracts.summary,
51
+ },
52
+ manifest,
53
+ runtime: {
54
+ publicUrl,
55
+ routerPort,
56
+ source: 'proteum-dev-runtime',
57
+ mcpUrl: `${publicUrl}/__proteum/mcp`,
58
+ traceEnabled: app.container.Trace.isDevTraceEnabled(),
59
+ profilerEnabled: app.container.Trace.isProfilingEnabled(),
60
+ connectedProjects: Object.entries(app.connectedProjects || {}).map(([namespace, project]) => ({
61
+ namespace,
62
+ urlInternal: (project as { urlInternal?: string }).urlInternal,
63
+ })),
64
+ },
65
+ });
66
+ },
67
+ async orient({ query }) {
68
+ return compactOrientationResponse(buildOrientationResponse(readManifest(), query));
69
+ },
70
+ async instructionsResolve({ query }) {
71
+ return resolveInstructionRouting({ appRoot: app.container.path.root, query });
72
+ },
73
+ async explainSummary({ query }) {
74
+ const manifest = readManifest();
75
+ const normalizedQuery = query?.trim();
76
+
77
+ return compactExplainSummary({
78
+ manifest,
79
+ owner: normalizedQuery ? explainOwner(manifest, normalizedQuery) : undefined,
80
+ query: normalizedQuery,
81
+ });
82
+ },
83
+ async doctor({ contracts = true }) {
84
+ const manifest = readManifest();
85
+
86
+ return compactDoctorResponse({
87
+ contracts: contracts ? buildContractsDoctorResponse(manifest) : undefined,
88
+ doctor: buildDoctorResponse(manifest),
89
+ });
90
+ },
91
+ async diagnose({
92
+ logsLevel = 'warn',
93
+ logsLimit = 40,
94
+ path,
95
+ query,
96
+ requestId,
97
+ }: {
98
+ logsLevel?: TDevConsoleLogLevel;
99
+ logsLimit?: number;
100
+ path?: string;
101
+ query?: string;
102
+ requestId?: string;
103
+ }) {
104
+ return compactDiagnoseResponse(
105
+ diagnostics().diagnose({
106
+ logsLevel,
107
+ logsLimit,
108
+ path,
109
+ query,
110
+ requestId,
111
+ }),
112
+ );
113
+ },
114
+ async traceLatest({ detail, limit, offset }) {
115
+ assertTraceEnabled();
116
+ const request = app.container.Trace.getLatestRequest();
117
+ if (!request) throw new Error('No request trace is available yet.');
118
+
119
+ return compactTraceResponse({ detail, limit, offset, request });
120
+ },
121
+ async traceShow({ detail, limit, offset, requestId }) {
122
+ assertTraceEnabled();
123
+ const request = app.container.Trace.getRequest(requestId);
124
+ if (!request) throw new Error(`Trace ${requestId} was not found.`);
125
+
126
+ return compactTraceResponse({ detail, limit, offset, request });
127
+ },
128
+ async perfTop({ groupBy = 'path', limit = 12, since = 'today' }) {
129
+ return compactPerfTopResponse(
130
+ diagnostics().perfTop({
131
+ groupBy: groupBy as TPerfGroupBy,
132
+ limit,
133
+ since,
134
+ }),
135
+ );
136
+ },
137
+ async perfRequest({ query }) {
138
+ return compactPerfRequestResponse(diagnostics().perfRequest(query));
139
+ },
140
+ async logsTail({ level = 'warn', limit = 40 }) {
141
+ return compactLogsResponse({
142
+ level,
143
+ limit,
144
+ response: diagnostics().readLogs(limit, level),
145
+ });
146
+ },
147
+ async readResource(uri) {
148
+ if (uri === 'proteum://runtime/status') return await provider.runtimeStatus({});
149
+ if (uri === 'proteum://instructions/router') return await provider.instructionsResolve({});
150
+ if (uri === 'proteum://manifest/summary') return await provider.explainSummary({});
151
+ if (uri === 'proteum://trace/latest/summary') return await provider.traceLatest({});
152
+ if (uri === 'proteum://perf/top') return await provider.perfTop({});
153
+
154
+ throw new Error(`Unknown Proteum MCP resource: ${uri}`);
155
+ },
156
+ };
157
+
158
+ return provider;
159
+ };