@happyvertical/smrt-dev-mcp 0.60.9 → 0.60.11
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 +33 -1
- package/README.md +17 -1
- package/agents/agent-skills.md +16 -0
- package/agents/intent-duplicates.md +26 -0
- package/dist/agent-skills.d.ts +2 -0
- package/dist/agent-skills.d.ts.map +1 -1
- package/dist/agent-skills.js +61 -0
- package/dist/agent-skills.js.map +1 -0
- package/dist/dev-plane.js +2 -2
- package/dist/{http-CCiO7qQW.js → http-BqKiyqgN.js} +2 -2
- package/dist/{http-CCiO7qQW.js.map → http-BqKiyqgN.js.map} +1 -1
- package/dist/index.js +13 -445
- package/dist/index.js.map +1 -1
- package/dist/knowledge/index.d.ts +1 -1
- package/dist/knowledge/index.d.ts.map +1 -1
- package/dist/knowledge/intents/audit-trail.md +63 -0
- package/dist/knowledge/intents/auth-and-users.md +67 -0
- package/dist/knowledge/intents/background-work.md +72 -0
- package/dist/knowledge/intents/feature-flags-and-entitlements.md +60 -0
- package/dist/knowledge/intents/file-and-asset-storage.md +71 -0
- package/dist/knowledge/intents/human-approvals.md +56 -0
- package/dist/knowledge/intents/index.md +41 -0
- package/dist/knowledge/intents/money-and-rates.md +65 -0
- package/dist/knowledge/intents/outbound-messages.md +67 -0
- package/dist/knowledge/intents/prompts-and-ai-operations.md +72 -0
- package/dist/knowledge/intents/relationships-and-trees.md +56 -0
- package/dist/knowledge/intents/scheduled-runs.md +61 -0
- package/dist/knowledge/intents/search-facts-knowledge.md +68 -0
- package/dist/knowledge/intents/secrets-and-config.md +66 -0
- package/dist/knowledge/intents/tenant-scoping.md +63 -0
- package/dist/knowledge/intents/webhooks-and-events.md +71 -0
- package/dist/knowledge/okf-bundle.d.ts +61 -0
- package/dist/knowledge/okf-bundle.d.ts.map +1 -0
- package/dist/{knowledge-DqoDX-lZ.js → knowledge-DUiMUjEg.js} +218 -3
- package/dist/knowledge-DUiMUjEg.js.map +1 -0
- package/dist/knowledge.js +2 -2
- package/dist/review-cli-DJIWUlFp.js +75 -0
- package/dist/review-cli-DJIWUlFp.js.map +1 -0
- package/dist/review-smrt-project-ByEwPcor.js +774 -0
- package/dist/review-smrt-project-ByEwPcor.js.map +1 -0
- package/dist/smrt-knowledge.okf.md +65 -0
- package/dist/{tool-catalog-RHq7nyCm.js → tool-catalog-BhnSCF8g.js} +11 -2
- package/dist/tool-catalog-BhnSCF8g.js.map +1 -0
- package/dist/tool-catalog.d.ts.map +1 -1
- package/dist/tools/intent-detector.d.ts +57 -0
- package/dist/tools/intent-detector.d.ts.map +1 -0
- package/dist/tools/intent-index.d.ts +40 -0
- package/dist/tools/intent-index.d.ts.map +1 -0
- package/dist/tools/review-cli.d.ts +40 -0
- package/dist/tools/review-cli.d.ts.map +1 -0
- package/dist/tools/review-smrt-project.d.ts +25 -0
- package/dist/tools/review-smrt-project.d.ts.map +1 -1
- package/package.json +10 -6
- package/plugin.json +1 -1
- package/skills/smrt-catalog/SKILL.md +42 -0
- package/skills/smrt-catalog/evals.json +28 -0
- package/skills/smrt-contribute/SKILL.md +73 -0
- package/skills/smrt-contribute/evals.json +18 -0
- package/dist/knowledge-DqoDX-lZ.js.map +0 -1
- package/dist/tool-catalog-RHq7nyCm.js.map +0 -1
package/AGENTS.md
CHANGED
|
@@ -9,7 +9,7 @@ deterministic SMRT ecosystem knowledge, and portable agent workflows.
|
|
|
9
9
|
|------|---------|
|
|
10
10
|
| `generate-smrt-class` | Generates `@smrt()` class with fields, decorator config, imports |
|
|
11
11
|
| `introspect-project` | Scans project directory for SMRT objects; compact summary by default, `detail: 'full'` for field/schema/method detail |
|
|
12
|
-
| `review-smrt-project` | Advisory downstream ecosystem alignment review for dependencies, storage, app shell, auth/tenancy,
|
|
12
|
+
| `review-smrt-project` | Advisory downstream ecosystem alignment review for dependencies, storage, app shell, auth/tenancy, manifest generation, and intent-driven hand-rolled-equivalent detection (#4690) |
|
|
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 |
|
|
@@ -33,6 +33,14 @@ deterministic SMRT ecosystem knowledge, and portable agent workflows.
|
|
|
33
33
|
README parameter tables to the exported `TOOLS` definitions. Add or change a
|
|
34
34
|
tool schema and its authored documentation in the same commit.
|
|
35
35
|
|
|
36
|
+
## Intent-driven duplicate detection (#4690)
|
|
37
|
+
|
|
38
|
+
`review-smrt-project` / `smrt-dev-mcp review` flag hand-rolled equivalents of
|
|
39
|
+
smrt packages from the intent index. The rules (`regex:` tokens become `RegExp`)
|
|
40
|
+
come from the full index packaged in this release, never from the reviewed tree;
|
|
41
|
+
`intentIndexPath` / `--intent-index` is caller-trusted. Details, token kinds and
|
|
42
|
+
the CLI/workflow contract: [agents/intent-duplicates.md](agents/intent-duplicates.md).
|
|
43
|
+
|
|
36
44
|
## Knowledge Discovery (#2143)
|
|
37
45
|
|
|
38
46
|
Discovery reads the workspace globs instead of assuming `<root>/packages`:
|
|
@@ -104,6 +112,18 @@ otherwise emitted docs read `/AGENTS.md` and changed-file matching compares
|
|
|
104
112
|
against a leading `/` and selects nothing. The root owns any changed path no
|
|
105
113
|
member package owns, mirroring the member-excluded scan.
|
|
106
114
|
|
|
115
|
+
### OKF component pages (#4686)
|
|
116
|
+
|
|
117
|
+
`pnpm knowledge:okf` (after `pnpm build`) writes `docs/knowledge/components/<unscoped-name>.md` per
|
|
118
|
+
workspace package from this index; never hand-edit them. `source_revision` is a `sha256` of the page's
|
|
119
|
+
inputs (manifest slice, package AGENTS/module-doc hashes, models, MCP tools, `agentSurface`), not a commit.
|
|
120
|
+
`knowledge:check --strict` fails on stale, edited, missing or extra pages and on dangling `/…md` links in
|
|
121
|
+
hand-authored pages. Details: header of `scripts/knowledge-okf.ts`.
|
|
122
|
+
|
|
123
|
+
Consumer bundle (#4689): `okf-bundle.ts` defines the `.agents/smrt-knowledge/` contract `smrt docs:agents` writes;
|
|
124
|
+
`check-knowledge-freshness` reports its age (`okf-bundle-age` info, `okf-bundle-aged` past 90 days) and flags
|
|
125
|
+
`stale-okf-bundle` when a page's `package_version` no longer matches `node_modules` until `docs:agents` reruns.
|
|
126
|
+
|
|
107
127
|
## Agent Skills
|
|
108
128
|
|
|
109
129
|
Bundled skills live under `skills/`, the Agent Plugins fixed discovery
|
|
@@ -115,7 +135,11 @@ Skill-aware harnesses can parse the frontmatter; other harnesses can ignore it.
|
|
|
115
135
|
Agents should fetch it with `get-agent-skill`, then call `build-context` (`task: review`) for
|
|
116
136
|
deterministic context, inspect the actual diff, and produce a findings-first
|
|
117
137
|
review.
|
|
138
|
+
- `skills/smrt-catalog/SKILL.md` — catalog router skill (#4708), installed into consumers by `smrt docs:agents`.
|
|
139
|
+
- `skills/smrt-contribute/SKILL.md` — contributor skill (#4709) for forks of this repository; an identical copy is
|
|
140
|
+
checked in at the root `.agents/skills/smrt-contribute/SKILL.md` and a test fails on drift.
|
|
118
141
|
|
|
142
|
+
Trigger evals, the registry and install contract, and the drift rule: [agents/agent-skills.md](agents/agent-skills.md).
|
|
119
143
|
|
|
120
144
|
## Project runtime prerequisite
|
|
121
145
|
|
|
@@ -157,6 +181,12 @@ server is always launched from a repo that installs the package. Prefer an
|
|
|
157
181
|
absolute `node /path/to/node_modules/@happyvertical/smrt-dev-mcp/dist/index.js`
|
|
158
182
|
launcher or a small wrapper script with an absolute Node path.
|
|
159
183
|
|
|
184
|
+
This monorepo's `.mcp.json` runs `node scripts/mcp-servers/smrt-dev-mcp.mjs`:
|
|
185
|
+
stdout is protocol-only, the launcher never installs or uses `pnpm exec`/`npx`
|
|
186
|
+
(their stdout banner breaks stdio, #4685), and it falls back to `tsx` on `src/`
|
|
187
|
+
when `dist` is missing or older, so rebuild `dist` after server edits. Guarded by
|
|
188
|
+
`src/launcher-stdio.test.ts`; see [scripts/mcp-servers/README.md](../../scripts/mcp-servers/README.md).
|
|
189
|
+
|
|
160
190
|
## MCP Tier Context
|
|
161
191
|
|
|
162
192
|
- **Tier 1** (Runtime): auto-generated from `@smrt()` objects — live data operations
|
|
@@ -170,6 +200,8 @@ launcher or a small wrapper script with an absolute Node path.
|
|
|
170
200
|
- `src/agent-skills.ts` — bundled skill registry and file loader
|
|
171
201
|
- `plugin.json` / `mcp.json` — Agent Plugins 1.0.0 portable root manifests
|
|
172
202
|
- `skills/smrt-code-review/SKILL.md` — downstream SMRT review procedure
|
|
203
|
+
- `skills/smrt-contribute/SKILL.md` — contributor skill (copy at repo-root `.agents/skills/smrt-contribute/`)
|
|
204
|
+
- `skills/smrt-catalog/SKILL.md` — catalog router skill; `skills/smrt-catalog/evals.json` — its trigger eval set
|
|
173
205
|
- `src/tools/generate-smrt-class.ts` — class generation logic and package-ready templates
|
|
174
206
|
- `src/tools/introspect-project.ts` — manifest-first project scanning, falling back to `@happyvertical/smrt-scanner`
|
|
175
207
|
- `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
|
package/README.md
CHANGED
|
@@ -334,7 +334,8 @@ available it falls back to `@happyvertical/smrt-scanner`.
|
|
|
334
334
|
Run an advisory downstream ecosystem review. The tool scans package manifests
|
|
335
335
|
and source imports for missing HappyVertical dependencies, direct storage
|
|
336
336
|
bypasses, custom HTTP shells, custom object manifest generation, local
|
|
337
|
-
auth/tenancy/audit seams,
|
|
337
|
+
auth/tenancy/audit seams, UI shell drift, and hand-rolled equivalents of
|
|
338
|
+
smrt packages. It returns deterministic findings
|
|
338
339
|
and suggested follow-up issue titles; it does not modify files.
|
|
339
340
|
|
|
340
341
|
| Parameter | Type | Required | Description |
|
|
@@ -343,6 +344,21 @@ and suggested follow-up issue titles; it does not modify files.
|
|
|
343
344
|
| `rootDir` | `string` | No | Compatibility alias for `directory` |
|
|
344
345
|
| `includeSourceEvidence` | `boolean` | No | Include file/line evidence (default: true) |
|
|
345
346
|
| `maxFindings` | `number` | No | Limit findings returned |
|
|
347
|
+
| `intentIndexPath` | `string` | No | Caller-trusted intent index driving duplicate detection (`regex:` tokens become `RegExp`). Default: the full index packaged in this release (`dist/knowledge/intents/index.md`), else the monorepo's `docs/knowledge/intents/index.md` in a source checkout; never read from `directory` |
|
|
348
|
+
| `detectDuplicates` | `boolean` | No | Run the intent-driven hand-rolled-equivalent pass (default: true) |
|
|
349
|
+
|
|
350
|
+
Duplicate detection is table-driven: each row of the intent index names home
|
|
351
|
+
packages and `import:`, `call:`, `regex:` and `decimal-field:` signals. When a
|
|
352
|
+
package shows a signal and imports none of the row's home packages, the tool
|
|
353
|
+
emits an advisory `duplicate-detection` finding anchored at `path:line` that
|
|
354
|
+
names the intent page and component pages. Unknown signal kinds are skipped
|
|
355
|
+
with a stderr note. The rules are never taken from the reviewed project, so a
|
|
356
|
+
pull request cannot delete them or inject a pattern; when no index can be
|
|
357
|
+
found the tool reports `skipped` with a note. The same pass runs from a shell with
|
|
358
|
+
`npx @happyvertical/smrt-dev-mcp review --directory . [--intent-index PATH] [--format summary]`
|
|
359
|
+
(exit 2 and a stderr message when no index is found, never a silent pass) and
|
|
360
|
+
from the reusable `consumer-knowledge-review.yml` workflow, which always uses
|
|
361
|
+
the packaged index.
|
|
346
362
|
|
|
347
363
|
### `reflect-knowledge`
|
|
348
364
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Agent skills: catalog and contribute
|
|
2
|
+
|
|
3
|
+
- `skills/smrt-catalog/SKILL.md` — router skill (#4708). Fires before infrastructure, utility, or integration
|
|
4
|
+
code in a project that depends on smrt packages; reads `.agents/smrt-knowledge/intents/index.md`, the intent
|
|
5
|
+
page, then the component page, proposes the smrt package and entry point before code, falls back to
|
|
6
|
+
`build-context` when the bundle is missing or stale, and drafts an upstream issue plus a marked temporary shim
|
|
7
|
+
when the home package lacks the behaviour. The description is the design: `skills/smrt-catalog/evals.json`
|
|
8
|
+
holds the positive/negative trigger prompts and `src/agent-skills.test.ts` asserts every positive signal class
|
|
9
|
+
is named. `smrt docs:agents` installs it into consumers (`./agent-skills` export: `getAgentSkill`,
|
|
10
|
+
`getAgentSkillsPackageVersion`).
|
|
11
|
+
- `skills/smrt-contribute/SKILL.md` — contributor skill (#4709): `CONTRIBUTING.md` as a fork-PR procedure with the
|
|
12
|
+
traps that fail from outside (package-name test filter, format then lint, generated knowledge pages, PR title
|
|
13
|
+
≤92, no changesets, `.github/` guard, fork CI approval). It deliberately omits the maintainer lifecycle.
|
|
14
|
+
The repository root checks in an identical copy at `.agents/skills/smrt-contribute/SKILL.md` so a harness
|
|
15
|
+
opening a checkout finds it without the MCP server; `src/agent-skills.test.ts` fails when they drift, so edit
|
|
16
|
+
both together. Trigger evals: `skills/smrt-contribute/evals.json`.
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Intent-driven duplicate detection (#4690)
|
|
2
|
+
|
|
3
|
+
`review-smrt-project` parses the intent index at runtime
|
|
4
|
+
(`intent-index.ts`, pure) and `intent-detector.ts` evaluates each row's signals;
|
|
5
|
+
no intent is named in code, so a new row with signals needs no change here.
|
|
6
|
+
Trust boundary: `regex:` tokens become `RegExp`, so rules are never read from the
|
|
7
|
+
reviewed tree. Index lookup: `intentIndexPath` / `--intent-index` (trusted by the
|
|
8
|
+
caller), else the full unfiltered index shipped in this package's
|
|
9
|
+
`dist/knowledge/intents/` (`scripts/ship-knowledge-pages.mjs`, list
|
|
10
|
+
`OKF_INTENT_HOME_PACKAGES`), else the monorepo `docs/knowledge/intents/index.md`
|
|
11
|
+
when run from the smrt checkout. The exported consumer bundle index is filtered
|
|
12
|
+
to installed packages and is not consulted. Tokens:
|
|
13
|
+
`import:<module>`, `call:<ident>`, `regex:<pattern>`, and
|
|
14
|
+
`decimal-field:<glob>` (a property matching the glob declared with a decimal
|
|
15
|
+
literal or `type: 'decimal'`). Unknown kinds are skipped with a stderr note. A
|
|
16
|
+
finding needs a signal and no import of any row home package in the same
|
|
17
|
+
package; it is advisory (`area: duplicate-detection`, `file: path:line`,
|
|
18
|
+
`intentPage`, `componentPages`). Fixtures live in `test-fixtures/` (outside
|
|
19
|
+
`src/`, so tsc, Biome, and vitest globs never see them).
|
|
20
|
+
|
|
21
|
+
`smrt-dev-mcp review --directory . [--intent-index P] [--format summary]
|
|
22
|
+
[--fail-on-findings]` runs the pass from a shell (exit 2 with a stderr message when
|
|
23
|
+
no index is found; the tool reports `skipped` with a note). The reusable
|
|
24
|
+
`.github/workflows/consumer-knowledge-review.yml` calls it through the pinned
|
|
25
|
+
published package (public on npmjs, so private consumers need no token), has no
|
|
26
|
+
index input, and writes the summary to `$GITHUB_STEP_SUMMARY`.
|
package/dist/agent-skills.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export interface AgentSkillBundle extends AgentSkillSummary {
|
|
|
11
11
|
content: string;
|
|
12
12
|
}>;
|
|
13
13
|
}
|
|
14
|
+
/** Version of the smrt-dev-mcp package that ships these skills. */
|
|
15
|
+
export declare function getAgentSkillsPackageVersion(): string;
|
|
14
16
|
export declare function listAgentSkills(): AgentSkillSummary[];
|
|
15
17
|
export declare function getAgentSkill(options: {
|
|
16
18
|
name: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-skills.d.ts","sourceRoot":"","sources":["../src/agent-skills.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,KAAK,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;
|
|
1
|
+
{"version":3,"file":"agent-skills.d.ts","sourceRoot":"","sources":["../src/agent-skills.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,KAAK,CAAC;QACpB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC,CAAC;CACJ;AAiCD,mEAAmE;AACnE,wBAAgB,4BAA4B,IAAI,MAAM,CAKrD;AAED,wBAAgB,eAAe,IAAI,iBAAiB,EAAE,CAErD;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC7B,GAAG,gBAAgB,CAoBnB"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/agent-skills.ts
|
|
5
|
+
var AGENT_SKILLS = [
|
|
6
|
+
{
|
|
7
|
+
name: "smrt-code-review",
|
|
8
|
+
description: "Harness-agnostic downstream SMRT code review workflow using smrt-dev-mcp deterministic context and prompt bundles.",
|
|
9
|
+
path: "skills/smrt-code-review/SKILL.md",
|
|
10
|
+
skillFile: "skills/smrt-code-review/SKILL.md",
|
|
11
|
+
references: ["skills/smrt-code-review/references/review-output.md"]
|
|
12
|
+
},
|
|
13
|
+
{
|
|
14
|
+
name: "smrt-catalog",
|
|
15
|
+
description: "Router skill: before writing infrastructure, utility, or integration code in a project that depends on smrt packages, consult the smrt knowledge catalog and reuse the installed package.",
|
|
16
|
+
path: "skills/smrt-catalog/SKILL.md",
|
|
17
|
+
skillFile: "skills/smrt-catalog/SKILL.md",
|
|
18
|
+
references: []
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "smrt-contribute",
|
|
22
|
+
description: "Contributor skill: the fork pull-request procedure for happyvertical/smrt (CONTRIBUTING.md as steps) for agents working in a fork or clone that is not a maintainer session.",
|
|
23
|
+
path: "skills/smrt-contribute/SKILL.md",
|
|
24
|
+
skillFile: "skills/smrt-contribute/SKILL.md",
|
|
25
|
+
references: []
|
|
26
|
+
}
|
|
27
|
+
];
|
|
28
|
+
/** Version of the smrt-dev-mcp package that ships these skills. */
|
|
29
|
+
function getAgentSkillsPackageVersion() {
|
|
30
|
+
return JSON.parse(readPackageFile("package.json")).version ?? "0.0.0";
|
|
31
|
+
}
|
|
32
|
+
function listAgentSkills() {
|
|
33
|
+
return AGENT_SKILLS.map(({ skillFile: _skillFile, ...skill }) => skill);
|
|
34
|
+
}
|
|
35
|
+
function getAgentSkill(options) {
|
|
36
|
+
const skill = AGENT_SKILLS.find((item) => item.name === options.name);
|
|
37
|
+
if (!skill) throw new Error(`Unknown agent skill: ${options.name}`);
|
|
38
|
+
return {
|
|
39
|
+
name: skill.name,
|
|
40
|
+
description: skill.description,
|
|
41
|
+
path: skill.path,
|
|
42
|
+
references: skill.references,
|
|
43
|
+
skillMarkdown: readPackageFile(skill.skillFile),
|
|
44
|
+
referenceFiles: options.includeReferences === false ? [] : skill.references.map((path) => ({
|
|
45
|
+
path,
|
|
46
|
+
content: readPackageFile(path)
|
|
47
|
+
}))
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
function readPackageFile(relativePath) {
|
|
51
|
+
return readFileSync(join(resolvePackageRoot(), relativePath), "utf8");
|
|
52
|
+
}
|
|
53
|
+
function resolvePackageRoot() {
|
|
54
|
+
const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
55
|
+
if (!existsSync(join(packageRoot, "package.json"))) throw new Error(`Unable to resolve smrt-dev-mcp package root from ${import.meta.url}`);
|
|
56
|
+
return packageRoot;
|
|
57
|
+
}
|
|
58
|
+
//#endregion
|
|
59
|
+
export { getAgentSkill, getAgentSkillsPackageVersion, listAgentSkills };
|
|
60
|
+
|
|
61
|
+
//# sourceMappingURL=agent-skills.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-skills.js","names":[],"sources":["../src/agent-skills.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport interface AgentSkillSummary {\n name: string;\n description: string;\n path: string;\n references: string[];\n}\n\nexport interface AgentSkillBundle extends AgentSkillSummary {\n skillMarkdown: string;\n referenceFiles: Array<{\n path: string;\n content: string;\n }>;\n}\n\ninterface AgentSkillDefinition extends AgentSkillSummary {\n skillFile: string;\n}\n\nconst AGENT_SKILLS: AgentSkillDefinition[] = [\n {\n name: 'smrt-code-review',\n description:\n 'Harness-agnostic downstream SMRT code review workflow using smrt-dev-mcp deterministic context and prompt bundles.',\n path: 'skills/smrt-code-review/SKILL.md',\n skillFile: 'skills/smrt-code-review/SKILL.md',\n references: ['skills/smrt-code-review/references/review-output.md'],\n },\n {\n name: 'smrt-catalog',\n description:\n 'Router skill: before writing infrastructure, utility, or integration code in a project that depends on smrt packages, consult the smrt knowledge catalog and reuse the installed package.',\n path: 'skills/smrt-catalog/SKILL.md',\n skillFile: 'skills/smrt-catalog/SKILL.md',\n references: [],\n },\n {\n name: 'smrt-contribute',\n description:\n 'Contributor skill: the fork pull-request procedure for happyvertical/smrt (CONTRIBUTING.md as steps) for agents working in a fork or clone that is not a maintainer session.',\n path: 'skills/smrt-contribute/SKILL.md',\n skillFile: 'skills/smrt-contribute/SKILL.md',\n references: [],\n },\n];\n\n/** Version of the smrt-dev-mcp package that ships these skills. */\nexport function getAgentSkillsPackageVersion(): string {\n const manifest = JSON.parse(readPackageFile('package.json')) as {\n version?: string;\n };\n return manifest.version ?? '0.0.0';\n}\n\nexport function listAgentSkills(): AgentSkillSummary[] {\n return AGENT_SKILLS.map(({ skillFile: _skillFile, ...skill }) => skill);\n}\n\nexport function getAgentSkill(options: {\n name: string;\n includeReferences?: boolean;\n}): AgentSkillBundle {\n const skill = AGENT_SKILLS.find((item) => item.name === options.name);\n if (!skill) {\n throw new Error(`Unknown agent skill: ${options.name}`);\n }\n\n return {\n name: skill.name,\n description: skill.description,\n path: skill.path,\n references: skill.references,\n skillMarkdown: readPackageFile(skill.skillFile),\n referenceFiles:\n options.includeReferences === false\n ? []\n : skill.references.map((path) => ({\n path,\n content: readPackageFile(path),\n })),\n };\n}\n\nfunction readPackageFile(relativePath: string): string {\n const packageRoot = resolvePackageRoot();\n return readFileSync(join(packageRoot, relativePath), 'utf8');\n}\n\nfunction resolvePackageRoot(): string {\n // Works in both source tests (`src/agent-skills.ts`) and built packages\n // (`dist/agent-skills.js`); guard it so packaging layout drift fails clearly.\n const packageRoot = dirname(dirname(fileURLToPath(import.meta.url)));\n if (!existsSync(join(packageRoot, 'package.json'))) {\n throw new Error(\n `Unable to resolve smrt-dev-mcp package root from ${import.meta.url}`,\n );\n }\n return packageRoot;\n}\n"],"mappings":";;;;AAuBA,IAAM,eAAuC;CAC3C;EACE,MAAM;EACN,aACE;EACF,MAAM;EACN,WAAW;EACX,YAAY,CAAC,qDAAqD;CACpE;CACA;EACE,MAAM;EACN,aACE;EACF,MAAM;EACN,WAAW;EACX,YAAY,CAAC;CACf;CACA;EACE,MAAM;EACN,aACE;EACF,MAAM;EACN,WAAW;EACX,YAAY,CAAC;CACf;AACF;;AAGA,SAAgB,+BAAuC;CAIrD,OAHiB,KAAK,MAAM,gBAAgB,cAAc,CAGnD,CAAA,CAAS,WAAW;AAC7B;AAEA,SAAgB,kBAAuC;CACrD,OAAO,aAAa,KAAK,EAAE,WAAW,YAAY,GAAG,YAAY,KAAK;AACxE;AAEA,SAAgB,cAAc,SAGT;CACnB,MAAM,QAAQ,aAAa,MAAM,SAAS,KAAK,SAAS,QAAQ,IAAI;CACpE,IAAI,CAAC,OACH,MAAM,IAAI,MAAM,wBAAwB,QAAQ,MAAM;CAGxD,OAAO;EACL,MAAM,MAAM;EACZ,aAAa,MAAM;EACnB,MAAM,MAAM;EACZ,YAAY,MAAM;EAClB,eAAe,gBAAgB,MAAM,SAAS;EAC9C,gBACE,QAAQ,sBAAsB,QAC1B,CAAC,IACD,MAAM,WAAW,KAAK,UAAU;GAC9B;GACA,SAAS,gBAAgB,IAAI;EAC/B,EAAE;CACV;AACF;AAEA,SAAS,gBAAgB,cAA8B;CAErD,OAAO,aAAa,KADA,mBACK,GAAa,YAAY,GAAG,MAAM;AAC7D;AAEA,SAAS,qBAA6B;CAGpC,MAAM,cAAc,QAAQ,QAAQ,cAAc,OAAO,KAAK,GAAG,CAAC,CAAC;CACnE,IAAI,CAAC,WAAW,KAAK,aAAa,cAAc,CAAC,GAC/C,MAAM,IAAI,MACR,oDAAoD,OAAO,KAAK,KAClE;CAEF,OAAO;AACT"}
|
package/dist/dev-plane.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { a as runtimeMigrationStatus, c as runtimeScheduleHealth, g as loadProjectCore, i as runtimeJobHealth, m as ProjectRuntimeResolutionError, o as runtimeRecentChanges, p as safeErrorMessage, r as runtimeDispatchHealth, s as runtimeRegistryDrift, t as RUNTIME_PROVENANCE } from "./tools-B0uaPTz0.js";
|
|
2
|
-
import { i as SERVER_VERSION, r as SERVER_NAME, t as bearerMatches } from "./http-
|
|
3
|
-
import { n as TOOLS } from "./tool-catalog-
|
|
2
|
+
import { i as SERVER_VERSION, r as SERVER_NAME, t as bearerMatches } from "./http-BqKiyqgN.js";
|
|
3
|
+
import { n as TOOLS } from "./tool-catalog-BhnSCF8g.js";
|
|
4
4
|
import { i as runtimeSchemaDiff, n as runtimeObject, r as runtimeRegistry } from "./observation-DbB4z-9t.js";
|
|
5
5
|
import { Server, createMcpHandler } from "@modelcontextprotocol/server";
|
|
6
6
|
//#region src/dev-plane.ts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { a as runtimeMigrationStatus, c as runtimeScheduleHealth, i as runtimeJobHealth, o as runtimeRecentChanges, r as runtimeDispatchHealth, s as runtimeRegistryDrift } from "./tools-B0uaPTz0.js";
|
|
2
|
-
import { n as TOOLS } from "./tool-catalog-
|
|
2
|
+
import { n as TOOLS } from "./tool-catalog-BhnSCF8g.js";
|
|
3
3
|
import { a as bootRuntime, i as runtimeSchemaDiff, n as runtimeObject, r as runtimeRegistry } from "./observation-DbB4z-9t.js";
|
|
4
4
|
import { readFileSync } from "node:fs";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
@@ -166,4 +166,4 @@ async function startRuntimeHttpHost(options = {}) {
|
|
|
166
166
|
//#endregion
|
|
167
167
|
export { SERVER_VERSION as i, startRuntimeHttpHost as n, SERVER_NAME as r, bearerMatches as t };
|
|
168
168
|
|
|
169
|
-
//# sourceMappingURL=http-
|
|
169
|
+
//# sourceMappingURL=http-BqKiyqgN.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-CCiO7qQW.js","names":[],"sources":["../src/server-info.ts","../src/http.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const SERVER_NAME = 'smrt-dev-mcp';\n\nfunction readPackageVersion(): string {\n try {\n const packageRoot = dirname(fileURLToPath(import.meta.url));\n const packageJsonPath = join(packageRoot, '..', 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {\n version?: unknown;\n };\n return typeof packageJson.version === 'string'\n ? packageJson.version\n : '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nexport const SERVER_VERSION = readPackageVersion();\n","/**\n * Standalone Level 2 runtime dev-plane host (#1831).\n *\n * Serves a *positive* read-only tool catalog over the stateless 2026-07-28\n * Streamable HTTP transport (#2147): `createMcpHandler` with `legacy: 'reject'`\n * and `maxSubscriptions: 0`, adapted to Node with `toNodeHandler`. No SSE,\n * no session header, no sticky routing.\n *\n * Security boundary (development only):\n * - binds loopback only; SDK localhost Host/Origin validation runs first;\n * - every request needs `Authorization: Bearer <token>` (constant-time\n * compare) — from `SMRT_DEV_MCP_TOKEN` or minted per process;\n * - no authenticated principal exists here, so scope stays fail-closed\n * global-only exactly as in Level 1;\n * - the catalog never includes generated CRUD, custom actions, `do()`, or\n * tool-backed `is()`.\n */\n\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport {\n createServer as createHttpServer,\n type Server as HttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport {\n localhostHostValidation,\n localhostOriginValidation,\n toNodeHandler,\n} from '@modelcontextprotocol/node';\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { createMcpHandler, Server } from '@modelcontextprotocol/server';\nimport { SERVER_NAME, SERVER_VERSION } from './server-info.js';\nimport { TOOLS } from './tool-catalog.js';\nimport { bootRuntime, type RuntimeBoot } from './tools/runtime/boot.js';\nimport {\n runtimeObject,\n runtimeRegistry,\n runtimeSchemaDiff,\n} from './tools/runtime/observation.js';\nimport {\n runtimeDispatchHealth,\n runtimeJobHealth,\n runtimeMigrationStatus,\n runtimeRecentChanges,\n runtimeRegistryDrift,\n runtimeScheduleHealth,\n} from './tools/runtime/tools.js';\n\n/**\n * The complete Level 2 catalog. A tool is exposed over HTTP only if it is\n * named here; the static stdio catalog is deliberately not mounted.\n */\nexport const RUNTIME_HTTP_TOOL_NAMES = [\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\nexport type RuntimeHttpToolName = (typeof RUNTIME_HTTP_TOOL_NAMES)[number];\n\nconst RUNTIME_HTTP_HANDLERS: Record<\n RuntimeHttpToolName,\n (args: Record<string, unknown>) => Promise<unknown>\n> = {\n 'runtime-registry': (args) => runtimeRegistry(args as never),\n 'runtime-object': (args) => runtimeObject(args as never),\n 'runtime-schema-diff': (args) => runtimeSchemaDiff(args as never),\n 'migration-status': (args) => runtimeMigrationStatus(args as never),\n 'job-health': (args) => runtimeJobHealth(args as never),\n 'schedule-health': (args) => runtimeScheduleHealth(args as never),\n 'dispatch-health': (args) => runtimeDispatchHealth(args as never),\n 'recent-changes': (args) => runtimeRecentChanges(args as never),\n 'registry-drift': (args) => runtimeRegistryDrift(args as never),\n};\n\n/** Catalog definitions for the HTTP plane, in catalog order. */\nexport function runtimeHttpTools() {\n const names = new Set<string>(RUNTIME_HTTP_TOOL_NAMES);\n return TOOLS.filter((tool) => names.has(tool.name));\n}\n\n/**\n * Build a fresh protocol server per request (stateless transport contract).\n * `projectRoot` is fixed at host start; per-request `projectPath` arguments\n * are ignored so a client cannot re-point the booted process.\n */\nexport function createRuntimeProtocolServer(projectRoot: string): Server {\n const server = new Server(\n { name: `${SERVER_NAME}-runtime`, version: SERVER_VERSION },\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler('tools/list', async () => ({\n tools: runtimeHttpTools(),\n }));\n server.setRequestHandler('tools/call', async (request) => {\n const name = request.params.name as string;\n const handler = (RUNTIME_HTTP_HANDLERS as Record<string, unknown>)[name];\n if (typeof handler !== 'function') {\n throw new Error(`Unknown runtime tool: ${name}`);\n }\n const { projectPath: _ignored, ...args } = (request.params.arguments ??\n {}) as Record<string, unknown>;\n const result = await (\n handler as (a: Record<string, unknown>) => Promise<unknown>\n )({ ...args, projectPath: projectRoot });\n const text = JSON.stringify(result, null, 2);\n return {\n content: [{ type: 'text', text }],\n structuredContent: result as Record<string, unknown>,\n } satisfies CallToolResult;\n });\n return server;\n}\n\nexport interface RuntimeHttpHostOptions {\n projectRoot?: string;\n /** Loopback interface only; anything else is refused at startup. */\n host?: '127.0.0.1' | 'localhost' | '::1';\n /** `0` (default) picks a free port. */\n port?: number;\n /** Bearer token; defaults to `SMRT_DEV_MCP_TOKEN` or a per-process random token. */\n token?: string;\n /** Path the MCP endpoint is mounted on (default `/mcp`). */\n path?: string;\n}\n\nexport interface RuntimeHttpHost {\n url: string;\n token: string;\n boot: RuntimeBoot;\n close(): Promise<void>;\n}\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);\n\nexport function bearerMatches(\n header: string | undefined,\n token: string,\n): boolean {\n if (!header?.startsWith('Bearer ')) return false;\n const presented = Buffer.from(header.slice('Bearer '.length));\n const expected = Buffer.from(token);\n return (\n presented.length === expected.length && timingSafeEqual(presented, expected)\n );\n}\n\nfunction deny(res: ServerResponse, status: number, message: string): void {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json');\n if (status === 401) {\n res.setHeader('www-authenticate', 'Bearer realm=\"smrt-dev-mcp runtime\"');\n }\n res.end(JSON.stringify({ error: message }));\n}\n\n/** Start the Level 2 host. Boots the confined runtime before listening. */\nexport async function startRuntimeHttpHost(\n options: RuntimeHttpHostOptions = {},\n): Promise<RuntimeHttpHost> {\n const host = options.host ?? '127.0.0.1';\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n 'The runtime dev-plane binds loopback only (127.0.0.1, localhost, ::1).',\n );\n }\n const path = options.path ?? '/mcp';\n const token =\n options.token ??\n process.env.SMRT_DEV_MCP_TOKEN?.trim() ??\n randomBytes(24).toString('base64url');\n if (!token) {\n throw new Error('SMRT_DEV_MCP_TOKEN must not be empty.');\n }\n const projectRoot = options.projectRoot ?? process.cwd();\n const boot = await bootRuntime({ projectRoot });\n\n const mcp = toNodeHandler(\n createMcpHandler(() => createRuntimeProtocolServer(projectRoot), {\n legacy: 'reject',\n maxSubscriptions: 0,\n }),\n );\n const hostGuard = localhostHostValidation();\n const originGuard = localhostOriginValidation();\n\n const httpServer: HttpServer = createHttpServer(\n (req: IncomingMessage, res: ServerResponse) => {\n // Guards answer and end the response themselves when they return false.\n if (!hostGuard(req, res)) return;\n if (!originGuard(req, res)) return;\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname !== path) {\n deny(res, 404, 'not found');\n return;\n }\n if (!bearerMatches(req.headers.authorization, token)) {\n deny(res, 401, 'missing or invalid bearer token');\n return;\n }\n void mcp(req, res);\n },\n );\n\n await new Promise<void>((resolve, reject) => {\n httpServer.once('error', reject);\n httpServer.listen(options.port ?? 0, host, () => {\n httpServer.off('error', reject);\n resolve();\n });\n });\n const address = httpServer.address() as AddressInfo;\n const urlHost =\n address.family === 'IPv6' ? `[${address.address}]` : address.address;\n return {\n url: `http://${urlHost}:${address.port}${path}`,\n token,\n boot,\n close: () =>\n new Promise<void>((resolve, reject) => {\n httpServer.closeAllConnections?.();\n httpServer.close((error) => (error ? reject(error) : resolve()));\n }),\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"http-BqKiyqgN.js","names":[],"sources":["../src/server-info.ts","../src/http.ts"],"sourcesContent":["import { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport const SERVER_NAME = 'smrt-dev-mcp';\n\nfunction readPackageVersion(): string {\n try {\n const packageRoot = dirname(fileURLToPath(import.meta.url));\n const packageJsonPath = join(packageRoot, '..', 'package.json');\n const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')) as {\n version?: unknown;\n };\n return typeof packageJson.version === 'string'\n ? packageJson.version\n : '0.0.0';\n } catch {\n return '0.0.0';\n }\n}\n\nexport const SERVER_VERSION = readPackageVersion();\n","/**\n * Standalone Level 2 runtime dev-plane host (#1831).\n *\n * Serves a *positive* read-only tool catalog over the stateless 2026-07-28\n * Streamable HTTP transport (#2147): `createMcpHandler` with `legacy: 'reject'`\n * and `maxSubscriptions: 0`, adapted to Node with `toNodeHandler`. No SSE,\n * no session header, no sticky routing.\n *\n * Security boundary (development only):\n * - binds loopback only; SDK localhost Host/Origin validation runs first;\n * - every request needs `Authorization: Bearer <token>` (constant-time\n * compare) — from `SMRT_DEV_MCP_TOKEN` or minted per process;\n * - no authenticated principal exists here, so scope stays fail-closed\n * global-only exactly as in Level 1;\n * - the catalog never includes generated CRUD, custom actions, `do()`, or\n * tool-backed `is()`.\n */\n\nimport { randomBytes, timingSafeEqual } from 'node:crypto';\nimport {\n createServer as createHttpServer,\n type Server as HttpServer,\n type IncomingMessage,\n type ServerResponse,\n} from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport {\n localhostHostValidation,\n localhostOriginValidation,\n toNodeHandler,\n} from '@modelcontextprotocol/node';\nimport type { CallToolResult } from '@modelcontextprotocol/server';\nimport { createMcpHandler, Server } from '@modelcontextprotocol/server';\nimport { SERVER_NAME, SERVER_VERSION } from './server-info.js';\nimport { TOOLS } from './tool-catalog.js';\nimport { bootRuntime, type RuntimeBoot } from './tools/runtime/boot.js';\nimport {\n runtimeObject,\n runtimeRegistry,\n runtimeSchemaDiff,\n} from './tools/runtime/observation.js';\nimport {\n runtimeDispatchHealth,\n runtimeJobHealth,\n runtimeMigrationStatus,\n runtimeRecentChanges,\n runtimeRegistryDrift,\n runtimeScheduleHealth,\n} from './tools/runtime/tools.js';\n\n/**\n * The complete Level 2 catalog. A tool is exposed over HTTP only if it is\n * named here; the static stdio catalog is deliberately not mounted.\n */\nexport const RUNTIME_HTTP_TOOL_NAMES = [\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\nexport type RuntimeHttpToolName = (typeof RUNTIME_HTTP_TOOL_NAMES)[number];\n\nconst RUNTIME_HTTP_HANDLERS: Record<\n RuntimeHttpToolName,\n (args: Record<string, unknown>) => Promise<unknown>\n> = {\n 'runtime-registry': (args) => runtimeRegistry(args as never),\n 'runtime-object': (args) => runtimeObject(args as never),\n 'runtime-schema-diff': (args) => runtimeSchemaDiff(args as never),\n 'migration-status': (args) => runtimeMigrationStatus(args as never),\n 'job-health': (args) => runtimeJobHealth(args as never),\n 'schedule-health': (args) => runtimeScheduleHealth(args as never),\n 'dispatch-health': (args) => runtimeDispatchHealth(args as never),\n 'recent-changes': (args) => runtimeRecentChanges(args as never),\n 'registry-drift': (args) => runtimeRegistryDrift(args as never),\n};\n\n/** Catalog definitions for the HTTP plane, in catalog order. */\nexport function runtimeHttpTools() {\n const names = new Set<string>(RUNTIME_HTTP_TOOL_NAMES);\n return TOOLS.filter((tool) => names.has(tool.name));\n}\n\n/**\n * Build a fresh protocol server per request (stateless transport contract).\n * `projectRoot` is fixed at host start; per-request `projectPath` arguments\n * are ignored so a client cannot re-point the booted process.\n */\nexport function createRuntimeProtocolServer(projectRoot: string): Server {\n const server = new Server(\n { name: `${SERVER_NAME}-runtime`, version: SERVER_VERSION },\n { capabilities: { tools: {} } },\n );\n server.setRequestHandler('tools/list', async () => ({\n tools: runtimeHttpTools(),\n }));\n server.setRequestHandler('tools/call', async (request) => {\n const name = request.params.name as string;\n const handler = (RUNTIME_HTTP_HANDLERS as Record<string, unknown>)[name];\n if (typeof handler !== 'function') {\n throw new Error(`Unknown runtime tool: ${name}`);\n }\n const { projectPath: _ignored, ...args } = (request.params.arguments ??\n {}) as Record<string, unknown>;\n const result = await (\n handler as (a: Record<string, unknown>) => Promise<unknown>\n )({ ...args, projectPath: projectRoot });\n const text = JSON.stringify(result, null, 2);\n return {\n content: [{ type: 'text', text }],\n structuredContent: result as Record<string, unknown>,\n } satisfies CallToolResult;\n });\n return server;\n}\n\nexport interface RuntimeHttpHostOptions {\n projectRoot?: string;\n /** Loopback interface only; anything else is refused at startup. */\n host?: '127.0.0.1' | 'localhost' | '::1';\n /** `0` (default) picks a free port. */\n port?: number;\n /** Bearer token; defaults to `SMRT_DEV_MCP_TOKEN` or a per-process random token. */\n token?: string;\n /** Path the MCP endpoint is mounted on (default `/mcp`). */\n path?: string;\n}\n\nexport interface RuntimeHttpHost {\n url: string;\n token: string;\n boot: RuntimeBoot;\n close(): Promise<void>;\n}\n\nconst LOOPBACK_HOSTS = new Set(['127.0.0.1', 'localhost', '::1']);\n\nexport function bearerMatches(\n header: string | undefined,\n token: string,\n): boolean {\n if (!header?.startsWith('Bearer ')) return false;\n const presented = Buffer.from(header.slice('Bearer '.length));\n const expected = Buffer.from(token);\n return (\n presented.length === expected.length && timingSafeEqual(presented, expected)\n );\n}\n\nfunction deny(res: ServerResponse, status: number, message: string): void {\n res.statusCode = status;\n res.setHeader('content-type', 'application/json');\n if (status === 401) {\n res.setHeader('www-authenticate', 'Bearer realm=\"smrt-dev-mcp runtime\"');\n }\n res.end(JSON.stringify({ error: message }));\n}\n\n/** Start the Level 2 host. Boots the confined runtime before listening. */\nexport async function startRuntimeHttpHost(\n options: RuntimeHttpHostOptions = {},\n): Promise<RuntimeHttpHost> {\n const host = options.host ?? '127.0.0.1';\n if (!LOOPBACK_HOSTS.has(host)) {\n throw new Error(\n 'The runtime dev-plane binds loopback only (127.0.0.1, localhost, ::1).',\n );\n }\n const path = options.path ?? '/mcp';\n const token =\n options.token ??\n process.env.SMRT_DEV_MCP_TOKEN?.trim() ??\n randomBytes(24).toString('base64url');\n if (!token) {\n throw new Error('SMRT_DEV_MCP_TOKEN must not be empty.');\n }\n const projectRoot = options.projectRoot ?? process.cwd();\n const boot = await bootRuntime({ projectRoot });\n\n const mcp = toNodeHandler(\n createMcpHandler(() => createRuntimeProtocolServer(projectRoot), {\n legacy: 'reject',\n maxSubscriptions: 0,\n }),\n );\n const hostGuard = localhostHostValidation();\n const originGuard = localhostOriginValidation();\n\n const httpServer: HttpServer = createHttpServer(\n (req: IncomingMessage, res: ServerResponse) => {\n // Guards answer and end the response themselves when they return false.\n if (!hostGuard(req, res)) return;\n if (!originGuard(req, res)) return;\n const url = new URL(req.url ?? '/', 'http://localhost');\n if (url.pathname !== path) {\n deny(res, 404, 'not found');\n return;\n }\n if (!bearerMatches(req.headers.authorization, token)) {\n deny(res, 401, 'missing or invalid bearer token');\n return;\n }\n void mcp(req, res);\n },\n );\n\n await new Promise<void>((resolve, reject) => {\n httpServer.once('error', reject);\n httpServer.listen(options.port ?? 0, host, () => {\n httpServer.off('error', reject);\n resolve();\n });\n });\n const address = httpServer.address() as AddressInfo;\n const urlHost =\n address.family === 'IPv6' ? `[${address.address}]` : address.address;\n return {\n url: `http://${urlHost}:${address.port}${path}`,\n token,\n boot,\n close: () =>\n new Promise<void>((resolve, reject) => {\n httpServer.closeAllConnections?.();\n httpServer.close((error) => (error ? reject(error) : resolve()));\n }),\n };\n}\n"],"mappings":"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"}
|