@nifrajs/cli 3.3.0 → 4.0.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/README.md +42 -3
- package/dist/app-files.d.ts +11 -0
- package/dist/app-files.d.ts.map +1 -0
- package/dist/app-files.js +34 -0
- package/dist/app-files.js.map +1 -0
- package/dist/assure-hydration.d.ts +7 -1
- package/dist/assure-hydration.d.ts.map +1 -1
- package/dist/assure-hydration.js +128 -43
- package/dist/assure-hydration.js.map +1 -1
- package/dist/assure.d.ts.map +1 -1
- package/dist/assure.js +16 -2
- package/dist/assure.js.map +1 -1
- package/dist/capabilities-tool.d.ts +5 -1
- package/dist/capabilities-tool.d.ts.map +1 -1
- package/dist/capabilities-tool.js +32 -10
- package/dist/capabilities-tool.js.map +1 -1
- package/dist/cdn-check.d.ts +41 -0
- package/dist/cdn-check.d.ts.map +1 -0
- package/dist/cdn-check.js +207 -0
- package/dist/cdn-check.js.map +1 -0
- package/dist/check-diagnostics.d.ts +7 -15
- package/dist/check-diagnostics.d.ts.map +1 -1
- package/dist/check-diagnostics.js +162 -713
- package/dist/check-diagnostics.js.map +1 -1
- package/dist/check-scan.d.ts +58 -7
- package/dist/check-scan.d.ts.map +1 -1
- package/dist/check-scan.js +323 -62
- package/dist/check-scan.js.map +1 -1
- package/dist/check.d.ts +1 -0
- package/dist/check.d.ts.map +1 -1
- package/dist/check.js +145 -54
- package/dist/check.js.map +1 -1
- package/dist/cli.d.ts +42 -16
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +306 -87
- package/dist/cli.js.map +1 -1
- package/dist/command-catalog.d.ts +60 -5
- package/dist/command-catalog.d.ts.map +1 -1
- package/dist/command-catalog.js +298 -33
- package/dist/command-catalog.js.map +1 -1
- package/dist/config-target.d.ts +8 -0
- package/dist/config-target.d.ts.map +1 -0
- package/dist/config-target.js +45 -0
- package/dist/config-target.js.map +1 -0
- package/dist/contracts.d.ts +3 -2
- package/dist/contracts.d.ts.map +1 -1
- package/dist/contracts.js +56 -16
- package/dist/contracts.js.map +1 -1
- package/dist/db-audit.d.ts +37 -0
- package/dist/db-audit.d.ts.map +1 -0
- package/dist/db-audit.js +99 -0
- package/dist/db-audit.js.map +1 -0
- package/dist/db-child.d.ts +64 -0
- package/dist/db-child.d.ts.map +1 -0
- package/dist/db-child.js +213 -0
- package/dist/db-child.js.map +1 -0
- package/dist/db-config.d.ts +60 -0
- package/dist/db-config.d.ts.map +1 -0
- package/dist/db-config.js +176 -0
- package/dist/db-config.js.map +1 -0
- package/dist/db-tool.d.ts +84 -0
- package/dist/db-tool.d.ts.map +1 -0
- package/dist/db-tool.js +537 -0
- package/dist/db-tool.js.map +1 -0
- package/dist/dev-bun-config.d.ts +20 -13
- package/dist/dev-bun-config.d.ts.map +1 -1
- package/dist/dev-bun-config.js +98 -41
- package/dist/dev-bun-config.js.map +1 -1
- package/dist/dev-feed-tool.d.ts +106 -0
- package/dist/dev-feed-tool.d.ts.map +1 -0
- package/dist/dev-feed-tool.js +713 -0
- package/dist/dev-feed-tool.js.map +1 -0
- package/dist/dev-server-client.d.ts +67 -0
- package/dist/dev-server-client.d.ts.map +1 -0
- package/dist/dev-server-client.js +195 -0
- package/dist/dev-server-client.js.map +1 -0
- package/dist/diagnostics.d.ts +86 -0
- package/dist/diagnostics.d.ts.map +1 -1
- package/dist/diagnostics.js +85 -0
- package/dist/diagnostics.js.map +1 -1
- package/dist/diff-tool.d.ts +3 -3
- package/dist/diff-tool.d.ts.map +1 -1
- package/dist/diff-tool.js +6 -5
- package/dist/diff-tool.js.map +1 -1
- package/dist/docs-search.d.ts.map +1 -1
- package/dist/docs-search.js +12 -4
- package/dist/docs-search.js.map +1 -1
- package/dist/doctor.d.ts +17 -0
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +76 -15
- package/dist/doctor.js.map +1 -1
- package/dist/env-file.d.ts +15 -0
- package/dist/env-file.d.ts.map +1 -1
- package/dist/env-file.js +73 -1
- package/dist/env-file.js.map +1 -1
- package/dist/fix-recipes.d.ts.map +1 -1
- package/dist/fix-recipes.js +41 -21
- package/dist/fix-recipes.js.map +1 -1
- package/dist/frontend-guidance.js +5 -5
- package/dist/frontend-guidance.js.map +1 -1
- package/dist/i18n-check.d.ts +16 -0
- package/dist/i18n-check.d.ts.map +1 -0
- package/dist/i18n-check.js +154 -0
- package/dist/i18n-check.js.map +1 -0
- package/dist/init-agents.d.ts +49 -13
- package/dist/init-agents.d.ts.map +1 -1
- package/dist/init-agents.js +239 -34
- package/dist/init-agents.js.map +1 -1
- package/dist/internal/code-unit-order.d.ts +4 -0
- package/dist/internal/code-unit-order.d.ts.map +1 -0
- package/dist/internal/code-unit-order.js +6 -0
- package/dist/internal/code-unit-order.js.map +1 -0
- package/dist/internal/script-kind.d.ts +7 -0
- package/dist/internal/script-kind.d.ts.map +1 -0
- package/dist/internal/script-kind.js +12 -0
- package/dist/internal/script-kind.js.map +1 -0
- package/dist/internal/source-facts.d.ts +5 -0
- package/dist/internal/source-facts.d.ts.map +1 -1
- package/dist/internal/source-facts.js +61 -5
- package/dist/internal/source-facts.js.map +1 -1
- package/dist/internal/typescript-import.d.ts +49 -24
- package/dist/internal/typescript-import.d.ts.map +1 -1
- package/dist/internal/typescript-import.js +507 -67
- package/dist/internal/typescript-import.js.map +1 -1
- package/dist/introspect.d.ts +6 -4
- package/dist/introspect.d.ts.map +1 -1
- package/dist/introspect.js +93 -74
- package/dist/introspect.js.map +1 -1
- package/dist/learn.js +3 -3
- package/dist/learn.js.map +1 -1
- package/dist/levels-tool.d.ts +10 -0
- package/dist/levels-tool.d.ts.map +1 -1
- package/dist/levels-tool.js +33 -3
- package/dist/levels-tool.js.map +1 -1
- package/dist/load.d.ts +71 -16
- package/dist/load.d.ts.map +1 -1
- package/dist/load.js +78 -18
- package/dist/load.js.map +1 -1
- package/dist/manifest-tool.d.ts.map +1 -1
- package/dist/manifest-tool.js +3 -2
- package/dist/manifest-tool.js.map +1 -1
- package/dist/mcp-app-tool.d.ts.map +1 -1
- package/dist/mcp-app-tool.js +2 -0
- package/dist/mcp-app-tool.js.map +1 -1
- package/dist/mcp-context.d.ts.map +1 -1
- package/dist/mcp-context.js +5 -6
- package/dist/mcp-context.js.map +1 -1
- package/dist/mcp-delegate.d.ts +57 -0
- package/dist/mcp-delegate.d.ts.map +1 -0
- package/dist/mcp-delegate.js +130 -0
- package/dist/mcp-delegate.js.map +1 -0
- package/dist/mcp-docs-tools.d.ts.map +1 -1
- package/dist/mcp-docs-tools.js +23 -5
- package/dist/mcp-docs-tools.js.map +1 -1
- package/dist/mcp-exec.d.ts +3 -0
- package/dist/mcp-exec.d.ts.map +1 -1
- package/dist/mcp-exec.js +263 -131
- package/dist/mcp-exec.js.map +1 -1
- package/dist/mcp-http.d.ts.map +1 -1
- package/dist/mcp-http.js +20 -5
- package/dist/mcp-http.js.map +1 -1
- package/dist/mcp-io.d.ts +26 -0
- package/dist/mcp-io.d.ts.map +1 -1
- package/dist/mcp-io.js +113 -0
- package/dist/mcp-io.js.map +1 -1
- package/dist/mcp-isolate.d.ts +48 -0
- package/dist/mcp-isolate.d.ts.map +1 -0
- package/dist/mcp-isolate.js +222 -0
- package/dist/mcp-isolate.js.map +1 -0
- package/dist/mcp-path.d.ts +10 -0
- package/dist/mcp-path.d.ts.map +1 -0
- package/dist/mcp-path.js +51 -0
- package/dist/mcp-path.js.map +1 -0
- package/dist/mcp-project-child.d.ts +74 -0
- package/dist/mcp-project-child.d.ts.map +1 -0
- package/dist/mcp-project-child.js +130 -0
- package/dist/mcp-project-child.js.map +1 -0
- package/dist/mcp-reflect.d.ts.map +1 -1
- package/dist/mcp-reflect.js +2 -2
- package/dist/mcp-reflect.js.map +1 -1
- package/dist/mcp-render.d.ts.map +1 -1
- package/dist/mcp-render.js +64 -45
- package/dist/mcp-render.js.map +1 -1
- package/dist/mcp-root.d.ts +37 -9
- package/dist/mcp-root.d.ts.map +1 -1
- package/dist/mcp-root.js +124 -25
- package/dist/mcp-root.js.map +1 -1
- package/dist/mcp-run.d.ts +18 -1
- package/dist/mcp-run.d.ts.map +1 -1
- package/dist/mcp-run.js +159 -55
- package/dist/mcp-run.js.map +1 -1
- package/dist/mcp-ws.js +4 -1
- package/dist/mcp-ws.js.map +1 -1
- package/dist/mcp.d.ts +18 -5
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +165 -82
- package/dist/mcp.js.map +1 -1
- package/dist/migrate-layout.d.ts +44 -0
- package/dist/migrate-layout.d.ts.map +1 -0
- package/dist/migrate-layout.js +982 -0
- package/dist/migrate-layout.js.map +1 -0
- package/dist/openapi-tool.d.ts +2 -2
- package/dist/openapi-tool.d.ts.map +1 -1
- package/dist/openapi-tool.js.map +1 -1
- package/dist/openapi-types.d.ts +2 -1
- package/dist/openapi-types.d.ts.map +1 -1
- package/dist/openapi-types.js +240 -12
- package/dist/openapi-types.js.map +1 -1
- package/dist/pipeline-report.d.ts +3 -2
- package/dist/pipeline-report.d.ts.map +1 -1
- package/dist/pipeline-report.js +19 -16
- package/dist/pipeline-report.js.map +1 -1
- package/dist/port.d.ts +5 -4
- package/dist/port.d.ts.map +1 -1
- package/dist/port.js +46 -29
- package/dist/port.js.map +1 -1
- package/dist/project-facts.d.ts +28 -4
- package/dist/project-facts.d.ts.map +1 -1
- package/dist/project-facts.js +12 -2
- package/dist/project-facts.js.map +1 -1
- package/dist/recipes/4.0.0.d.ts +9 -0
- package/dist/recipes/4.0.0.d.ts.map +1 -0
- package/dist/recipes/4.0.0.js +34 -0
- package/dist/recipes/4.0.0.js.map +1 -0
- package/dist/recipes/index.d.ts.map +1 -1
- package/dist/recipes/index.js +2 -0
- package/dist/recipes/index.js.map +1 -1
- package/dist/release-verification.d.ts +11 -1
- package/dist/release-verification.d.ts.map +1 -1
- package/dist/release-verification.js +47 -4
- package/dist/release-verification.js.map +1 -1
- package/dist/review.d.ts +21 -0
- package/dist/review.d.ts.map +1 -0
- package/dist/review.js +823 -0
- package/dist/review.js.map +1 -0
- package/dist/route-types.d.ts +28 -0
- package/dist/route-types.d.ts.map +1 -0
- package/dist/route-types.js +106 -0
- package/dist/route-types.js.map +1 -0
- package/dist/rules/codes.d.ts +9 -0
- package/dist/rules/codes.d.ts.map +1 -1
- package/dist/rules/codes.js +9 -0
- package/dist/rules/codes.js.map +1 -1
- package/dist/rules/data-guard.d.ts +12 -0
- package/dist/rules/data-guard.d.ts.map +1 -0
- package/dist/rules/data-guard.js +213 -0
- package/dist/rules/data-guard.js.map +1 -0
- package/dist/rules/index.d.ts +3 -0
- package/dist/rules/index.d.ts.map +1 -1
- package/dist/rules/index.js +17 -5
- package/dist/rules/index.js.map +1 -1
- package/dist/rules/islands.d.ts.map +1 -1
- package/dist/rules/islands.js +7 -4
- package/dist/rules/islands.js.map +1 -1
- package/dist/rules/legacy.d.ts +3 -1
- package/dist/rules/legacy.d.ts.map +1 -1
- package/dist/rules/legacy.js +829 -34
- package/dist/rules/legacy.js.map +1 -1
- package/dist/rules/nano.d.ts.map +1 -1
- package/dist/rules/nano.js +15 -6
- package/dist/rules/nano.js.map +1 -1
- package/dist/rules/pages.d.ts +15 -0
- package/dist/rules/pages.d.ts.map +1 -0
- package/dist/rules/pages.js +88 -0
- package/dist/rules/pages.js.map +1 -0
- package/dist/rules/routes.d.ts +11 -0
- package/dist/rules/routes.d.ts.map +1 -1
- package/dist/rules/routes.js +180 -2
- package/dist/rules/routes.js.map +1 -1
- package/dist/rules/secrets.d.ts +10 -0
- package/dist/rules/secrets.d.ts.map +1 -0
- package/dist/rules/secrets.js +169 -0
- package/dist/rules/secrets.js.map +1 -0
- package/dist/rules/security.d.ts +7 -0
- package/dist/rules/security.d.ts.map +1 -1
- package/dist/rules/security.js +171 -65
- package/dist/rules/security.js.map +1 -1
- package/dist/rules/typescript.d.ts +16 -0
- package/dist/rules/typescript.d.ts.map +1 -0
- package/dist/rules/typescript.js +59 -0
- package/dist/rules/typescript.js.map +1 -0
- package/dist/rules/zones.d.ts +17 -0
- package/dist/rules/zones.d.ts.map +1 -0
- package/dist/rules/zones.js +174 -0
- package/dist/rules/zones.js.map +1 -0
- package/dist/scaffold.d.ts +25 -12
- package/dist/scaffold.d.ts.map +1 -1
- package/dist/scaffold.js +154 -90
- package/dist/scaffold.js.map +1 -1
- package/dist/sdk.d.ts +17 -3
- package/dist/sdk.d.ts.map +1 -1
- package/dist/sdk.js +941 -153
- package/dist/sdk.js.map +1 -1
- package/dist/search-terms.d.ts +2 -0
- package/dist/search-terms.d.ts.map +1 -1
- package/dist/search-terms.js +23 -8
- package/dist/search-terms.js.map +1 -1
- package/dist/smoke.d.ts +79 -0
- package/dist/smoke.d.ts.map +1 -0
- package/dist/smoke.js +494 -0
- package/dist/smoke.js.map +1 -0
- package/dist/sync-manifest.d.ts.map +1 -1
- package/dist/sync-manifest.js +2 -1
- package/dist/sync-manifest.js.map +1 -1
- package/dist/sync-routes.d.ts +4 -3
- package/dist/sync-routes.d.ts.map +1 -1
- package/dist/sync-routes.js +4 -3
- package/dist/sync-routes.js.map +1 -1
- package/dist/test-tool.d.ts.map +1 -1
- package/dist/test-tool.js +30 -11
- package/dist/test-tool.js.map +1 -1
- package/dist/types-search.d.ts.map +1 -1
- package/dist/types-search.js +11 -5
- package/dist/types-search.js.map +1 -1
- package/dist/upgrade.d.ts +36 -6
- package/dist/upgrade.d.ts.map +1 -1
- package/dist/upgrade.js +167 -29
- package/dist/upgrade.js.map +1 -1
- package/dist/verification-plan.d.ts.map +1 -1
- package/dist/verification-plan.js +8 -0
- package/dist/verification-plan.js.map +1 -1
- package/dist/verification.d.ts.map +1 -1
- package/dist/verification.js +45 -12
- package/dist/verification.js.map +1 -1
- package/dist/web-app-options.d.ts +19 -0
- package/dist/web-app-options.d.ts.map +1 -0
- package/dist/web-app-options.js +49 -0
- package/dist/web-app-options.js.map +1 -0
- package/dist/work-graph.d.ts +3 -0
- package/dist/work-graph.d.ts.map +1 -1
- package/dist/work-graph.js +33 -16
- package/dist/work-graph.js.map +1 -1
- package/docs/examples.json +185 -29
- package/docs/llms-full.txt +4123 -548
- package/docs/types.json +14391 -10060
- package/package.json +19 -11
- package/src/app-files.ts +40 -0
- package/src/assure-hydration.ts +139 -46
- package/src/assure.ts +17 -2
- package/src/capabilities-tool.ts +35 -9
- package/src/cdn-check.ts +268 -0
- package/src/check-diagnostics.ts +199 -797
- package/src/check-scan.ts +394 -63
- package/src/check.ts +146 -58
- package/src/cli.ts +347 -92
- package/src/command-catalog.ts +407 -45
- package/src/config-target.ts +47 -0
- package/src/contracts.ts +66 -15
- package/src/db-audit.ts +128 -0
- package/src/db-child.ts +285 -0
- package/src/db-config.ts +224 -0
- package/src/db-tool.ts +690 -0
- package/src/dev-bun-config.ts +116 -40
- package/src/dev-feed-tool.ts +912 -0
- package/src/dev-server-client.ts +267 -0
- package/src/diagnostics.ts +179 -0
- package/src/diff-tool.ts +6 -5
- package/src/docs-search.ts +10 -3
- package/src/doctor.ts +98 -15
- package/src/env-file.ts +78 -1
- package/src/fix-recipes.ts +44 -31
- package/src/frontend-guidance.ts +5 -5
- package/src/i18n-check.ts +179 -0
- package/src/init-agents.ts +301 -38
- package/src/internal/code-unit-order.ts +5 -0
- package/src/internal/script-kind.ts +11 -0
- package/src/internal/source-facts.ts +96 -22
- package/src/internal/typescript-import.ts +663 -68
- package/src/introspect.ts +93 -82
- package/src/learn.ts +3 -3
- package/src/levels-tool.ts +47 -2
- package/src/load.ts +144 -32
- package/src/manifest-tool.ts +3 -2
- package/src/mcp-app-tool.ts +2 -0
- package/src/mcp-context.ts +5 -7
- package/src/mcp-delegate.ts +162 -0
- package/src/mcp-docs-tools.ts +23 -5
- package/src/mcp-exec.ts +301 -161
- package/src/mcp-http.ts +22 -5
- package/src/mcp-io.ts +130 -0
- package/src/mcp-isolate.ts +287 -0
- package/src/mcp-path.ts +50 -0
- package/src/mcp-project-child.ts +182 -0
- package/src/mcp-reflect.ts +3 -2
- package/src/mcp-render.ts +72 -42
- package/src/mcp-root.ts +130 -24
- package/src/mcp-run.ts +190 -56
- package/src/mcp-ws.ts +4 -1
- package/src/mcp.ts +193 -91
- package/src/migrate-layout.ts +1121 -0
- package/src/openapi-tool.ts +7 -3
- package/src/openapi-types.ts +315 -13
- package/src/pipeline-report.ts +22 -18
- package/src/port.ts +53 -30
- package/src/project-facts.ts +42 -8
- package/src/recipes/4.0.0.ts +35 -0
- package/src/recipes/index.ts +2 -0
- package/src/release-verification.ts +74 -5
- package/src/review.ts +988 -0
- package/src/route-types.ts +133 -0
- package/src/rules/codes.ts +9 -0
- package/src/rules/data-guard.ts +230 -0
- package/src/rules/index.ts +18 -5
- package/src/rules/islands.ts +12 -4
- package/src/rules/legacy.ts +947 -35
- package/src/rules/nano.ts +18 -6
- package/src/rules/pages.ts +99 -0
- package/src/rules/routes.ts +181 -2
- package/src/rules/secrets.ts +174 -0
- package/src/rules/security.ts +198 -70
- package/src/rules/typescript.ts +77 -0
- package/src/rules/zones.ts +195 -0
- package/src/scaffold.ts +175 -98
- package/src/sdk.ts +1215 -158
- package/src/search-terms.ts +24 -7
- package/src/smoke.ts +607 -0
- package/src/sync-manifest.ts +2 -1
- package/src/sync-routes.ts +4 -3
- package/src/test-tool.ts +32 -11
- package/src/types-search.ts +11 -4
- package/src/upgrade.ts +205 -25
- package/src/verification-plan.ts +27 -0
- package/src/verification.ts +50 -12
- package/src/web-app-options.ts +57 -0
- package/src/work-graph.ts +40 -16
package/src/mcp.ts
CHANGED
|
@@ -20,19 +20,32 @@
|
|
|
20
20
|
* - `nifra_levels` - the cumulative verification ladder (L0 contract → L4 invariants): what the
|
|
21
21
|
* project actually proves, and why each level it misses does not hold.
|
|
22
22
|
* - `nifra_doctor` - package.json dependency drift detector, with safe local-version auto-fix.
|
|
23
|
-
* - `
|
|
23
|
+
* - `nifra_errors` - the running dev server's errors (SSR, loader, API, build, browser, hydration,
|
|
24
|
+
* crash), each a structured diagnostic tagged with its request; `since` cursors, stale flags.
|
|
25
|
+
* - `nifra_logs` - the dev server's console output, server and browser, tagged by request.
|
|
26
|
+
* - `nifra_db_schema` / `nifra_db_query` / `nifra_db_role` - the development database declared as
|
|
27
|
+
* `devDatabase` in nifra.config.ts: its schema, one read-only query (or plan) per call in a fresh
|
|
28
|
+
* subprocess killed at the deadline, and the SQL for a read-only role. Separate tools, so a client
|
|
29
|
+
* can allow reading the schema without allowing queries.
|
|
30
|
+
* - `nifra_explain` - resolve an error (pasted, or the dev server's latest) into a structured
|
|
24
31
|
* diagnostic: stable code, a codeframe in the user's source, and the recognised cause + fix.
|
|
25
32
|
* - `nifra_inspect` - read the running dev server's recent request traces (method/path/status/
|
|
26
|
-
* duration/ISR)
|
|
33
|
+
* duration/ISR/errors): what your requests ACTUALLY did, not a guess.
|
|
27
34
|
*
|
|
28
35
|
* Wire it into a client (e.g. Claude Desktop / Cursor) as: command `nifra`, args `["mcp"]` (or
|
|
29
36
|
* `["mcp", "<dir>"]` to pin the project directory explicitly). The server does NOT silently trust its
|
|
30
37
|
* spawn directory: the root is resolved via `./mcp-root.ts` (marker walk-up, the client's MCP `roots`,
|
|
31
38
|
* fail-closed tools when no nifra project is found) and announced in `initialize` + on every project
|
|
32
|
-
* tool result.
|
|
39
|
+
* tool result. A project that installs its own `@nifrajs/cli` at a different version gets the session
|
|
40
|
+
* handed to that CLI; when that is impossible the version-sensitive tools refuse rather than answer
|
|
41
|
+
* for the wrong release (see `./mcp-delegate.ts`). The protocol is hand-rolled (newline-delimited JSON-RPC 2.0 over stdio), including
|
|
33
42
|
* standard MCP progress notifications and request cancellation - no SDK dependency, the same
|
|
34
43
|
* minimal-surface choice as the rest of nifra. The pure dispatch lives in `./mcp-protocol.ts`; this
|
|
35
44
|
* module is the I/O shell (stdin loop, tool wiring, the run subprocess).
|
|
45
|
+
*
|
|
46
|
+
* This process runs no project code and holds none of the project's `.env`: the config, the backend
|
|
47
|
+
* and everything they import run in a fresh subprocess per call (`./mcp-isolate.ts`), and a session
|
|
48
|
+
* whose process Bun started with `.env` values moves to a copy that never loads them (`./cli.ts`).
|
|
36
49
|
*/
|
|
37
50
|
|
|
38
51
|
import { stat } from "node:fs/promises"
|
|
@@ -40,11 +53,14 @@ import { resolve } from "node:path"
|
|
|
40
53
|
import { loadDocsCorpus } from "./docs-search.ts"
|
|
41
54
|
import { loadExamplesCorpus } from "./examples.ts"
|
|
42
55
|
import type { LoadedApp } from "./load.ts"
|
|
43
|
-
import {
|
|
56
|
+
import { loadMonorepoApps } from "./load.ts"
|
|
57
|
+
import { delegateToProjectCli, refuseVersionSensitive } from "./mcp-delegate.ts"
|
|
44
58
|
import { docsTools } from "./mcp-docs-tools.ts"
|
|
45
59
|
import {
|
|
46
60
|
createMcpProtocolState,
|
|
47
61
|
handleRpc,
|
|
62
|
+
isJsonRpcRequest,
|
|
63
|
+
isJsonRpcResponse,
|
|
48
64
|
type JsonRpcNotification,
|
|
49
65
|
type JsonRpcRequest,
|
|
50
66
|
type JsonRpcResponse,
|
|
@@ -54,11 +70,6 @@ import {
|
|
|
54
70
|
type McpTool,
|
|
55
71
|
rpcError,
|
|
56
72
|
} from "./mcp-protocol.ts"
|
|
57
|
-
import {
|
|
58
|
-
extractBackendPrompts,
|
|
59
|
-
extractBackendResources,
|
|
60
|
-
extractBackendTools,
|
|
61
|
-
} from "./mcp-reflect.ts"
|
|
62
73
|
import { loadTypesCorpus } from "./types-search.ts"
|
|
63
74
|
|
|
64
75
|
export {
|
|
@@ -80,21 +91,31 @@ import {
|
|
|
80
91
|
type ToolingDrift,
|
|
81
92
|
} from "./mcp-root.ts"
|
|
82
93
|
|
|
83
|
-
export type { BoundedOutput } from "./mcp-io.ts"
|
|
94
|
+
export type { BoundedLine, BoundedOutput } from "./mcp-io.ts"
|
|
84
95
|
export {
|
|
96
|
+
CHILD_INPUT_MAX_BYTES,
|
|
85
97
|
CHILD_OUTPUT_MAX_BYTES,
|
|
86
98
|
CHILD_TIMEOUT_MS,
|
|
87
99
|
LOCAL_TOOL_FETCH_TIMEOUT_MS,
|
|
88
100
|
LOCAL_TOOL_MAX_RESPONSE_BYTES,
|
|
89
101
|
notNifraResponse,
|
|
102
|
+
readBoundedLines,
|
|
90
103
|
readBoundedResponse,
|
|
91
104
|
readBoundedStream,
|
|
105
|
+
serializeBoundedJson,
|
|
92
106
|
timeoutMessage,
|
|
93
107
|
validateLocalPort,
|
|
94
108
|
} from "./mcp-io.ts"
|
|
95
109
|
export { clientSupportsRoots, guardTools } from "./mcp-root.ts"
|
|
96
110
|
|
|
97
|
-
import {
|
|
111
|
+
import { projectTools } from "./mcp-exec.ts"
|
|
112
|
+
import {
|
|
113
|
+
createAppSurface,
|
|
114
|
+
detectMonorepoIsolated,
|
|
115
|
+
type IsolatedSurface,
|
|
116
|
+
isolateResources,
|
|
117
|
+
isolateTools,
|
|
118
|
+
} from "./mcp-isolate.ts"
|
|
98
119
|
|
|
99
120
|
export type { CachedAppLoaderOptions } from "./mcp-exec.ts"
|
|
100
121
|
export {
|
|
@@ -110,7 +131,7 @@ export {
|
|
|
110
131
|
wsHandler,
|
|
111
132
|
} from "./mcp-exec.ts"
|
|
112
133
|
|
|
113
|
-
import { namespaceForApp,
|
|
134
|
+
import { namespaceForApp, projectPrompts, projectResources } from "./mcp-context.ts"
|
|
114
135
|
|
|
115
136
|
export type { CommandMcpToolOptions } from "./mcp-context.ts"
|
|
116
137
|
export {
|
|
@@ -122,76 +143,82 @@ export {
|
|
|
122
143
|
resolveProjectDir,
|
|
123
144
|
} from "./mcp-context.ts"
|
|
124
145
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
} catch {
|
|
136
|
-
// Not loadable here (no web config, or the config itself throws). The server still serves.
|
|
137
|
-
}
|
|
138
|
-
return { resources, prompts }
|
|
139
|
-
}
|
|
146
|
+
/**
|
|
147
|
+
* The loader the server builds its tool and resource lists with. It never loads: every handler that
|
|
148
|
+
* reaches the app runs in a project subprocess (`./mcp-isolate.ts`), so one that tried here fails.
|
|
149
|
+
*/
|
|
150
|
+
const noAppInServer = (): Promise<LoadedApp> =>
|
|
151
|
+
Promise.reject(
|
|
152
|
+
new Error("[nifra] the MCP server does not load the app; project code runs in a subprocess"),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
const isolatedToolsByDir = new Map<string, McpTool[]>()
|
|
140
156
|
|
|
141
157
|
/**
|
|
142
|
-
* The
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* so an unloadable project failed `initialize` itself with a `-32603` and the session never opened.
|
|
146
|
-
* An app's own tools are an extension of the built-in set, so their absence must not withdraw the
|
|
147
|
-
* built-ins - a backend-only project still gets docs, examples, types, check, doctor, levels and test.
|
|
158
|
+
* The project's tools, each forwarded to a project subprocess unless it runs no project code. Built
|
|
159
|
+
* once per directory: `nifra_run`/`nifra_render` hold their warm worker in the tool, so a list rebuilt
|
|
160
|
+
* per message started a fresh worker for every `warm: true` call.
|
|
148
161
|
*/
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
162
|
+
function isolatedProjectTools(cwd: string): McpTool[] {
|
|
163
|
+
let tools = isolatedToolsByDir.get(cwd)
|
|
164
|
+
if (tools === undefined) {
|
|
165
|
+
tools = isolateTools(cwd, projectTools(cwd, noAppInServer))
|
|
166
|
+
isolatedToolsByDir.set(cwd, tools)
|
|
167
|
+
}
|
|
168
|
+
return tools
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** The project's resources and prompts, with the ones the app declares. */
|
|
172
|
+
function appFeatures(cwd: string, surface: IsolatedSurface): McpServerFeatures {
|
|
173
|
+
return {
|
|
174
|
+
resources: [
|
|
175
|
+
...isolateResources(cwd, projectResources(cwd, noAppInServer)),
|
|
176
|
+
...surface.resources,
|
|
177
|
+
],
|
|
178
|
+
prompts: [...projectPrompts(), ...surface.prompts],
|
|
154
179
|
}
|
|
155
180
|
}
|
|
156
181
|
|
|
157
182
|
const ROOTS_REQUEST_ID = "nifra:roots/list"
|
|
183
|
+
const MAX_STDIO_MESSAGE_BYTES = 8 * 1024 * 1024
|
|
184
|
+
const STDIO_ENCODER = new TextEncoder()
|
|
185
|
+
|
|
186
|
+
interface ProjectApp {
|
|
187
|
+
readonly name: string
|
|
188
|
+
readonly cwd: string
|
|
189
|
+
readonly surface: () => Promise<IsolatedSurface>
|
|
190
|
+
}
|
|
158
191
|
|
|
159
192
|
/** Everything derived from the project root - rebuilt wholesale when the root changes (adoption of a
|
|
160
193
|
* client workspace root), so no per-tool state can keep pointing at the old directory. */
|
|
161
194
|
interface ProjectContext {
|
|
162
|
-
|
|
195
|
+
/** A monorepo root's apps; `undefined` when the root is one app. */
|
|
196
|
+
readonly apps: readonly ProjectApp[] | undefined
|
|
163
197
|
readonly features: McpServerFeatures
|
|
164
|
-
readonly
|
|
198
|
+
readonly surface: () => Promise<IsolatedSurface>
|
|
165
199
|
}
|
|
166
200
|
|
|
167
201
|
async function createProjectContext(root: string): Promise<ProjectContext> {
|
|
168
|
-
const monorepo = await
|
|
202
|
+
const monorepo = await detectMonorepoIsolated(root)
|
|
169
203
|
if (monorepo) {
|
|
170
|
-
const
|
|
204
|
+
const apps = (await loadMonorepoApps(root, monorepo)).map(
|
|
205
|
+
({ name, cwd }): ProjectApp => ({ name, cwd, surface: createAppSurface(cwd) }),
|
|
206
|
+
)
|
|
171
207
|
const allResources: McpResource[] = []
|
|
172
208
|
const allPrompts: McpPrompt[] = []
|
|
173
|
-
for (const { name, cwd: appCwd } of
|
|
174
|
-
const
|
|
175
|
-
const ns = namespaceForApp(
|
|
176
|
-
name,
|
|
177
|
-
[],
|
|
178
|
-
await backendFeatures(loader, projectFeatures(appCwd, loader)),
|
|
179
|
-
)
|
|
209
|
+
for (const { name, cwd: appCwd, surface } of apps) {
|
|
210
|
+
const ns = namespaceForApp(name, [], appFeatures(appCwd, await surface()))
|
|
180
211
|
allResources.push(...(ns.features.resources ?? []))
|
|
181
212
|
allPrompts.push(...(ns.features.prompts ?? []))
|
|
182
213
|
}
|
|
183
214
|
return {
|
|
184
|
-
|
|
215
|
+
apps,
|
|
185
216
|
features: { resources: allResources, prompts: allPrompts },
|
|
186
|
-
|
|
217
|
+
surface: createAppSurface(root),
|
|
187
218
|
}
|
|
188
219
|
}
|
|
189
|
-
const
|
|
190
|
-
return {
|
|
191
|
-
monorepo,
|
|
192
|
-
features: await backendFeatures(loadAppCached, projectFeatures(root, loadAppCached)),
|
|
193
|
-
loadAppCached,
|
|
194
|
-
}
|
|
220
|
+
const surface = createAppSurface(root)
|
|
221
|
+
return { apps: undefined, features: appFeatures(root, await surface()), surface }
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
/**
|
|
@@ -210,22 +237,49 @@ export async function runMcpServer(
|
|
|
210
237
|
throw new Error(`nifra mcp: directory not found: ${requested}`)
|
|
211
238
|
}
|
|
212
239
|
let rootState = await resolveRootState(requested, explicitDir !== undefined)
|
|
240
|
+
if (rootState.isProject) {
|
|
241
|
+
// Before anything reads stdin: a hand-off child inherits the descriptor and owns the session.
|
|
242
|
+
const exitCode = await delegateToProjectCli({
|
|
243
|
+
cwd,
|
|
244
|
+
root: rootState.root,
|
|
245
|
+
version,
|
|
246
|
+
args: explicitDir === undefined ? [] : [requested],
|
|
247
|
+
})
|
|
248
|
+
if (exitCode !== undefined) {
|
|
249
|
+
process.exitCode = exitCode
|
|
250
|
+
return
|
|
251
|
+
}
|
|
252
|
+
}
|
|
213
253
|
let ctx = await createProjectContext(rootState.root)
|
|
214
254
|
let drift: ToolingDrift | undefined = await detectToolingDrift(rootState.root, version)
|
|
215
255
|
const serverInfo = { name: "nifra", version }
|
|
216
256
|
const state = createMcpProtocolState()
|
|
217
257
|
const send = (message: JsonRpcResponse | JsonRpcNotification): void => {
|
|
218
|
-
|
|
258
|
+
let encoded: string
|
|
259
|
+
try {
|
|
260
|
+
encoded = JSON.stringify(message)
|
|
261
|
+
} catch {
|
|
262
|
+
encoded = JSON.stringify(
|
|
263
|
+
rpcError(responseIdOf(message), -32603, "MCP response is not serializable"),
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
if (STDIO_ENCODER.encode(encoded).byteLength > MAX_STDIO_MESSAGE_BYTES)
|
|
267
|
+
encoded = JSON.stringify(
|
|
268
|
+
rpcError(responseIdOf(message), -32603, "MCP response exceeded the size limit"),
|
|
269
|
+
)
|
|
270
|
+
process.stdout.write(`${encoded}\n`)
|
|
219
271
|
}
|
|
220
272
|
let rootsSupported = false
|
|
273
|
+
let rootsRequestPending = false
|
|
221
274
|
const requestRoots = (): void => {
|
|
222
|
-
if (!rootsSupported) return
|
|
275
|
+
if (!rootsSupported || rootsRequestPending) return
|
|
276
|
+
rootsRequestPending = true
|
|
223
277
|
process.stdout.write(
|
|
224
278
|
`${JSON.stringify({ jsonrpc: "2.0", id: ROOTS_REQUEST_ID, method: "roots/list" })}\n`,
|
|
225
279
|
)
|
|
226
280
|
}
|
|
227
|
-
const onRootsAnswer = async (message:
|
|
228
|
-
const result =
|
|
281
|
+
const onRootsAnswer = async (message: JsonRpcResponse): Promise<void> => {
|
|
282
|
+
const result = "result" in message ? message.result : undefined
|
|
229
283
|
// An error response (or a malformed one) carries no roots: keep the current state - the absence
|
|
230
284
|
// of workspace data is not a mismatch, and never a reason to move the root.
|
|
231
285
|
if (result === undefined) return
|
|
@@ -241,35 +295,42 @@ export async function runMcpServer(
|
|
|
241
295
|
// must see the adopted root, even though dispatches themselves run concurrently.
|
|
242
296
|
let rootsUpdate: Promise<void> = Promise.resolve()
|
|
243
297
|
|
|
298
|
+
const dispatchRootsAnswer = (message: JsonRpcResponse): void => {
|
|
299
|
+
// The roots answer is a response to a request initiated by this server, not a client RPC request.
|
|
300
|
+
// Serialize adoption with normal dispatch so a following request cannot observe a half-updated root.
|
|
301
|
+
if (!rootsRequestPending) return
|
|
302
|
+
rootsRequestPending = false
|
|
303
|
+
rootsUpdate = onRootsAnswer(message).catch(() => {})
|
|
304
|
+
}
|
|
305
|
+
|
|
244
306
|
const dispatch = async (message: JsonRpcRequest): Promise<void> => {
|
|
245
|
-
// The client's answer to OUR `roots/list` request - a response (id, no method), which the pure
|
|
246
|
-
// dispatch would reject as an unknown method. Intercept it before handleRpc ever sees it.
|
|
247
|
-
if (message.method === undefined && message.id === ROOTS_REQUEST_ID) {
|
|
248
|
-
// Swallow failures: a bad answer must not poison the chain and 500 every later dispatch.
|
|
249
|
-
rootsUpdate = onRootsAnswer(message).catch(() => {})
|
|
250
|
-
await rootsUpdate
|
|
251
|
-
return
|
|
252
|
-
}
|
|
253
307
|
await rootsUpdate
|
|
254
308
|
if (message.method === "initialize") {
|
|
255
309
|
rootsSupported = clientSupportsRoots(message.params)
|
|
256
310
|
}
|
|
257
311
|
|
|
258
312
|
let activeTools: McpTool[]
|
|
259
|
-
if (ctx.
|
|
260
|
-
const appEntries = await loadMonorepoApps(rootState.root, ctx.monorepo)
|
|
313
|
+
if (ctx.apps) {
|
|
261
314
|
const allTools: McpTool[] = []
|
|
262
|
-
for (const { name, cwd: appCwd } of
|
|
263
|
-
const
|
|
264
|
-
|
|
315
|
+
for (const { name, cwd: appCwd, surface } of ctx.apps) {
|
|
316
|
+
const tools = [
|
|
317
|
+
...refuseVersionSensitive(isolatedProjectTools(appCwd), drift),
|
|
318
|
+
...(await surface()).tools,
|
|
319
|
+
]
|
|
265
320
|
const ns = namespaceForApp(name, tools, { resources: [], prompts: [] })
|
|
266
321
|
allTools.push(...ns.tools)
|
|
267
322
|
}
|
|
268
|
-
activeTools = [
|
|
323
|
+
activeTools = [
|
|
324
|
+
...refuseVersionSensitive(
|
|
325
|
+
docsTools(loadDocsCorpus, loadExamplesCorpus, loadTypesCorpus),
|
|
326
|
+
drift,
|
|
327
|
+
),
|
|
328
|
+
...allTools,
|
|
329
|
+
]
|
|
269
330
|
} else {
|
|
270
331
|
activeTools = [
|
|
271
|
-
...
|
|
272
|
-
...(await
|
|
332
|
+
...refuseVersionSensitive(isolatedProjectTools(rootState.root), drift),
|
|
333
|
+
...(await ctx.surface()).tools,
|
|
273
334
|
]
|
|
274
335
|
}
|
|
275
336
|
const verdict = await rootVerdict(rootState)
|
|
@@ -287,6 +348,8 @@ export async function runMcpServer(
|
|
|
287
348
|
const response = await handleRpc(message, activeTools, serverInfo, features, {
|
|
288
349
|
state,
|
|
289
350
|
sendNotification: send,
|
|
351
|
+
// The stdio caller is the developer's own agent: a failure it cannot read it cannot fix.
|
|
352
|
+
exposeToolErrors: true,
|
|
290
353
|
})
|
|
291
354
|
if (response) send(response)
|
|
292
355
|
// Ask for the client's workspace roots once the handshake completes, and again whenever the
|
|
@@ -301,25 +364,64 @@ export async function runMcpServer(
|
|
|
301
364
|
|
|
302
365
|
const decoder = new TextDecoder()
|
|
303
366
|
let buffer = ""
|
|
367
|
+
let bufferBytes = 0
|
|
368
|
+
let discardingOversized = false
|
|
369
|
+
const processLine = (rawLine: string): void => {
|
|
370
|
+
const line = rawLine.trim()
|
|
371
|
+
if (line === "") return
|
|
372
|
+
if (STDIO_ENCODER.encode(rawLine).byteLength > MAX_STDIO_MESSAGE_BYTES) {
|
|
373
|
+
send(rpcError(null, -32600, "MCP message exceeded the size limit"))
|
|
374
|
+
return
|
|
375
|
+
}
|
|
376
|
+
let parsed: unknown
|
|
377
|
+
try {
|
|
378
|
+
parsed = JSON.parse(line) as unknown
|
|
379
|
+
} catch {
|
|
380
|
+
send(rpcError(null, -32700, "parse error"))
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
if (isJsonRpcRequest(parsed)) {
|
|
384
|
+
void dispatch(parsed).catch((err) => {
|
|
385
|
+
const msg = err instanceof Error ? err.message : String(err)
|
|
386
|
+
send(rpcError(parsed.id ?? null, -32603, msg))
|
|
387
|
+
})
|
|
388
|
+
} else if (isJsonRpcResponse(parsed) && parsed.id === ROOTS_REQUEST_ID) {
|
|
389
|
+
dispatchRootsAnswer(parsed)
|
|
390
|
+
} else {
|
|
391
|
+
// Do not let null, arrays, scalars, responses for unknown ids, or malformed envelopes reach the
|
|
392
|
+
// dispatch shell. In particular, reading `.id` from a hostile `null` must never crash the loop.
|
|
393
|
+
send(rpcError(null, -32600, "Invalid Request"))
|
|
394
|
+
}
|
|
395
|
+
}
|
|
304
396
|
for await (const chunk of Bun.stdin.stream()) {
|
|
305
|
-
|
|
397
|
+
let text = decoder.decode(chunk as Uint8Array, { stream: true })
|
|
398
|
+
if (discardingOversized) {
|
|
399
|
+
const newline = text.indexOf("\n")
|
|
400
|
+
if (newline === -1) continue
|
|
401
|
+
text = text.slice(newline + 1)
|
|
402
|
+
discardingOversized = false
|
|
403
|
+
}
|
|
404
|
+
buffer += text
|
|
405
|
+
bufferBytes += STDIO_ENCODER.encode(text).byteLength
|
|
306
406
|
let nl = buffer.indexOf("\n")
|
|
307
407
|
while (nl !== -1) {
|
|
308
|
-
const line = buffer.slice(0, nl)
|
|
408
|
+
const line = buffer.slice(0, nl)
|
|
309
409
|
buffer = buffer.slice(nl + 1)
|
|
410
|
+
bufferBytes = Math.max(0, bufferBytes - STDIO_ENCODER.encode(line).byteLength - 1)
|
|
310
411
|
nl = buffer.indexOf("\n")
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
void dispatch(message).catch((err) => {
|
|
320
|
-
const msg = err instanceof Error ? err.message : String(err)
|
|
321
|
-
send(rpcError(message.id ?? null, -32603, msg))
|
|
322
|
-
})
|
|
412
|
+
processLine(line)
|
|
413
|
+
}
|
|
414
|
+
if (bufferBytes > MAX_STDIO_MESSAGE_BYTES) {
|
|
415
|
+
buffer = ""
|
|
416
|
+
bufferBytes = 0
|
|
417
|
+
discardingOversized = true
|
|
418
|
+
send(rpcError(null, -32600, "MCP message exceeded the size limit"))
|
|
323
419
|
}
|
|
324
420
|
}
|
|
325
421
|
}
|
|
422
|
+
|
|
423
|
+
function responseIdOf(message: JsonRpcResponse | JsonRpcNotification): string | number | null {
|
|
424
|
+
if (!("id" in message)) return null
|
|
425
|
+
const id = message.id
|
|
426
|
+
return id === null || typeof id === "string" || typeof id === "number" ? id : null
|
|
427
|
+
}
|