@webjsdev/mcp 0.1.5 → 0.1.6

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 CHANGED
@@ -34,9 +34,7 @@ subcommand) delegates to this same server, so both routes run identical code.
34
34
  MCP `resources` (the `agent-docs/*` corpus + `AGENTS.md` as `webjs-docs://*`),
35
35
  and `prompts` (the recipes as guided workflows).
36
36
  - **`source` tool**: reads the framework's own no-build source from
37
- `node_modules/@webjsdev/*/src` (read-only, traversal-guarded). Under Bun
38
- zero-install (no `node_modules`) it falls back to Bun's global cache, where the
39
- packages live, so it works without an install.
37
+ `node_modules/@webjsdev/*/src` (read-only, traversal-guarded).
40
38
 
41
39
  The docs corpus is bundled into the package at `prepack`, so `npx @webjsdev/mcp`
42
40
  is self-contained; in the monorepo it falls back to the live repo-root docs.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "type": "module",
5
5
  "description": "The webjs Model Context Protocol server: live app introspection (routes / actions / components / check) plus the framework knowledge layer (docs, recipes, source) for AI coding agents",
6
6
  "bin": {
@@ -15,7 +15,7 @@ reference there.
15
15
  | `agent-docs/styling.md` | Tailwind helpers + vanilla-CSS opt-out conventions |
16
16
  | `agent-docs/built-ins.md` | Auth, sessions, env vars, caching (cache(), HTTP cache, asset-hash, conditional GET), rate-limit, broadcast, file storage |
17
17
  | `agent-docs/configuration.md` | The `package.json` `"webjs"` block (security headers, CSP, redirects, trailing-slash, basePath, client-router opt-out, ingress caps, dev/start task orchestration) + observability |
18
- | `agent-docs/runtime.md` | Node vs Bun (commands, the zero-install Bun bootstrap, the listener/stripper/SQLite/WebSocket differences) + the future-Deno seam |
18
+ | `agent-docs/runtime.md` | Node vs Bun (commands, install, the listener/stripper/SQLite/WebSocket differences) + the future-Deno seam |
19
19
  | `agent-docs/advanced.md` | Suspense streaming, performance, bundling, client router (prefetch, frames, view transitions, stream actions), WebSockets |
20
20
  | `agent-docs/typescript.md` | TS at runtime + full-stack type safety |
21
21
  | `agent-docs/service-worker.md` | The opt-in progressive-enhancement service worker (`public/sw.js`) |
@@ -90,7 +90,7 @@ See `agent-docs/framework-dev.md` for monorepo commands, workspace layout, per-f
90
90
 
91
91
  An **AI-first, web-components-first** framework inspired by NextJs, Lit, and Rails. The component runtime API matches lit (reactive properties, the lit lifecycle hooks, ReactiveControllers, the `lit-html` directive set, `html` / `css` templates) so lit training data transfers directly, but webjs ships its own no-build implementation under `packages/core/src/`. Reactive properties are the one deliberate divergence (invariant 10): instead of lit's `@property()` decorator or a `static properties` block, webjs declares them through the declare-free base-class factory `extends WebComponent({ count: Number })`.
92
92
 
93
- - **No build step.** Source files are served as native ES modules. JSDoc `.js` is default; `.ts` / `.mts` is stripped through a pluggable stripper (#508): Node 24+'s built-in `module.stripTypeScriptTypes`, or `amaro` on Bun (byte-identical, position-preserving) (invariant 10 + `agent-docs/typescript.md`). **Runs on Node 24+ or Bun** (run a Bun app with `bun --bun run dev` / `start`); the early `assertNodeVersion()` preflight enforces the Node floor and admits Bun. On Bun, `startServer` selects a native `Bun.serve` listener shell instead of the node:http one (skipping the compat bridge for ~1.9x more req/s on the listening path, at near-complete feature parity, the one node-only gap being 103 Early Hints since `Bun.serve` has no informational-response API), via a runtime-neutral seam that also sets up future `Deno.serve` / embedded adapters. Edge runtimes (no filesystem) are a separate, later target. See `agent-docs/runtime.md` for the Node vs Bun command + difference reference (and the zero-install Bun bootstrap).
93
+ - **No build step.** Source files are served as native ES modules. JSDoc `.js` is default; `.ts` / `.mts` is stripped through a pluggable stripper (#508): Node 24+'s built-in `module.stripTypeScriptTypes`, or `amaro` on Bun (byte-identical, position-preserving) (invariant 10 + `agent-docs/typescript.md`). **Runs on Node 24+ or Bun** (run a Bun app with `bun --bun run dev` / `start`); the early `assertNodeVersion()` preflight enforces the Node floor and admits Bun. On Bun, `startServer` selects a native `Bun.serve` listener shell instead of the node:http one (skipping the compat bridge for ~1.9x more req/s on the listening path, at near-complete feature parity, the one node-only gap being 103 Early Hints since `Bun.serve` has no informational-response API), via a runtime-neutral seam that also sets up future `Deno.serve` / embedded adapters. Edge runtimes (no filesystem) are a separate, later target. See `agent-docs/runtime.md` for the Node vs Bun command + difference reference.
94
94
  - **SSR + CSR by default.** Pages are server-rendered HTML; components render light DOM by default, shadow DOM opt-in via `static shadow = true` with DSD SSR.
95
95
  - **Progressive enhancement is the default architecture.** Pages and components are SSR'd; with JS off, content reads, `<a>` navigates, `<form>` server actions submit, display-only elements render. JS is opt-in *per interactive behaviour* (`@click`, a reactive property assignment, a signal mutation). Never write a first paint that depends on hydration; never use `fetch` + JS where a `<form>` + server action would do.
96
96
  - **Display-only components are elided from the browser.** A component with no interactivity signal renders identical HTML with or without its JS, so the framework strips its import (and any vendor reachable only through it, importmap entry included) from the served source. Automatic, conservative, verified differentially. Disable with `"webjs": { "elide": false }` or `WEBJS_ELIDE=0`. See `agent-docs/components.md`.
@@ -357,7 +357,7 @@ The advanced client-router surface is in `agent-docs/advanced.md`: **link prefet
357
357
 
358
358
  ## Scaffolding
359
359
 
360
- Three scaffolds exist (do not invent template names): `webjs create <name>` (full-stack: layout, page, components, modules, Drizzle+SQLite), `webjs create <name> --template api` (backend-only routes + modules + Drizzle, no SSR), `webjs create <name> --template saas` (auth + login/signup + protected dashboard + User model). The `--db sqlite|postgres` flag (default sqlite) picks the dialect; the schema/queries/actions are identical across dialects (see #563). The `--runtime node|bun` flag (default node, #541) is ORTHOGONAL to the template and re-flavors any of the three for Bun (the dev/start/db scripts run through a generated `webjs-bun.mjs` bootstrap under `bun --bun`, so the SERVER runs on Bun AND Bun auto-install resolves deps on demand at the package.json/`bun.lock` versions (an `onLoad` specifier rewrite pins them so zero-install fetches the declared version, not latest, #685), so a Bun app serves with NO `bun install`, install optional for editor types / offline, #675; `bun.lock`, a pure `oven/bun:1` Dockerfile (#595; safe since cli@0.10.20's npx-free `webjs db migrate` (#570) needs no Node in the image) + bun-install CI, bun-command agent docs; the test/check/typecheck tooling stays plain `webjs` on Node and still expects an install); `bun create webjs <name>` auto-detects it. Pick from the request: default for any product with UI (todo, blog, dashboard, marketplace, social, e-commerce), `api` for an HTTP/JSON API with no UI, `saas` for accounts/login/signup; default to full-stack when ambiguous.
360
+ Three scaffolds exist (do not invent template names): `webjs create <name>` (full-stack: layout, page, components, modules, Drizzle+SQLite), `webjs create <name> --template api` (backend-only routes + modules + Drizzle, no SSR), `webjs create <name> --template saas` (auth + login/signup + protected dashboard + User model). The `--db sqlite|postgres` flag (default sqlite) picks the dialect; the schema/queries/actions are identical across dialects (see #563). The `--runtime node|bun` flag (default node, #541) is ORTHOGONAL to the template and re-flavors any of the three for Bun (`bun --bun` dev/start scripts so the SERVER runs on Bun, `bun.lock`, a pure `oven/bun:1` Dockerfile (#595; safe since cli@0.10.20's npx-free `webjs db migrate` (#570) needs no Node in the image) + bun-install CI, bun-command agent docs; the test/db/check tooling stays on Node); `bun create webjs <name>` auto-detects it. Pick from the request: default for any product with UI (todo, blog, dashboard, marketplace, social, e-commerce), `api` for an HTTP/JSON API with no UI, `saas` for accounts/login/signup; default to full-stack when ambiguous.
361
361
 
362
362
  Rules: **always scaffold via `webjs create`** (never hand-roll). **Default to a real database (Drizzle + SQLite); NEVER use JSON files, in-memory arrays, or localStorage for persistence.** Update `db/schema.server.ts` to real models FIRST, then `webjs db generate` + `webjs db migrate`, then build pages/actions/queries. **Treat the scaffold as REFERENCE, not the final product:** replace the example page / `User` model / components and adapt `app/layout.ts` (brand, nav, content width; the default `<main class="max-w-[760px]">` reading column needs widening for a full-bleed app). ENFORCED: examples carry a `webjs-scaffold-placeholder` comment and `no-scaffold-placeholder` fails until the content is replaced and the marker deleted. Docs at https://docs.webjs.com.
363
363
 
@@ -11,7 +11,7 @@ user-facing reference is the docs-site page `/docs/runtime`; keep the two in syn
11
11
 
12
12
  | Area | Node 24+ | Bun |
13
13
  |---|---|---|
14
- | Install | `npm install` (required) | optional (zero-install via Bun auto-install) |
14
+ | Install | `npm install` (required) | `bun install` (required, like Node) |
15
15
  | Run | `npm run dev` / `npm run start` | `bun run dev` / `bun run start` |
16
16
  | Listener | `node:http` shell | native `Bun.serve` (about 1.9x req/s on the listening path) |
17
17
  | TS strip | built-in `module.stripTypeScriptTypes` | `amaro` (byte-identical, position-preserving) |
@@ -29,52 +29,26 @@ correctness (the modulepreload hints still ship in the document head).
29
29
  `webjs create <name>` defaults to Node. `webjs create <name> --runtime bun` (or
30
30
  `bun create webjs <name>`, auto-detected from the invoking package manager,
31
31
  #541) emits a Bun-flavored app: `bun.lock`, a pure `oven/bun:1` Dockerfile +
32
- bun-install CI, bun-command agent docs, and the zero-install bootstrap below.
33
- `--runtime` is orthogonal to `--template`.
32
+ bun-install CI, and bun-command agent docs. `--runtime` is orthogonal to
33
+ `--template`.
34
34
 
35
- ## Bun zero-install (#675)
35
+ ## Bun
36
36
 
37
- A Bun app's `dev` / `start` / `db` scripts run through a generated app-local
38
- `webjs-bun.mjs` bootstrap under `bun --bun`:
37
+ A Bun app installs with `bun install` (like Node), then its `dev` / `start` /
38
+ `db` scripts force `bun --bun` so the server runs on Bun:
39
39
 
40
- ```js
41
- // webjs-bun.mjs
42
- await import('@webjsdev/cli/bin/webjs.js');
40
+ ```sh
41
+ bun install
42
+ bun run dev # or: bun run start
43
43
  ```
44
44
 
45
- `bun --bun` overrides the `webjs` bin's Node shebang so the server runs on Bun;
46
- importing the CLI by bare specifier lets Bun auto-install resolve `@webjsdev/*`
47
- and the app's deps on demand, so a fresh app serves with **no `bun install`**.
48
- The CLI's `start` is in-process and `dev` re-execs via `process.execPath` (which
49
- is `bun` here), so the server stays on Bun once the CLI does. `bunx
50
- @webjsdev/cli` is deliberately NOT used (it runs on Node via the shebang AND
51
- eager-installs the whole tree). The `start.before` migrate also routes through
52
- the bootstrap, so the boot-time `webjs db migrate` needs no `webjs` bin in
53
- `node_modules`. `bun install` stays optional: run it for editor type
54
- intelligence (no `node_modules` means no local type files) or a pinned offline
55
- install. The Node-targeted tooling scripts (`test` / `check` / `typecheck`)
56
- stay plain `webjs` on Node and still expect an install.
57
-
58
- **Version pinning (#685).** Bun's auto-install fetches `latest` for a bare
59
- `import 'zod'`, ignoring package.json and `bun.lock` (it consults neither the
60
- manifest, the lockfile, nor an `onResolve` plugin, verified in #684). webjs
61
- closes that gap with an `onLoad` transform: it rewrites a declared dep's bare
62
- specifier to an inline-versioned one (`zod` to `zod@3.22.4`), which Bun's
63
- auto-install DOES honor. The version is the EXACT one from `bun.lock` when
64
- present, else an exact `package.json` pin. Bun resolves an exact inline version
65
- but ENOENTs on a range or dist-tag (`zod@^3`, `zod@latest`), so a dep declared
66
- as a RANGE without a `bun.lock` is left bare and resolves to latest as before:
67
- commit a `bun.lock` (or pin exact versions) to get pinned zero-install. The
68
- rewrite is server-runtime only (it shapes what Bun fetches for SSR and server
69
- actions). The browser is still served bare specifiers resolved via the importmap
70
- / jspm, unchanged. Only declared deps are rewritten (a transitive dep follows
71
- from its pinned parent's manifest), and it is a no-op when `node_modules` exists
72
- (Bun uses the installed copy). Default on. Opt out with `WEBJS_PIN=0` or
73
- `{ "webjs": { "pin": false } }`.
74
-
75
- **Reproducibility:** dev resolves on demand (now at the pinned versions), and the
76
- scaffold's Bun Dockerfile still keeps an explicit `bun install` so a prod image
77
- is immutable and self-contained with no registry fetch at boot.
45
+ `bun --bun` overrides the `webjs` bin's Node shebang so the server runs on Bun
46
+ (selecting the native `Bun.serve` listener and `amaro` type stripping); the app's
47
+ deps resolve from `node_modules`, the same as Node. The `start.before` migrate
48
+ (`webjs db migrate`) runs under Bun too. Commit a `bun.lock` (the Bun analog of
49
+ `package-lock.json`) for reproducible, offline installs; the scaffold's Bun
50
+ Dockerfile runs `bun install` and serves via `CMD ["bun", "--bun", "run",
51
+ "start"]`.
78
52
 
79
53
  ## SQLite busy_timeout (#674)
80
54
 
package/src/mcp-source.js CHANGED
@@ -48,15 +48,8 @@ const MAX_FILES = 4000;
48
48
  * reliable. The source dir is `src/`, or `lib/` for the cli. A package that is
49
49
  * not installed is skipped (not every app depends on every `@webjsdev/*`).
50
50
  *
51
- * Under Bun ZERO-INSTALL (no `node_modules`, #675) the packages live only in
52
- * Bun's global install cache (`<bunCacheDir>/@webjsdev/<pkg>@<version>@@@<n>/`,
53
- * which holds the full source). When the `node_modules` walk finds nothing and a
54
- * `bunCacheDir` + `readdir` are supplied, fall back to scanning that scope dir
55
- * for the package's versioned entry (highest version wins, best-effort), so the
56
- * tool works whether the package is installed OR resolved from the Bun cache.
57
- *
58
51
  * @param {string} cwd
59
- * @param {{ exists: (p: string) => boolean, readdir?: (d: string) => Array<{ name: string, isDir: boolean }>, bunCacheDir?: string | null }} fsDeps
52
+ * @param {{ exists: (p: string) => boolean }} fsDeps
60
53
  * @returns {Array<{ pkg: string, root: string, src: string }>}
61
54
  */
62
55
  export function resolveFrameworkRoots(cwd, fsDeps) {
@@ -76,8 +69,6 @@ export function resolveFrameworkRoots(cwd, fsDeps) {
76
69
  const cand = join(base, '@webjsdev', pkg);
77
70
  if (fsDeps.exists(join(cand, 'package.json'))) { root = cand; break; }
78
71
  }
79
- // Zero-install fallback: no node_modules, so look in Bun's global cache.
80
- if (!root) root = resolveFromBunCache(pkg, fsDeps);
81
72
  if (!root) continue;
82
73
  // Most packages keep source in `src/`; the cli keeps it in `lib/`. Use
83
74
  // whichever exists so every framework package's source is reachable.
@@ -91,69 +82,6 @@ export function resolveFrameworkRoots(cwd, fsDeps) {
91
82
  return out;
92
83
  }
93
84
 
94
- /**
95
- * Locate `@webjsdev/<pkg>` in Bun's global install cache (the zero-install
96
- * fallback). Bun caches a scoped package at `<bunCacheDir>/@webjsdev/<pkg>@<ver>@@@<n>/`
97
- * (the versioned dir holds the full source; a sibling unversioned `<pkg>/` dir is
98
- * metadata, skipped). Picks the highest cached version (semver-aware, so
99
- * `0.10.0` beats `0.9.0`), and returns its path only if it carries a
100
- * `package.json`. Returns '' when there is no cache dir, no `readdir`, or no
101
- * matching entry.
102
- *
103
- * @param {string} pkg
104
- * @param {{ exists: (p: string) => boolean, readdir?: (d: string) => Array<{ name: string, isDir: boolean }>, bunCacheDir?: string | null }} fsDeps
105
- * @returns {string}
106
- */
107
- function resolveFromBunCache(pkg, fsDeps) {
108
- const { bunCacheDir, readdir, exists } = fsDeps;
109
- if (!bunCacheDir || typeof readdir !== 'function') return '';
110
- const scopeDir = join(bunCacheDir, '@webjsdev');
111
- if (!exists(scopeDir)) return '';
112
- let entries;
113
- try { entries = readdir(scopeDir); } catch { return ''; }
114
- const prefix = `${pkg}@`;
115
- // A cache dir is `<pkg>@<version>@@@<n>`; extract <version> (between the name
116
- // and the `@@@` cache-key suffix) and sort by it, highest first.
117
- const versioned = entries
118
- .filter((e) => e.isDir && e.name.startsWith(prefix) && e.name.includes('@@@'))
119
- .map((e) => ({ name: e.name, version: e.name.slice(prefix.length).split('@@@')[0] }))
120
- .sort((a, b) => compareVersions(b.version, a.version));
121
- for (const { name } of versioned) {
122
- const cand = join(scopeDir, name);
123
- if (exists(join(cand, 'package.json'))) return cand;
124
- }
125
- return '';
126
- }
127
-
128
- /**
129
- * Compare two semver-ish version strings numerically (so `0.10.0` > `0.9.0`),
130
- * with a release ranking ABOVE its prerelease (`1.0.0` > `1.0.0-rc.1`). Compares
131
- * the release core (`major.minor.patch`) segment by segment numerically; on a
132
- * tie, a version WITHOUT a prerelease tag sorts higher, else the prerelease tags
133
- * compare lexically. The build suffix (`+...`) is ignored, per semver. Returns
134
- * negative / 0 / positive like a comparator.
135
- * @param {string} a
136
- * @param {string} b
137
- * @returns {number}
138
- */
139
- function compareVersions(a, b) {
140
- const parse = (v) => {
141
- const [core, pre = ''] = v.split('+')[0].split('-');
142
- return { nums: core.split('.').map(Number), pre };
143
- };
144
- const A = parse(a);
145
- const B = parse(b);
146
- for (let i = 0; i < Math.max(A.nums.length, B.nums.length); i++) {
147
- const x = A.nums[i] || 0;
148
- const y = B.nums[i] || 0;
149
- if (x !== y) return x - y;
150
- }
151
- if (A.pre === B.pre) return 0;
152
- if (!A.pre) return 1; // a release outranks any prerelease of the same core
153
- if (!B.pre) return -1;
154
- return A.pre < B.pre ? -1 : 1;
155
- }
156
-
157
85
  /**
158
86
  * Recursively list text-source files under `dir` (absolute paths), skipping
159
87
  * `node_modules` / `dist` and bounded by {@link MAX_FILES}.
package/src/mcp.js CHANGED
@@ -21,8 +21,7 @@
21
21
  */
22
22
 
23
23
  import { createInterface } from 'node:readline';
24
- import { relative, join } from 'node:path';
25
- import { spawnSync } from 'node:child_process';
24
+ import { relative } from 'node:path';
26
25
 
27
26
  import {
28
27
  resolveDocsLocation,
@@ -37,29 +36,6 @@ import { resolveFrameworkRoots, runSourceTool } from './mcp-source.js';
37
36
 
38
37
  const PROTOCOL_VERSION = '2024-11-05';
39
38
 
40
- /**
41
- * Best-effort path to Bun's global install cache, for the zero-install `source`
42
- * fallback (#687): under Bun zero-install an app has no `node_modules`, so
43
- * `@webjsdev/*` lives only in this cache. Prefers `bun pm cache` (authoritative),
44
- * then `$BUN_INSTALL/install/cache`, then `~/.bun/install/cache`. Returns null
45
- * when none exists (e.g. Bun is not installed), which leaves the tool on the
46
- * node_modules path with no behavior change.
47
- * @param {(p: string) => boolean} exists
48
- * @returns {string | null}
49
- */
50
- function resolveBunCacheDir(exists) {
51
- try {
52
- const out = spawnSync('bun', ['pm', 'cache'], { encoding: 'utf8' });
53
- const dir = out && out.status === 0 && out.stdout ? out.stdout.trim() : '';
54
- if (dir && exists(dir)) return dir;
55
- } catch { /* bun not on PATH */ }
56
- const candidates = [];
57
- if (process.env.BUN_INSTALL) candidates.push(join(process.env.BUN_INSTALL, 'install', 'cache'));
58
- if (process.env.HOME) candidates.push(join(process.env.HOME, '.bun', 'install', 'cache'));
59
- for (const c of candidates) { if (exists(c)) return c; }
60
- return null;
61
- }
62
-
63
39
  // Mirrors packages/server/src/action-config.js. A drift test in
64
40
  // packages/mcp/test/mcp.test.mjs asserts these stay in sync with the source.
65
41
  /** HTTP verbs an action may declare (mirrors RPC_VERBS in action-config.js). */
@@ -509,7 +485,7 @@ export async function runMcpServer(opts) {
509
485
  const { readdirSync, existsSync, realpathSync } = await import('node:fs');
510
486
  const readdir = (d) => readdirSync(d, { withFileTypes: true }).map((e) => ({ name: e.name, isDir: e.isDirectory() }));
511
487
  sourceDeps = {
512
- roots: resolveFrameworkRoots(cwd, { exists: existsSync, readdir, bunCacheDir: resolveBunCacheDir(existsSync) }),
488
+ roots: resolveFrameworkRoots(cwd, { exists: existsSync }),
513
489
  readFile,
514
490
  readdir,
515
491
  realpath: realpathSync,