@happyvertical/smrt-dev-mcp 0.47.1 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +10 -9
- package/README.md +151 -66
- package/dist/dev-plane.d.ts +48 -0
- package/dist/dev-plane.d.ts.map +1 -0
- package/dist/dev-plane.js +211 -0
- package/dist/dev-plane.js.map +1 -0
- package/dist/http-TeoaK9Xr.js +168 -0
- package/dist/http-TeoaK9Xr.js.map +1 -0
- package/dist/http.d.ts +96 -0
- package/dist/http.d.ts.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +86 -428
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +27 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/{knowledge-DmZ-7GO9.js → knowledge-BOsSAdPo.js} +82 -655
- package/dist/knowledge-BOsSAdPo.js.map +1 -0
- package/dist/knowledge.js +2 -2
- package/dist/observation-VJgHaPps.js +802 -0
- package/dist/observation-VJgHaPps.js.map +1 -0
- package/dist/runtime.d.ts +21 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +18 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server-info.d.ts +3 -0
- package/dist/server-info.d.ts.map +1 -0
- package/dist/tool-catalog-Zl-bcimC.js +643 -0
- package/dist/tool-catalog-Zl-bcimC.js.map +1 -0
- package/dist/tool-catalog.d.ts.map +1 -1
- package/dist/tools/introspect-project.d.ts +8 -0
- package/dist/tools/introspect-project.d.ts.map +1 -1
- package/dist/tools/runtime/boot.d.ts +70 -0
- package/dist/tools/runtime/boot.d.ts.map +1 -0
- package/dist/tools/runtime/connection.d.ts +19 -0
- package/dist/tools/runtime/connection.d.ts.map +1 -1
- package/dist/tools/runtime/observation.d.ts +43 -0
- package/dist/tools/runtime/observation.d.ts.map +1 -0
- package/dist/tools/runtime/tools.d.ts +19 -0
- package/dist/tools/runtime/tools.d.ts.map +1 -1
- package/package.json +14 -6
- package/skills/smrt-code-review/SKILL.md +1 -1
- package/dist/knowledge-DmZ-7GO9.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -13,14 +13,10 @@ deterministic SMRT ecosystem knowledge, and portable agent workflows.
|
|
|
13
13
|
| `reflect-knowledge` | Reports deterministic SMRT + HappyVertical SDK knowledge coverage and freshness |
|
|
14
14
|
| `reflect-domain-knowledge` | Reports downstream/domain artifact coverage and freshness |
|
|
15
15
|
| `check-knowledge-freshness` | Runs deterministic agent-doc and stale-reference checks |
|
|
16
|
-
| `
|
|
17
|
-
| `
|
|
18
|
-
| `build-domain-review-context` | Domain-scoped review context builder with `scope`/`package` filters |
|
|
19
|
-
| `smrt-review` | Returns deterministic review findings and a reusable review prompt bundle |
|
|
20
|
-
| `build-architecture-context` | Builds architecture planning context from an idea or docs |
|
|
16
|
+
| `build-context` | One context entry: `task: review` (file-anchored findings + `reviewHints` + prompt bundle) or `task: architecture` (ranked packages + recommendations); replaces the four `build-*-context` tools (#2780) |
|
|
17
|
+
| `smrt-review` | Deprecated name for `build-context` `task: review`; removed next minor |
|
|
21
18
|
| `build-package-specialist-context` | Builds deterministic package-specific Workbench context and source references |
|
|
22
|
-
| `
|
|
23
|
-
| `smrt-architecture` | Recommends SMRT/SDK packages, object-model sketch, risks, and questions |
|
|
19
|
+
| `smrt-architecture` | Deprecated name for `build-context` `task: architecture`; removed next minor |
|
|
24
20
|
| `list-agent-skills` | Lists bundled harness-agnostic agent skills |
|
|
25
21
|
| `get-agent-skill` | Returns a bundled agent skill as Markdown plus optional references |
|
|
26
22
|
| `migration-status` | Live migration status from the `_smrt_schema_migrations` system table — completed/running/failed/rolled_back counts, latest completed and failed migrations (runtime provenance; read-only; optional live DB, #1824) |
|
|
@@ -29,6 +25,9 @@ deterministic SMRT ecosystem knowledge, and portable agent workflows.
|
|
|
29
25
|
| `dispatch-health` | Live dispatch health from the `_smrt_dispatch`/`_smrt_dispatch_subscriptions` system tables — stuck/pending by type/status, subscription topology; payloads/metadata never read (runtime provenance; read-only, #1824) |
|
|
30
26
|
| `recent-changes` | Tail of the `_smrt_changes` append-only change feed with cursor semantics, filterable by table and tenant (runtime provenance; read-only, #1824) |
|
|
31
27
|
| `registry-drift` | Registry drift report — `_smrt_registry` is retired and reported as such, never queried or fabricated (read-only, #1824) |
|
|
28
|
+
| `runtime-registry` | Sanitized snapshot of the booted `ObjectRegistry` from project + installed manifests; no project code executed (booted provenance; read-only, #1831) |
|
|
29
|
+
| `runtime-object` | One booted object's sanitized definition plus generated DDL (booted provenance; read-only, #1831) |
|
|
30
|
+
| `runtime-schema-diff` | Booted registry schemas vs live dev DB via the `db:diff` comparer; introspection only, no drops/relaxations (runtime provenance; read-only, #1831) |
|
|
32
31
|
|
|
33
32
|
`pnpm knowledge:check --strict --format markdown` compares this catalog and the
|
|
34
33
|
README parameter tables to the exported `TOOLS` definitions. Add or change a
|
|
@@ -97,7 +96,7 @@ Per package, objects resolve in this order, and the winner is recorded in
|
|
|
97
96
|
The index exposes `coverage` and `diagnostics` (added in `schemaVersion: 2`). Zero
|
|
98
97
|
discovered objects is an **error-grade** diagnostic that names the roots and
|
|
99
98
|
artifact paths checked plus the fix, and it propagates into
|
|
100
|
-
`
|
|
99
|
+
`build-context`/`reflect-*` results and prompt bundles.
|
|
101
100
|
|
|
102
101
|
A workspace-root package has an empty `relativeDirectory`, so package paths are
|
|
103
102
|
built with `packageRelativePath`/`packageDocPaths` rather than interpolation —
|
|
@@ -113,7 +112,7 @@ with YAML frontmatter (`name` and `description`) and harness-neutral body text.
|
|
|
113
112
|
Skill-aware harnesses can parse the frontmatter; other harnesses can ignore it.
|
|
114
113
|
|
|
115
114
|
- `skills/smrt-code-review/SKILL.md` — downstream SMRT code review workflow.
|
|
116
|
-
Agents should fetch it with `get-agent-skill`, then call `
|
|
115
|
+
Agents should fetch it with `get-agent-skill`, then call `build-context` (`task: review`) for
|
|
117
116
|
deterministic context, inspect the actual diff, and produce a findings-first
|
|
118
117
|
review.
|
|
119
118
|
|
|
@@ -144,6 +143,8 @@ launcher or a small wrapper script with an absolute Node path.
|
|
|
144
143
|
- `skills/smrt-code-review/SKILL.md` — downstream SMRT review procedure
|
|
145
144
|
- `src/tools/generate-smrt-class.ts` — class generation logic and package-ready templates
|
|
146
145
|
- `src/tools/introspect-project.ts` — manifest-first project scanning, falling back to `@happyvertical/smrt-scanner`
|
|
146
|
+
- `src/dev-plane.ts` — in-app runtime dev-plane (#2782): `createDevPlane({ token, projectRoot, db })` serves JSON + MCP on one mount with loopback/bearer guards and a positive catalog (`registry-live` + the nine runtime tools); mounted by the generated `_dev/[...tool]` SvelteKit route
|
|
147
|
+
- `src/runtime.ts` — `RUNTIME_TOOLS` map for in-process callers (`smrt dev:runtime` fallback)
|
|
147
148
|
- `src/tools/review-smrt-project.ts` — advisory ecosystem-alignment checks for downstream projects
|
|
148
149
|
|
|
149
150
|
## Gotchas
|
package/README.md
CHANGED
|
@@ -6,6 +6,12 @@ or architecture prompt bundles.
|
|
|
6
6
|
|
|
7
7
|
## Installation
|
|
8
8
|
|
|
9
|
+
New to agent-assisted s-m-r-t development? Start with the end-to-end guide on
|
|
10
|
+
the docs site: [Developing a s-m-r-t app with an agent](https://s-m-r-t.dev/docs/agent-tooling)
|
|
11
|
+
(source: `docs/content/agent-tooling.md`). It walks through install, the
|
|
12
|
+
calls that matter in order, provenance labels, and the optional live-database
|
|
13
|
+
and HTTP setups this README details.
|
|
14
|
+
|
|
9
15
|
```bash
|
|
10
16
|
pnpm install @happyvertical/smrt-dev-mcp
|
|
11
17
|
```
|
|
@@ -148,11 +154,15 @@ Knowledge and introspection tools return a **summary** by default and accept
|
|
|
148
154
|
|
|
149
155
|
- `introspect-project` summary returns one compact record per object
|
|
150
156
|
(`className`, `qualifiedName`, `extends`, `tableName`, `tenantScope`,
|
|
151
|
-
`fieldCount`, compact relationship strings, `mcpOperations`). A response
|
|
152
|
-
exceeds its character budget
|
|
153
|
-
|
|
154
|
-
budget
|
|
155
|
-
|
|
157
|
+
`fieldCount`, compact relationship strings, `mcpOperations`). A response
|
|
158
|
+
that exceeds its character budget, or that was cut short by `limit`, reports
|
|
159
|
+
a `truncated` block with the omitted count and guidance instead of being
|
|
160
|
+
silently cut. `maxChars` overrides the budget, and any truncated response
|
|
161
|
+
carries `nextCursor`; pass it back as `cursor` to read the next alphabetical
|
|
162
|
+
page. `runtime-registry` pages the
|
|
163
|
+
same way (`page.nextCursor`, `limit` default 50) while its summary stays
|
|
164
|
+
global.
|
|
165
|
+
- `build-context` (and its deprecated names `smrt-review`/`smrt-architecture`) lists
|
|
156
166
|
authored `AGENTS.md` and module docs **by path** rather than embedding them,
|
|
157
167
|
and return compact package records. With `detail: "full"`, they embed the package
|
|
158
168
|
AGENTS doc plus module docs matching changed files or request text. Source paths and globs
|
|
@@ -287,6 +297,8 @@ available it falls back to `@happyvertical/smrt-scanner`.
|
|
|
287
297
|
| `manifestPath` | `string` | No | Explicit manifest artifact path |
|
|
288
298
|
| `detail` | `'summary' \| 'full'` | No | Default `summary`. `full` returns field, schema, and method detail |
|
|
289
299
|
| `maxChars` | `number` | No | Response character budget; overflow is reported under `truncated` |
|
|
300
|
+
| `cursor` | `string` | No | Resume after this `className` (alphabetical); pass a previous response's `nextCursor` |
|
|
301
|
+
| `limit` | `number` | No | Maximum objects per page, applied before the character budget |
|
|
290
302
|
| `includeFields` | `boolean` | No | Include field details (`detail: "full"` only) |
|
|
291
303
|
| `includeRelationships` | `boolean` | No | Analyze relationships (`detail: "full"` only) |
|
|
292
304
|
| `includeMethods` | `boolean` | No | Include public method details (`detail: "full"` only) |
|
|
@@ -337,49 +349,36 @@ Run the same deterministic freshness checks exposed by `pnpm knowledge:check`.
|
|
|
337
349
|
| `changed` | `boolean` | No | Limit stale-pattern checks to changed files |
|
|
338
350
|
| `strict` | `boolean` | No | Treat stale-pattern findings as errors |
|
|
339
351
|
|
|
340
|
-
### `
|
|
352
|
+
### `build-context`
|
|
341
353
|
|
|
342
|
-
|
|
343
|
-
`
|
|
354
|
+
Build model-ready context for a task. `task: "review"` routes `changedFiles`
|
|
355
|
+
and `focus` to package experts and returns file-anchored deterministic
|
|
356
|
+
findings, package-level `reviewHints` (relationship features, generated MCP
|
|
357
|
+
surface: context for a reviewer, not defects), and a prompt bundle.
|
|
358
|
+
`task: "architecture"` ranks packages by the idea text (package names, object
|
|
359
|
+
classes, tables, fields, tags) and returns the bundle plus recommendations.
|
|
360
|
+
Replaces `build-review-context`, `build-domain-review-context`,
|
|
361
|
+
`build-architecture-context`, and `build-domain-architecture-context`, which
|
|
362
|
+
dispatched to the same code.
|
|
344
363
|
|
|
345
364
|
| Parameter | Type | Required | Description |
|
|
346
365
|
|-----------|------|----------|-------------|
|
|
366
|
+
| `task` | `'review' \| 'architecture'` | Yes | What the context is for |
|
|
347
367
|
| `rootDir` | `string` | No | Project root directory (default: cwd) |
|
|
348
|
-
| `
|
|
349
|
-
| `
|
|
350
|
-
| `
|
|
351
|
-
| `
|
|
352
|
-
|
|
353
|
-
### `build-review-context`
|
|
354
|
-
|
|
355
|
-
Select relevant s-m-r-t and HappyVertical SDK package expertise for changed files,
|
|
356
|
-
then return a model-ready prompt bundle.
|
|
357
|
-
|
|
358
|
-
| Parameter | Type | Required | Description |
|
|
359
|
-
|-----------|------|----------|-------------|
|
|
360
|
-
| `rootDir` | `string` | No | Project root directory (default: cwd) |
|
|
361
|
-
| `changedFiles` | `string[]` | No | Files to route to package experts |
|
|
362
|
-
| `focus` | `string` | No | Review focus or concern |
|
|
363
|
-
| `documentation` | `string` | No | Additional docs or notes to include |
|
|
364
|
-
| `detail` | `'summary' \| 'full' \| 'complete'` | No | Default `summary`; `full` embeds package docs and matching modules; `complete` embeds all modules and full package records |
|
|
365
|
-
|
|
366
|
-
### `build-domain-review-context`
|
|
367
|
-
|
|
368
|
-
Domain-scoped alias for `build-review-context`.
|
|
369
|
-
|
|
370
|
-
| Parameter | Type | Required | Description |
|
|
371
|
-
|-----------|------|----------|-------------|
|
|
372
|
-
| `rootDir` | `string` | No | Project root directory (default: cwd) |
|
|
373
|
-
| `changedFiles` | `string[]` | No | Files to route to package experts |
|
|
374
|
-
| `focus` | `string` | No | Review focus or concern |
|
|
375
|
-
| `documentation` | `string` | No | Additional docs or notes to include |
|
|
368
|
+
| `changedFiles` | `string[]` | No | Files to route to package experts (`task: "review"`) |
|
|
369
|
+
| `focus` | `string` | No | Concern to prioritise |
|
|
370
|
+
| `documentation` | `string` | No | Existing docs, notes, or requirements |
|
|
371
|
+
| `idea` | `string` | No | Product or implementation idea (`task: "architecture"`) |
|
|
376
372
|
| `scope` | `'project' \| 'local' \| 'package' \| 'sdk' \| 'installed'` | No | Knowledge source scope (default: `project`) |
|
|
377
373
|
| `package` | `string` | No | Package name or short name to focus |
|
|
374
|
+
| `mode` | `'findings' \| 'prompt-bundle' \| 'both'` | No | Response mode for `task: "review"` (default: `both`) |
|
|
378
375
|
| `detail` | `'summary' \| 'full' \| 'complete'` | No | Default `summary`; `full` embeds package docs and matching modules; `complete` embeds all modules and full package records |
|
|
379
376
|
|
|
380
377
|
### `smrt-review`
|
|
381
378
|
|
|
382
|
-
|
|
379
|
+
Deprecated compatibility name for `build-context` with `task: "review"`;
|
|
380
|
+
removed in the next minor release. Responses carry a `deprecated_tool_name`
|
|
381
|
+
diagnostic.
|
|
383
382
|
|
|
384
383
|
| Parameter | Type | Required | Description |
|
|
385
384
|
|-----------|------|----------|-------------|
|
|
@@ -390,19 +389,6 @@ Return deterministic review findings, a prompt bundle, or both.
|
|
|
390
389
|
| `mode` | `'findings' \| 'prompt-bundle' \| 'both'` | No | Response mode (default: `both`) |
|
|
391
390
|
| `detail` | `'summary' \| 'full' \| 'complete'` | No | Default `summary`; `full` embeds package docs and matching modules; `complete` embeds all modules and full package records |
|
|
392
391
|
|
|
393
|
-
### `build-architecture-context`
|
|
394
|
-
|
|
395
|
-
Select relevant s-m-r-t and SDK package expertise for an idea or documentation,
|
|
396
|
-
then return a model-ready architecture prompt bundle.
|
|
397
|
-
|
|
398
|
-
| Parameter | Type | Required | Description |
|
|
399
|
-
|-----------|------|----------|-------------|
|
|
400
|
-
| `rootDir` | `string` | No | Project root directory (default: cwd) |
|
|
401
|
-
| `idea` | `string` | No | Product or implementation idea |
|
|
402
|
-
| `documentation` | `string` | No | Existing docs or requirements |
|
|
403
|
-
| `focus` | `string` | No | Architecture concern to prioritize |
|
|
404
|
-
| `detail` | `'summary' \| 'full' \| 'complete'` | No | Default `summary`; `full` embeds package docs and matching modules; `complete` embeds all modules and full package records |
|
|
405
|
-
|
|
406
392
|
### `build-package-specialist-context`
|
|
407
393
|
|
|
408
394
|
Build deterministic package-specific context for the Workbench, including the
|
|
@@ -415,24 +401,11 @@ context.
|
|
|
415
401
|
| `package` | `string` | Yes | Package name or short package query |
|
|
416
402
|
| `focus` | `string` | No | Package concern to prioritize |
|
|
417
403
|
|
|
418
|
-
### `build-domain-architecture-context`
|
|
419
|
-
|
|
420
|
-
Domain-scoped alias for `build-architecture-context`.
|
|
421
|
-
|
|
422
|
-
| Parameter | Type | Required | Description |
|
|
423
|
-
|-----------|------|----------|-------------|
|
|
424
|
-
| `rootDir` | `string` | No | Project root directory (default: cwd) |
|
|
425
|
-
| `idea` | `string` | No | Product or implementation idea |
|
|
426
|
-
| `documentation` | `string` | No | Existing docs or requirements |
|
|
427
|
-
| `focus` | `string` | No | Architecture concern to prioritize |
|
|
428
|
-
| `scope` | `'project' \| 'local' \| 'package' \| 'sdk' \| 'installed'` | No | Knowledge source scope (default: `project`) |
|
|
429
|
-
| `package` | `string` | No | Package name or short name to focus |
|
|
430
|
-
| `detail` | `'summary' \| 'full' \| 'complete'` | No | Default `summary`; `full` embeds package docs and matching modules; `complete` embeds all modules and full package records |
|
|
431
|
-
|
|
432
404
|
### `smrt-architecture`
|
|
433
405
|
|
|
434
|
-
|
|
435
|
-
|
|
406
|
+
Deprecated compatibility name for `build-context` with `task: "architecture"`;
|
|
407
|
+
removed in the next minor release. Responses carry a `deprecated_tool_name`
|
|
408
|
+
diagnostic.
|
|
436
409
|
|
|
437
410
|
| Parameter | Type | Required | Description |
|
|
438
411
|
|-----------|------|----------|-------------|
|
|
@@ -535,6 +508,46 @@ static manifest tools. Read-only.
|
|
|
535
508
|
| `dbUrl` | `string` | No | Optional dev database URL override (read-only diagnostics); prefer `SMRT_DEV_DB_URL` or `cli.database` config |
|
|
536
509
|
| `dbType` | `'sqlite' \| 'postgres' \| 'duckdb'` | No | Optional engine hint for `dbUrl` or the environment connection; inferred from the URL scheme when omitted |
|
|
537
510
|
|
|
511
|
+
### `runtime-registry`
|
|
512
|
+
|
|
513
|
+
Sanitized snapshot of the booted `ObjectRegistry`: objects, packages, tables,
|
|
514
|
+
fields, methods, tenancy, and inheritance, projected through a plain-JSON DTO.
|
|
515
|
+
The boot registers the project's `.smrt/manifest.json` (or `dist/manifest.json`)
|
|
516
|
+
and every installed s-m-r-t package manifest; no project code is imported. Booted
|
|
517
|
+
provenance (`booted (registry)`); read-only.
|
|
518
|
+
|
|
519
|
+
| Parameter | Type | Required | Description |
|
|
520
|
+
|-----------|------|----------|-------------|
|
|
521
|
+
| `projectPath` | `string` | No | Project root to boot manifests from (default: the server working directory; ignored by the HTTP host, which boots once) |
|
|
522
|
+
| `objects` | `string[]` | No | Restrict field/method detail to these simple or qualified object names |
|
|
523
|
+
| `detail` | `boolean` | No | Include field and method detail for every object (default: only when `objects` is given) |
|
|
524
|
+
| `cursor` | `string` | No | Resume after this qualified object name; pass a previous response's `page.nextCursor` |
|
|
525
|
+
| `limit` | `number` | No | Objects per page (default 50, capped at 500) |
|
|
526
|
+
|
|
527
|
+
### `runtime-object`
|
|
528
|
+
|
|
529
|
+
One booted object: sanitized fields, methods, tenancy, inheritance, and the
|
|
530
|
+
DDL the registry would generate for it. Booted provenance; read-only.
|
|
531
|
+
|
|
532
|
+
| Parameter | Type | Required | Description |
|
|
533
|
+
|-----------|------|----------|-------------|
|
|
534
|
+
| `projectPath` | `string` | No | Project root to boot manifests from (default: the server working directory; ignored by the HTTP host, which boots once) |
|
|
535
|
+
| `name` | `string` | Yes | Simple or qualified object name |
|
|
536
|
+
| `engine` | `'sqlite' \| 'postgres' \| 'duckdb'` | No | Engine for the DDL preview (default: registry default) |
|
|
537
|
+
|
|
538
|
+
### `runtime-schema-diff`
|
|
539
|
+
|
|
540
|
+
Booted registry schemas versus the live dev database, using the same comparer
|
|
541
|
+
as `db:diff`/`db:migrate`. Introspection only: drops and relaxations are never
|
|
542
|
+
proposed and nothing is executed. Runtime provenance; read-only. Without a
|
|
543
|
+
configured connection it returns a successful static-only result.
|
|
544
|
+
|
|
545
|
+
| Parameter | Type | Required | Description |
|
|
546
|
+
|-----------|------|----------|-------------|
|
|
547
|
+
| `projectPath` | `string` | No | Project root to boot manifests from (default: the server working directory; ignored by the HTTP host, which boots once) |
|
|
548
|
+
| `dbUrl` | `string` | No | Optional dev database URL override (read-only diagnostics); prefer `SMRT_DEV_DB_URL` or `cli.database` config |
|
|
549
|
+
| `dbType` | `'sqlite' \| 'postgres' \| 'duckdb'` | No | Optional engine hint for `dbUrl` or the environment connection; inferred from the URL scheme when omitted |
|
|
550
|
+
|
|
538
551
|
## Runtime Diagnostics (Optional Live DB)
|
|
539
552
|
|
|
540
553
|
The six runtime-diagnostics tools above (`migration-status`, `job-health`,
|
|
@@ -554,6 +567,17 @@ anywhere → every runtime tool returns a successful static-only result
|
|
|
554
567
|
(`provenance: 'static'`, `connected: false`) and the server starts and serves
|
|
555
568
|
all static tools unaffected.
|
|
556
569
|
|
|
570
|
+
Accepted URL forms: `file:///abs/dev.db`, `sqlite:///abs/dev.db`
|
|
571
|
+
(`sqlite:` is normalised to a `file:` URL; a relative `sqlite:` path resolves
|
|
572
|
+
against the server's working directory), a bare path, `postgres://…`, or
|
|
573
|
+
`duckdb:…`. `:memory:` is treated as not configured.
|
|
574
|
+
|
|
575
|
+
Older dev databases whose `_smrt_*` tables predate a column are still
|
|
576
|
+
answered: each reader selects only the columns that exist and reports the rest
|
|
577
|
+
in a `schema_behind` diagnostic (and `schemaBehind` on the data) rather than
|
|
578
|
+
failing the category. A read that does fail carries the redacted driver
|
|
579
|
+
message as its cause.
|
|
580
|
+
|
|
557
581
|
**Provenance labeling.** Live results carry `provenance: 'runtime (live DB)'`;
|
|
558
582
|
static-only results carry `provenance: 'static'`. Never conflate the two: the
|
|
559
583
|
manifest reports what the code declares, runtime tools report what the running
|
|
@@ -569,6 +593,67 @@ not `smrt-app-mcp` and never writes.
|
|
|
569
593
|
(passwords and token query params masked); driver errors pass through a
|
|
570
594
|
redacting normalizer before they can appear in a diagnostic.
|
|
571
595
|
|
|
596
|
+
## Runtime Dev-Plane Host (HTTP, Level 2)
|
|
597
|
+
|
|
598
|
+
`smrt-dev-mcp --http [--port N] [--project DIR]` boots the confined runtime
|
|
599
|
+
once and serves a **positive, read-only** catalog over the stateless
|
|
600
|
+
Streamable HTTP transport (no SSE, no `Mcp-Session-Id`, no sticky routing):
|
|
601
|
+
`runtime-registry`, `runtime-object`, `runtime-schema-diff`, and the six
|
|
602
|
+
live-DB diagnostics above. The static stdio catalog is not mounted, and
|
|
603
|
+
generated CRUD, custom actions, `do()`, and tool-backed `is()` are never
|
|
604
|
+
exposed.
|
|
605
|
+
|
|
606
|
+
- Binds loopback only; the SDK's localhost Host and Origin validation runs on
|
|
607
|
+
every request.
|
|
608
|
+
- Every request needs `Authorization: Bearer <token>`. Set
|
|
609
|
+
`SMRT_DEV_MCP_TOKEN`, or let the process mint one and print it once to
|
|
610
|
+
stderr. A supplied token is never echoed.
|
|
611
|
+
- No authenticated principal exists on this plane, so scope is fail-closed
|
|
612
|
+
global-only exactly as for the stdio diagnostics.
|
|
613
|
+
- Per-request `projectPath` arguments are ignored: the booted project is fixed
|
|
614
|
+
at start. Restart the process to observe a rebuilt manifest.
|
|
615
|
+
|
|
616
|
+
```bash
|
|
617
|
+
SMRT_DEV_MCP_TOKEN=dev-secret smrt-dev-mcp --http --port 3939 --project .
|
|
618
|
+
# → [smrt-dev-mcp] runtime dev-plane listening at http://127.0.0.1:3939/mcp
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
## In-App Dev-Plane (Level 3)
|
|
622
|
+
|
|
623
|
+
The same read-only runtime tools can run *inside* a SvelteKit app's dev
|
|
624
|
+
server, where the real decorated `ObjectRegistry` is already live and the
|
|
625
|
+
app's own database configuration is at hand. Enable the generated route in
|
|
626
|
+
the Vite plugin and install this package as a devDependency of the app:
|
|
627
|
+
|
|
628
|
+
```ts
|
|
629
|
+
smrtPlugin({ sveltekit: { enabled: true, devPlaneRoute: { enabled: true } } })
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
The generator writes `src/routes/api/_dev/[...tool]/+server.ts` (marked
|
|
633
|
+
auto-generated), which mounts `createDevPlane` from
|
|
634
|
+
`@happyvertical/smrt-dev-mcp/dev-plane`:
|
|
635
|
+
|
|
636
|
+
- **JSON**: `GET /api/_dev` lists the catalog; `GET|POST /api/_dev/<tool>`
|
|
637
|
+
returns the tool's envelope (arguments from the query string or a JSON
|
|
638
|
+
body). `curl -H "Authorization: Bearer $SMRT_DEV_MCP_TOKEN" http://127.0.0.1:5173/api/_dev/registry-live`
|
|
639
|
+
- **MCP**: `POST /api/_dev/mcp`, the same stateless Streamable HTTP contract
|
|
640
|
+
as `--http`.
|
|
641
|
+
- **Catalog**: `registry-live` (the app's live registry, no manifest boot,
|
|
642
|
+
provenance `live (app registry)`) plus the nine Level 2 tools. The static
|
|
643
|
+
stdio catalog, generated CRUD, custom actions, and `do()` are never mounted.
|
|
644
|
+
|
|
645
|
+
Boundary: the route 404s outside SvelteKit dev mode and returns 503 until
|
|
646
|
+
`SMRT_DEV_MCP_TOKEN` is set; every request must present that bearer token from
|
|
647
|
+
a loopback `Host` (and loopback `Origin` when present). Per-request
|
|
648
|
+
`projectPath`, `dbUrl`, and `dbType` are dropped: calls are pinned to the app's
|
|
649
|
+
root and to the database from `getSmrtConfig(...).db`, so the SDK's connection
|
|
650
|
+
cache hands the plane the app's own handle. Vite's SSR HMR re-registers classes
|
|
651
|
+
on change, so `registry-live` needs no restart.
|
|
652
|
+
|
|
653
|
+
From a terminal, `smrt dev:runtime <tool> [--arg key=value]...` calls the
|
|
654
|
+
route when `--url`/`SMRT_DEV_PLANE_URL` is set and otherwise runs the Level 2
|
|
655
|
+
boot locally, so the same envelope is available either way.
|
|
656
|
+
|
|
572
657
|
## MCP Resources And Prompts
|
|
573
658
|
|
|
574
659
|
Resources:
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-app runtime dev-plane (#2782, Level 3 of #2307).
|
|
3
|
+
*
|
|
4
|
+
* The standalone `--http` host (Level 2) boots manifests in its own process.
|
|
5
|
+
* This module runs *inside* the application's dev server, where the real
|
|
6
|
+
* decorated `ObjectRegistry` is already live and the app's own database
|
|
7
|
+
* configuration is at hand. It serves the same read-only runtime tools as
|
|
8
|
+
* Level 2 plus `registry-live`, over two surfaces on one mount:
|
|
9
|
+
*
|
|
10
|
+
* - JSON: `GET <base>` (catalog), `GET|POST <base>/<tool>` (envelope);
|
|
11
|
+
* arguments come from the query string or a JSON body.
|
|
12
|
+
* - MCP: `POST <base>/mcp`, stateless Streamable HTTP (#2147).
|
|
13
|
+
*
|
|
14
|
+
* Security boundary: the caller (a generated SvelteKit route) only mounts
|
|
15
|
+
* this in dev mode; every request must present `Authorization: Bearer` with
|
|
16
|
+
* the configured token and arrive on a loopback `Host`. No principal exists,
|
|
17
|
+
* so scope is fail-closed global-only. The catalog is positive: generated
|
|
18
|
+
* CRUD, custom actions, `do()`, and tool-backed `is()` are never mounted.
|
|
19
|
+
*/
|
|
20
|
+
/** Provenance label for the application's own live registry. */
|
|
21
|
+
export declare const LIVE_REGISTRY_PROVENANCE = "live (app registry)";
|
|
22
|
+
export interface DevPlaneDatabase {
|
|
23
|
+
url?: string;
|
|
24
|
+
type?: string;
|
|
25
|
+
}
|
|
26
|
+
export interface DevPlaneOptions {
|
|
27
|
+
/** Bearer token every request must present. */
|
|
28
|
+
token: string;
|
|
29
|
+
/** Project root; used only to relativize paths in snapshots. */
|
|
30
|
+
projectRoot?: string;
|
|
31
|
+
/**
|
|
32
|
+
* The app's database configuration (typically `getSmrtConfig(name).db`
|
|
33
|
+
* from the generated `$lib/server/smrt`). Passed to the runtime tools as
|
|
34
|
+
* `dbUrl`/`dbType`, so the SDK's connection cache hands back the same
|
|
35
|
+
* handle the app uses. `:memory:` or absent means static-only answers.
|
|
36
|
+
*/
|
|
37
|
+
db?: DevPlaneDatabase;
|
|
38
|
+
}
|
|
39
|
+
export interface DevPlane {
|
|
40
|
+
/** Tool names served, in catalog order. */
|
|
41
|
+
tools: readonly string[];
|
|
42
|
+
/** Serve one request mounted at `basePath` (e.g. `/api/_dev`). */
|
|
43
|
+
handleRequest(request: Request, basePath: string): Promise<Response>;
|
|
44
|
+
}
|
|
45
|
+
/** Positive catalog: the nine Level 2 tools plus the in-process-only one. */
|
|
46
|
+
export declare const DEV_PLANE_TOOL_NAMES: readonly ["registry-live", "runtime-registry", "runtime-object", "runtime-schema-diff", "migration-status", "job-health", "schedule-health", "dispatch-health", "recent-changes", "registry-drift"];
|
|
47
|
+
export declare function createDevPlane(options: DevPlaneOptions): DevPlane;
|
|
48
|
+
//# sourceMappingURL=dev-plane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-plane.d.ts","sourceRoot":"","sources":["../src/dev-plane.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AA6BH,gEAAgE;AAChE,eAAO,MAAM,wBAAwB,wBAAwB,CAAC;AAE9D,MAAM,WAAW,gBAAgB;IAC/B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,gEAAgE;IAChE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;OAKG;IACH,EAAE,CAAC,EAAE,gBAAgB,CAAC;CACvB;AAED,MAAM,WAAW,QAAQ;IACvB,2CAA2C;IAC3C,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,kEAAkE;IAClE,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtE;AAID,6EAA6E;AAC7E,eAAO,MAAM,oBAAoB,qMAWvB,CAAC;AAwFX,wBAAgB,cAAc,CAAC,OAAO,EAAE,eAAe,GAAG,QAAQ,CA+IjE"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { i as SERVER_VERSION, r as SERVER_NAME, t as bearerMatches } from "./http-TeoaK9Xr.js";
|
|
2
|
+
import { n as TOOLS } from "./tool-catalog-Zl-bcimC.js";
|
|
3
|
+
import { c as runtimeMigrationStatus, d as runtimeScheduleHealth, i as RUNTIME_PROVENANCE, l as runtimeRecentChanges, n as runtimeRegistry, o as runtimeDispatchHealth, p as safeErrorMessage, r as runtimeSchemaDiff, s as runtimeJobHealth, t as runtimeObject, u as runtimeRegistryDrift } from "./observation-VJgHaPps.js";
|
|
4
|
+
import { Server, createMcpHandler } from "@modelcontextprotocol/server";
|
|
5
|
+
import { snapshotRegistry } from "@happyvertical/smrt-core";
|
|
6
|
+
//#region src/dev-plane.ts
|
|
7
|
+
/**
|
|
8
|
+
* In-app runtime dev-plane (#2782, Level 3 of #2307).
|
|
9
|
+
*
|
|
10
|
+
* The standalone `--http` host (Level 2) boots manifests in its own process.
|
|
11
|
+
* This module runs *inside* the application's dev server, where the real
|
|
12
|
+
* decorated `ObjectRegistry` is already live and the app's own database
|
|
13
|
+
* configuration is at hand. It serves the same read-only runtime tools as
|
|
14
|
+
* Level 2 plus `registry-live`, over two surfaces on one mount:
|
|
15
|
+
*
|
|
16
|
+
* - JSON: `GET <base>` (catalog), `GET|POST <base>/<tool>` (envelope);
|
|
17
|
+
* arguments come from the query string or a JSON body.
|
|
18
|
+
* - MCP: `POST <base>/mcp`, stateless Streamable HTTP (#2147).
|
|
19
|
+
*
|
|
20
|
+
* Security boundary: the caller (a generated SvelteKit route) only mounts
|
|
21
|
+
* this in dev mode; every request must present `Authorization: Bearer` with
|
|
22
|
+
* the configured token and arrive on a loopback `Host`. No principal exists,
|
|
23
|
+
* so scope is fail-closed global-only. The catalog is positive: generated
|
|
24
|
+
* CRUD, custom actions, `do()`, and tool-backed `is()` are never mounted.
|
|
25
|
+
*/
|
|
26
|
+
/** Provenance label for the application's own live registry. */
|
|
27
|
+
var LIVE_REGISTRY_PROVENANCE = "live (app registry)";
|
|
28
|
+
/** Positive catalog: the nine Level 2 tools plus the in-process-only one. */
|
|
29
|
+
var DEV_PLANE_TOOL_NAMES = [
|
|
30
|
+
"registry-live",
|
|
31
|
+
"runtime-registry",
|
|
32
|
+
"runtime-object",
|
|
33
|
+
"runtime-schema-diff",
|
|
34
|
+
"migration-status",
|
|
35
|
+
"job-health",
|
|
36
|
+
"schedule-health",
|
|
37
|
+
"dispatch-health",
|
|
38
|
+
"recent-changes",
|
|
39
|
+
"registry-drift"
|
|
40
|
+
];
|
|
41
|
+
var LOOPBACK = /* @__PURE__ */ new Set([
|
|
42
|
+
"127.0.0.1",
|
|
43
|
+
"localhost",
|
|
44
|
+
"::1",
|
|
45
|
+
"[::1]"
|
|
46
|
+
]);
|
|
47
|
+
function hostIsLoopback(request) {
|
|
48
|
+
const hostname = (request.headers.get("host") ?? "").replace(/:\d+$/, "").toLowerCase();
|
|
49
|
+
if (!LOOPBACK.has(hostname)) return false;
|
|
50
|
+
const origin = request.headers.get("origin");
|
|
51
|
+
if (!origin) return true;
|
|
52
|
+
try {
|
|
53
|
+
return LOOPBACK.has(new URL(origin).hostname.toLowerCase());
|
|
54
|
+
} catch {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function json(status, body, headers = {}) {
|
|
59
|
+
return new Response(JSON.stringify(body, null, 2), {
|
|
60
|
+
status,
|
|
61
|
+
headers: {
|
|
62
|
+
"content-type": "application/json",
|
|
63
|
+
...headers
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* `registry-live`: snapshot the registry as the app has it right now. No
|
|
69
|
+
* manifest boot; the app's own imports (via `$lib/server/smrt`) registered
|
|
70
|
+
* these classes, and Vite's SSR HMR re-registers them on change.
|
|
71
|
+
*/
|
|
72
|
+
async function registryLive(args, projectRoot) {
|
|
73
|
+
const objects = Array.isArray(args.objects) ? args.objects.map(String) : typeof args.objects === "string" ? args.objects.split(",").map((s) => s.trim()).filter(Boolean) : void 0;
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
coverage: null,
|
|
77
|
+
diagnostics: [],
|
|
78
|
+
data: {
|
|
79
|
+
provenance: LIVE_REGISTRY_PROVENANCE,
|
|
80
|
+
registrySource: "live",
|
|
81
|
+
snapshot: snapshotRegistry({
|
|
82
|
+
projectRoot,
|
|
83
|
+
objects,
|
|
84
|
+
detail: typeof args.detail === "boolean" ? args.detail : args.detail === "true" || Boolean(objects?.length)
|
|
85
|
+
})
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
async function parseArgs(request, url) {
|
|
90
|
+
const args = {};
|
|
91
|
+
for (const [key, value] of url.searchParams) if (value === "true") args[key] = true;
|
|
92
|
+
else if (value === "false") args[key] = false;
|
|
93
|
+
else if (/^-?\d+(\.\d+)?$/.test(value)) args[key] = Number(value);
|
|
94
|
+
else args[key] = value;
|
|
95
|
+
if (request.method === "POST") {
|
|
96
|
+
if ((request.headers.get("content-type") ?? "").includes("application/json")) {
|
|
97
|
+
const body = await request.json().catch(() => null);
|
|
98
|
+
if (body && typeof body === "object" && !Array.isArray(body)) Object.assign(args, body);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return args;
|
|
102
|
+
}
|
|
103
|
+
function createDevPlane(options) {
|
|
104
|
+
if (!options.token) throw new Error("createDevPlane requires a non-empty bearer token");
|
|
105
|
+
const projectRoot = options.projectRoot;
|
|
106
|
+
const dbArgs = options.db?.url && options.db.url !== ":memory:" ? {
|
|
107
|
+
dbUrl: options.db.url,
|
|
108
|
+
...options.db.type ? { dbType: options.db.type } : {}
|
|
109
|
+
} : {};
|
|
110
|
+
const pin = (args) => {
|
|
111
|
+
const { projectPath: _p, dbUrl: _u, dbType: _t, ...rest } = args;
|
|
112
|
+
return {
|
|
113
|
+
...rest,
|
|
114
|
+
projectPath: projectRoot,
|
|
115
|
+
...dbArgs
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
const handlers = {
|
|
119
|
+
"registry-live": (args) => registryLive(args, projectRoot),
|
|
120
|
+
"runtime-registry": (args) => runtimeRegistry(pin(args)),
|
|
121
|
+
"runtime-object": (args) => runtimeObject(pin(args)),
|
|
122
|
+
"runtime-schema-diff": (args) => runtimeSchemaDiff(pin(args)),
|
|
123
|
+
"migration-status": (args) => runtimeMigrationStatus(pin(args)),
|
|
124
|
+
"job-health": (args) => runtimeJobHealth(pin(args)),
|
|
125
|
+
"schedule-health": (args) => runtimeScheduleHealth(pin(args)),
|
|
126
|
+
"dispatch-health": (args) => runtimeDispatchHealth(pin(args)),
|
|
127
|
+
"recent-changes": (args) => runtimeRecentChanges(pin(args)),
|
|
128
|
+
"registry-drift": (args) => runtimeRegistryDrift(pin(args))
|
|
129
|
+
};
|
|
130
|
+
const catalogTools = () => {
|
|
131
|
+
const known = new Map(TOOLS.map((tool) => [tool.name, tool]));
|
|
132
|
+
return DEV_PLANE_TOOL_NAMES.map((name) => known.get(name) ?? {
|
|
133
|
+
name,
|
|
134
|
+
description: "Sanitized snapshot of the application's live ObjectRegistry (no manifest boot; live provenance; read-only)",
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: "object",
|
|
137
|
+
properties: {
|
|
138
|
+
objects: {
|
|
139
|
+
type: "array",
|
|
140
|
+
items: { type: "string" }
|
|
141
|
+
},
|
|
142
|
+
detail: { type: "boolean" }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
const mcp = createMcpHandler(() => {
|
|
148
|
+
const server = new Server({
|
|
149
|
+
name: `${SERVER_NAME}-dev-plane`,
|
|
150
|
+
version: SERVER_VERSION
|
|
151
|
+
}, { capabilities: { tools: {} } });
|
|
152
|
+
server.setRequestHandler("tools/list", async () => ({ tools: catalogTools() }));
|
|
153
|
+
server.setRequestHandler("tools/call", async (request) => {
|
|
154
|
+
const name = request.params.name;
|
|
155
|
+
const handler = handlers[name];
|
|
156
|
+
if (!handler) throw new Error(`Unknown dev-plane tool: ${String(name)}`);
|
|
157
|
+
const result = await handler(request.params.arguments ?? {});
|
|
158
|
+
return {
|
|
159
|
+
content: [{
|
|
160
|
+
type: "text",
|
|
161
|
+
text: JSON.stringify(result, null, 2)
|
|
162
|
+
}],
|
|
163
|
+
structuredContent: result
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
return server;
|
|
167
|
+
}, {
|
|
168
|
+
legacy: "reject",
|
|
169
|
+
maxSubscriptions: 0
|
|
170
|
+
});
|
|
171
|
+
return {
|
|
172
|
+
tools: DEV_PLANE_TOOL_NAMES,
|
|
173
|
+
async handleRequest(request, basePath) {
|
|
174
|
+
if (!hostIsLoopback(request)) return json(403, { error: "dev-plane requests must arrive on loopback" });
|
|
175
|
+
if (!bearerMatches(request.headers.get("authorization") ?? void 0, options.token)) return json(401, { error: "missing or invalid bearer token" }, { "www-authenticate": "Bearer realm=\"smrt dev-plane\"" });
|
|
176
|
+
const url = new URL(request.url);
|
|
177
|
+
const base = basePath.replace(/\/+$/, "");
|
|
178
|
+
if (!url.pathname.startsWith(base)) return json(404, { error: "not found" });
|
|
179
|
+
const rest = url.pathname.slice(base.length).replace(/^\/+/, "");
|
|
180
|
+
if (rest === "") return json(200, {
|
|
181
|
+
provenance: LIVE_REGISTRY_PROVENANCE,
|
|
182
|
+
tools: catalogTools().map((t) => ({
|
|
183
|
+
name: t.name,
|
|
184
|
+
description: t.description
|
|
185
|
+
})),
|
|
186
|
+
mcp: `${base}/mcp`
|
|
187
|
+
});
|
|
188
|
+
if (rest === "mcp") return mcp.fetch(request);
|
|
189
|
+
const handler = handlers[rest];
|
|
190
|
+
if (!handler || request.method !== "GET" && request.method !== "POST") return json(404, { error: `unknown dev-plane tool: ${rest}` });
|
|
191
|
+
try {
|
|
192
|
+
return json(200, await handler(await parseArgs(request, url)));
|
|
193
|
+
} catch (error) {
|
|
194
|
+
return json(200, {
|
|
195
|
+
ok: false,
|
|
196
|
+
coverage: null,
|
|
197
|
+
diagnostics: [{
|
|
198
|
+
severity: "warning",
|
|
199
|
+
code: "dev_plane_tool_error",
|
|
200
|
+
message: safeErrorMessage(error)
|
|
201
|
+
}],
|
|
202
|
+
data: { provenance: RUNTIME_PROVENANCE }
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
//#endregion
|
|
209
|
+
export { DEV_PLANE_TOOL_NAMES, LIVE_REGISTRY_PROVENANCE, createDevPlane };
|
|
210
|
+
|
|
211
|
+
//# sourceMappingURL=dev-plane.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dev-plane.js","names":[],"sources":["../src/dev-plane.ts"],"sourcesContent":["/**\n * In-app runtime dev-plane (#2782, Level 3 of #2307).\n *\n * The standalone `--http` host (Level 2) boots manifests in its own process.\n * This module runs *inside* the application's dev server, where the real\n * decorated `ObjectRegistry` is already live and the app's own database\n * configuration is at hand. It serves the same read-only runtime tools as\n * Level 2 plus `registry-live`, over two surfaces on one mount:\n *\n * - JSON: `GET <base>` (catalog), `GET|POST <base>/<tool>` (envelope);\n * arguments come from the query string or a JSON body.\n * - MCP: `POST <base>/mcp`, stateless Streamable HTTP (#2147).\n *\n * Security boundary: the caller (a generated SvelteKit route) only mounts\n * this in dev mode; every request must present `Authorization: Bearer` with\n * the configured token and arrive on a loopback `Host`. No principal exists,\n * so scope is fail-closed global-only. The catalog is positive: generated\n * CRUD, custom actions, `do()`, and tool-backed `is()` are never mounted.\n */\n\nimport { snapshotRegistry } from '@happyvertical/smrt-core';\nimport {\n type CallToolResult,\n createMcpHandler,\n Server,\n type Tool,\n} from '@modelcontextprotocol/server';\nimport { bearerMatches } from './http.js';\nimport { SERVER_NAME, SERVER_VERSION } from './server-info.js';\nimport { TOOLS } from './tool-catalog.js';\nimport { safeErrorMessage } from './tools/runtime/connection.js';\nimport {\n runtimeObject,\n runtimeRegistry,\n runtimeSchemaDiff,\n} from './tools/runtime/observation.js';\nimport {\n RUNTIME_PROVENANCE,\n type RuntimeToolEnvelope,\n runtimeDispatchHealth,\n runtimeJobHealth,\n runtimeMigrationStatus,\n runtimeRecentChanges,\n runtimeRegistryDrift,\n runtimeScheduleHealth,\n} from './tools/runtime/tools.js';\n\n/** Provenance label for the application's own live registry. */\nexport const LIVE_REGISTRY_PROVENANCE = 'live (app registry)';\n\nexport interface DevPlaneDatabase {\n url?: string;\n type?: string;\n}\n\nexport interface DevPlaneOptions {\n /** Bearer token every request must present. */\n token: string;\n /** Project root; used only to relativize paths in snapshots. */\n projectRoot?: string;\n /**\n * The app's database configuration (typically `getSmrtConfig(name).db`\n * from the generated `$lib/server/smrt`). Passed to the runtime tools as\n * `dbUrl`/`dbType`, so the SDK's connection cache hands back the same\n * handle the app uses. `:memory:` or absent means static-only answers.\n */\n db?: DevPlaneDatabase;\n}\n\nexport interface DevPlane {\n /** Tool names served, in catalog order. */\n tools: readonly string[];\n /** Serve one request mounted at `basePath` (e.g. `/api/_dev`). */\n handleRequest(request: Request, basePath: string): Promise<Response>;\n}\n\ntype ToolHandler = (args: Record<string, unknown>) => Promise<unknown>;\n\n/** Positive catalog: the nine Level 2 tools plus the in-process-only one. */\nexport const DEV_PLANE_TOOL_NAMES = [\n 'registry-live',\n 'runtime-registry',\n 'runtime-object',\n 'runtime-schema-diff',\n 'migration-status',\n 'job-health',\n 'schedule-health',\n 'dispatch-health',\n 'recent-changes',\n 'registry-drift',\n] as const;\n\nconst LOOPBACK = new Set(['127.0.0.1', 'localhost', '::1', '[::1]']);\n\nfunction hostIsLoopback(request: Request): boolean {\n const host = request.headers.get('host') ?? '';\n const hostname = host.replace(/:\\d+$/, '').toLowerCase();\n if (!LOOPBACK.has(hostname)) return false;\n const origin = request.headers.get('origin');\n if (!origin) return true;\n try {\n return LOOPBACK.has(new URL(origin).hostname.toLowerCase());\n } catch {\n return false;\n }\n}\n\nfunction json(\n status: number,\n body: unknown,\n headers: Record<string, string> = {},\n): Response {\n return new Response(JSON.stringify(body, null, 2), {\n status,\n headers: { 'content-type': 'application/json', ...headers },\n });\n}\n\n/**\n * `registry-live`: snapshot the registry as the app has it right now. No\n * manifest boot; the app's own imports (via `$lib/server/smrt`) registered\n * these classes, and Vite's SSR HMR re-registers them on change.\n */\nasync function registryLive(\n args: Record<string, unknown>,\n projectRoot: string | undefined,\n): Promise<RuntimeToolEnvelope> {\n const objects = Array.isArray(args.objects)\n ? args.objects.map(String)\n : typeof args.objects === 'string'\n ? args.objects\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : undefined;\n const snapshot = snapshotRegistry({\n projectRoot,\n objects,\n detail:\n typeof args.detail === 'boolean'\n ? args.detail\n : args.detail === 'true' || Boolean(objects?.length),\n });\n return {\n ok: true,\n coverage: null,\n diagnostics: [],\n data: {\n provenance: LIVE_REGISTRY_PROVENANCE,\n registrySource: 'live',\n snapshot,\n },\n };\n}\n\nasync function parseArgs(\n request: Request,\n url: URL,\n): Promise<Record<string, unknown>> {\n const args: Record<string, unknown> = {};\n for (const [key, value] of url.searchParams) {\n if (value === 'true') args[key] = true;\n else if (value === 'false') args[key] = false;\n else if (/^-?\\d+(\\.\\d+)?$/.test(value)) args[key] = Number(value);\n else args[key] = value;\n }\n if (request.method === 'POST') {\n const type = request.headers.get('content-type') ?? '';\n if (type.includes('application/json')) {\n const body = (await request.json().catch(() => null)) as unknown;\n if (body && typeof body === 'object' && !Array.isArray(body)) {\n Object.assign(args, body as Record<string, unknown>);\n }\n }\n }\n return args;\n}\n\nexport function createDevPlane(options: DevPlaneOptions): DevPlane {\n if (!options.token) {\n throw new Error('createDevPlane requires a non-empty bearer token');\n }\n const projectRoot = options.projectRoot;\n const dbArgs =\n options.db?.url && options.db.url !== ':memory:'\n ? {\n dbUrl: options.db.url,\n ...(options.db.type ? { dbType: options.db.type } : {}),\n }\n : {};\n\n // Every call is pinned to the app's root and database; client-supplied\n // projectPath/dbUrl/dbType are dropped so a caller cannot re-point it.\n const pin = (args: Record<string, unknown>): Record<string, unknown> => {\n const { projectPath: _p, dbUrl: _u, dbType: _t, ...rest } = args;\n return { ...rest, projectPath: projectRoot, ...dbArgs };\n };\n\n const handlers: Record<(typeof DEV_PLANE_TOOL_NAMES)[number], ToolHandler> = {\n 'registry-live': (args) => registryLive(args, projectRoot),\n 'runtime-registry': (args) => runtimeRegistry(pin(args) as never),\n 'runtime-object': (args) => runtimeObject(pin(args) as never),\n 'runtime-schema-diff': (args) => runtimeSchemaDiff(pin(args) as never),\n 'migration-status': (args) => runtimeMigrationStatus(pin(args) as never),\n 'job-health': (args) => runtimeJobHealth(pin(args) as never),\n 'schedule-health': (args) => runtimeScheduleHealth(pin(args) as never),\n 'dispatch-health': (args) => runtimeDispatchHealth(pin(args) as never),\n 'recent-changes': (args) => runtimeRecentChanges(pin(args) as never),\n 'registry-drift': (args) => runtimeRegistryDrift(pin(args) as never),\n };\n\n const catalogTools = (): Tool[] => {\n const known = new Map(TOOLS.map((tool) => [tool.name, tool]));\n return DEV_PLANE_TOOL_NAMES.map(\n (name) =>\n known.get(name) ?? {\n name,\n description:\n \"Sanitized snapshot of the application's live ObjectRegistry (no manifest boot; live provenance; read-only)\",\n inputSchema: {\n type: 'object',\n properties: {\n objects: { type: 'array', items: { type: 'string' } },\n detail: { type: 'boolean' },\n },\n },\n },\n ) as Tool[];\n };\n\n const mcp = createMcpHandler(\n () => {\n const server = new Server(\n { name: `${SERVER_NAME}-dev-plane`, version: SERVER_VERSION },\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler('tools/list', async () => ({\n tools: catalogTools(),\n }));\n server.setRequestHandler('tools/call', async (request) => {\n const name = request.params\n .name as (typeof DEV_PLANE_TOOL_NAMES)[number];\n const handler = handlers[name];\n if (!handler)\n throw new Error(`Unknown dev-plane tool: ${String(name)}`);\n const result = await handler(\n (request.params.arguments ?? {}) as Record<string, unknown>,\n );\n return {\n content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],\n structuredContent: result as Record<string, unknown>,\n } satisfies CallToolResult;\n });\n return server;\n },\n { legacy: 'reject', maxSubscriptions: 0 },\n );\n\n return {\n tools: DEV_PLANE_TOOL_NAMES,\n async handleRequest(request, basePath) {\n if (!hostIsLoopback(request)) {\n return json(403, {\n error: 'dev-plane requests must arrive on loopback',\n });\n }\n if (\n !bearerMatches(\n request.headers.get('authorization') ?? undefined,\n options.token,\n )\n ) {\n return json(\n 401,\n { error: 'missing or invalid bearer token' },\n {\n 'www-authenticate': 'Bearer realm=\"smrt dev-plane\"',\n },\n );\n }\n const url = new URL(request.url);\n const base = basePath.replace(/\\/+$/, '');\n if (!url.pathname.startsWith(base))\n return json(404, { error: 'not found' });\n const rest = url.pathname.slice(base.length).replace(/^\\/+/, '');\n if (rest === '') {\n return json(200, {\n provenance: LIVE_REGISTRY_PROVENANCE,\n tools: catalogTools().map((t) => ({\n name: t.name,\n description: t.description,\n })),\n mcp: `${base}/mcp`,\n });\n }\n if (rest === 'mcp') {\n return mcp.fetch(request);\n }\n const handler = handlers[rest as (typeof DEV_PLANE_TOOL_NAMES)[number]];\n if (!handler || (request.method !== 'GET' && request.method !== 'POST')) {\n return json(404, { error: `unknown dev-plane tool: ${rest}` });\n }\n try {\n return json(200, await handler(await parseArgs(request, url)));\n } catch (error) {\n return json(200, {\n ok: false,\n coverage: null,\n diagnostics: [\n {\n severity: 'warning',\n code: 'dev_plane_tool_error',\n // Same redaction the runtime tools apply to driver errors.\n message: safeErrorMessage(error),\n },\n ],\n data: { provenance: RUNTIME_PROVENANCE },\n });\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,IAAa,2BAA2B;;AA+BxC,IAAa,uBAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,IAAM,2BAAW,IAAI,IAAI;CAAC;CAAa;CAAa;CAAO;AAAO,CAAC;AAEnE,SAAS,eAAe,SAA2B;CAEjD,MAAM,YADO,QAAQ,QAAQ,IAAI,MAAM,KAAK,GAAA,CACtB,QAAQ,SAAS,EAAE,CAAC,CAAC,YAAY;CACvD,IAAI,CAAC,SAAS,IAAI,QAAQ,GAAG,OAAO;CACpC,MAAM,SAAS,QAAQ,QAAQ,IAAI,QAAQ;CAC3C,IAAI,CAAC,QAAQ,OAAO;CACpB,IAAI;EACF,OAAO,SAAS,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,SAAS,YAAY,CAAC;CAC5D,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,KACP,QACA,MACA,UAAkC,CAAC,GACzB;CACV,OAAO,IAAI,SAAS,KAAK,UAAU,MAAM,MAAM,CAAC,GAAG;EACjD;EACA,SAAS;GAAE,gBAAgB;GAAoB,GAAG;EAAQ;CAC5D,CAAC;AACH;;;;;;AAOA,eAAe,aACb,MACA,aAC8B;CAC9B,MAAM,UAAU,MAAM,QAAQ,KAAK,OAAO,IACtC,KAAK,QAAQ,IAAI,MAAM,IACvB,OAAO,KAAK,YAAY,WACtB,KAAK,QACF,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC,CACpB,OAAO,OAAO,IACjB,KAAA;CASN,OAAO;EACL,IAAI;EACJ,UAAU;EACV,aAAa,CAAC;EACd,MAAM;GACJ,YAAY;GACZ,gBAAgB;GAChB,UAfa,iBAAiB;IAChC;IACA;IACA,QACE,OAAO,KAAK,WAAW,YACnB,KAAK,SACL,KAAK,WAAW,UAAU,QAAQ,SAAS,MAAM;GACzD,CAQI;EACF;CACF;AACF;AAEA,eAAe,UACb,SACA,KACkC;CAClC,MAAM,OAAgC,CAAC;CACvC,KAAK,MAAM,CAAC,KAAK,UAAU,IAAI,cAC7B,IAAI,UAAU,QAAQ,KAAK,OAAO;MAC7B,IAAI,UAAU,SAAS,KAAK,OAAO;MACnC,IAAI,kBAAkB,KAAK,KAAK,GAAG,KAAK,OAAO,OAAO,KAAK;MAC3D,KAAK,OAAO;CAEnB,IAAI,QAAQ,WAAW;OACR,QAAQ,QAAQ,IAAI,cAAc,KAAK,GAAA,CAC3C,SAAS,kBAAkB,GAAG;GACrC,MAAM,OAAQ,MAAM,QAAQ,KAAK,CAAC,CAAC,YAAY,IAAI;GACnD,IAAI,QAAQ,OAAO,SAAS,YAAY,CAAC,MAAM,QAAQ,IAAI,GACzD,OAAO,OAAO,MAAM,IAA+B;EAEvD;;CAEF,OAAO;AACT;AAEA,SAAgB,eAAe,SAAoC;CACjE,IAAI,CAAC,QAAQ,OACX,MAAM,IAAI,MAAM,kDAAkD;CAEpE,MAAM,cAAc,QAAQ;CAC5B,MAAM,SACJ,QAAQ,IAAI,OAAO,QAAQ,GAAG,QAAQ,aAClC;EACE,OAAO,QAAQ,GAAG;EAClB,GAAI,QAAQ,GAAG,OAAO,EAAE,QAAQ,QAAQ,GAAG,KAAK,IAAI,CAAC;CACvD,IACA,CAAC;CAIP,MAAM,OAAO,SAA2D;EACtE,MAAM,EAAE,aAAa,IAAI,OAAO,IAAI,QAAQ,IAAI,GAAG,SAAS;EAC5D,OAAO;GAAE,GAAG;GAAM,aAAa;GAAa,GAAG;EAAO;CACxD;CAEA,MAAM,WAAuE;EAC3E,kBAAkB,SAAS,aAAa,MAAM,WAAW;EACzD,qBAAqB,SAAS,gBAAgB,IAAI,IAAI,CAAU;EAChE,mBAAmB,SAAS,cAAc,IAAI,IAAI,CAAU;EAC5D,wBAAwB,SAAS,kBAAkB,IAAI,IAAI,CAAU;EACrE,qBAAqB,SAAS,uBAAuB,IAAI,IAAI,CAAU;EACvE,eAAe,SAAS,iBAAiB,IAAI,IAAI,CAAU;EAC3D,oBAAoB,SAAS,sBAAsB,IAAI,IAAI,CAAU;EACrE,oBAAoB,SAAS,sBAAsB,IAAI,IAAI,CAAU;EACrE,mBAAmB,SAAS,qBAAqB,IAAI,IAAI,CAAU;EACnE,mBAAmB,SAAS,qBAAqB,IAAI,IAAI,CAAU;CACrE;CAEA,MAAM,qBAA6B;EACjC,MAAM,QAAQ,IAAI,IAAI,MAAM,KAAK,SAAS,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;EAC5D,OAAO,qBAAqB,KACzB,SACC,MAAM,IAAI,IAAI,KAAK;GACjB;GACA,aACE;GACF,aAAa;IACX,MAAM;IACN,YAAY;KACV,SAAS;MAAE,MAAM;MAAS,OAAO,EAAE,MAAM,SAAS;KAAE;KACpD,QAAQ,EAAE,MAAM,UAAU;IAC5B;GACF;EACF,CACJ;CACF;CAEA,MAAM,MAAM,uBACJ;EACJ,MAAM,SAAS,IAAI,OACjB;GAAE,MAAM,GAAG,YAAY;GAAa,SAAS;EAAe,GAC5D,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,EAAE,CAChC;EACA,OAAO,kBAAkB,cAAc,aAAa,EAClD,OAAO,aAAa,EACtB,EAAE;EACF,OAAO,kBAAkB,cAAc,OAAO,YAAY;GACxD,MAAM,OAAO,QAAQ,OAClB;GACH,MAAM,UAAU,SAAS;GACzB,IAAI,CAAC,SACH,MAAM,IAAI,MAAM,2BAA2B,OAAO,IAAI,GAAG;GAC3D,MAAM,SAAS,MAAM,QAClB,QAAQ,OAAO,aAAa,CAAC,CAChC;GACA,OAAO;IACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,CAAC;IAAE,CAAC;IACjE,mBAAmB;GACrB;EACF,CAAC;EACD,OAAO;CACT,GACA;EAAE,QAAQ;EAAU,kBAAkB;CAAE,CAC1C;CAEA,OAAO;EACL,OAAO;EACP,MAAM,cAAc,SAAS,UAAU;GACrC,IAAI,CAAC,eAAe,OAAO,GACzB,OAAO,KAAK,KAAK,EACf,OAAO,6CACT,CAAC;GAEH,IACE,CAAC,cACC,QAAQ,QAAQ,IAAI,eAAe,KAAK,KAAA,GACxC,QAAQ,KACV,GAEA,OAAO,KACL,KACA,EAAE,OAAO,kCAAkC,GAC3C,EACE,oBAAoB,kCACtB,CACF;GAEF,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;GAC/B,MAAM,OAAO,SAAS,QAAQ,QAAQ,EAAE;GACxC,IAAI,CAAC,IAAI,SAAS,WAAW,IAAI,GAC/B,OAAO,KAAK,KAAK,EAAE,OAAO,YAAY,CAAC;GACzC,MAAM,OAAO,IAAI,SAAS,MAAM,KAAK,MAAM,CAAC,CAAC,QAAQ,QAAQ,EAAE;GAC/D,IAAI,SAAS,IACX,OAAO,KAAK,KAAK;IACf,YAAY;IACZ,OAAO,aAAa,CAAC,CAAC,KAAK,OAAO;KAChC,MAAM,EAAE;KACR,aAAa,EAAE;IACjB,EAAE;IACF,KAAK,GAAG,KAAK;GACf,CAAC;GAEH,IAAI,SAAS,OACX,OAAO,IAAI,MAAM,OAAO;GAE1B,MAAM,UAAU,SAAS;GACzB,IAAI,CAAC,WAAY,QAAQ,WAAW,SAAS,QAAQ,WAAW,QAC9D,OAAO,KAAK,KAAK,EAAE,OAAO,2BAA2B,OAAO,CAAC;GAE/D,IAAI;IACF,OAAO,KAAK,KAAK,MAAM,QAAQ,MAAM,UAAU,SAAS,GAAG,CAAC,CAAC;GAC/D,SAAS,OAAO;IACd,OAAO,KAAK,KAAK;KACf,IAAI;KACJ,UAAU;KACV,aAAa,CACX;MACE,UAAU;MACV,MAAM;MAEN,SAAS,iBAAiB,KAAK;KACjC,CACF;KACA,MAAM,EAAE,YAAY,mBAAmB;IACzC,CAAC;GACH;EACF;CACF;AACF"}
|