@webjsdev/cli 0.10.10 → 0.10.12

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.
@@ -0,0 +1,244 @@
1
+ /**
2
+ * The `source` tool for `webjs mcp` (#378): read-only access to the FRAMEWORK
3
+ * source itself.
4
+ *
5
+ * webjs is buildless, so every app's `node_modules/@webjsdev/<pkg>/src` holds the
6
+ * authored JSDoc `.js`, and server-side that source runs directly. (The one built
7
+ * artifact is the `@webjsdev/core` browser bundle in `dist/`, which this tool
8
+ * deliberately skips: it surfaces only the authored `src/`.) That is a real
9
+ * advantage: when the docs do not answer a question, an agent can read the real
10
+ * authored source. This tool makes that first-class and
11
+ * discoverable (and reachable for an MCP-only client with no filesystem tools):
12
+ * - no args (or `package`): list the resolved `@webjsdev/*` packages + their
13
+ * `src/` entry-point files.
14
+ * - `query`: grep the framework `src/` trees, returning bounded `file:line`
15
+ * hits (with a disclosed cap, no silent truncation).
16
+ * - `path`: read one source file (e.g. `server/src/ssr.js`), traversal-guarded
17
+ * to stay inside a resolved framework package root.
18
+ *
19
+ * READ-ONLY and side-effect-free: it only reads files, loads no module, and
20
+ * cannot read outside the resolved `@webjsdev/*` package roots. Zero-dependency,
21
+ * consistent with the rest of the server. PURE given injected `deps`
22
+ * (`{ roots, readFile, readdir }`), so it is testable against a fake tree.
23
+ *
24
+ * @module mcp-source
25
+ */
26
+
27
+ import { createRequire } from 'node:module';
28
+ import { join, resolve, sep, relative } from 'node:path';
29
+
30
+ /** The published framework packages whose source an agent may want to read. */
31
+ export const FRAMEWORK_PACKAGES = ['core', 'server', 'cli', 'ts-plugin', 'ui'];
32
+
33
+ /** Source file extensions worth grepping / reading (text, not assets). */
34
+ const TEXT_EXT = /\.(?:js|ts|mjs|mts|cjs|cts|json|md)$/i;
35
+
36
+ /** Bound the grep output (disclosed when hit) and the walk (defensive). */
37
+ const MAX_HITS = 60;
38
+ const MAX_FILES = 4000;
39
+
40
+ /**
41
+ * Resolve each `@webjsdev/*` package's root + source dir from `cwd`. Locates the
42
+ * root by checking each `require.resolve.paths` node_modules dir on disk for
43
+ * `@webjsdev/<pkg>/package.json`, so it works for a real `node_modules` install
44
+ * AND the monorepo workspace (where the dir is a symlink to `packages/<pkg>`),
45
+ * and honours hoisting. This fs check is deliberate: `<pkg>/package.json` is
46
+ * blocked by `exports` for server/cli/ui, and the bin-only cli has no main
47
+ * entry, so neither `resolve('<pkg>/package.json')` nor `resolve('<pkg>')` is
48
+ * reliable. The source dir is `src/`, or `lib/` for the cli. A package that is
49
+ * not installed is skipped (not every app depends on every `@webjsdev/*`).
50
+ *
51
+ * @param {string} cwd
52
+ * @param {{ exists: (p: string) => boolean }} fsDeps
53
+ * @returns {Array<{ pkg: string, root: string, src: string }>}
54
+ */
55
+ export function resolveFrameworkRoots(cwd, fsDeps) {
56
+ const req = createRequire(join(cwd, '__webjs_mcp_source__.js'));
57
+ /** @type {Array<{ pkg: string, root: string, src: string }>} */
58
+ const out = [];
59
+ for (const pkg of FRAMEWORK_PACKAGES) {
60
+ // Find the package ROOT by checking each node_modules search path on disk,
61
+ // NOT via `require.resolve('<pkg>')` or `<pkg>/package.json`: a package whose
62
+ // `exports` omits `./package.json` (server/cli/ui) or that has no main entry
63
+ // (cli is a bin-only package) would otherwise be unreachable. The fs check
64
+ // bypasses both and still honours hoisting (the search paths include every
65
+ // parent `node_modules`).
66
+ const bases = req.resolve.paths(`@webjsdev/${pkg}`) || [];
67
+ let root = '';
68
+ for (const base of bases) {
69
+ const cand = join(base, '@webjsdev', pkg);
70
+ if (fsDeps.exists(join(cand, 'package.json'))) { root = cand; break; }
71
+ }
72
+ if (!root) continue;
73
+ // Most packages keep source in `src/`; the cli keeps it in `lib/`. Use
74
+ // whichever exists so every framework package's source is reachable.
75
+ const src = fsDeps.exists(join(root, 'src'))
76
+ ? join(root, 'src')
77
+ : fsDeps.exists(join(root, 'lib'))
78
+ ? join(root, 'lib')
79
+ : '';
80
+ if (src) out.push({ pkg, root, src });
81
+ }
82
+ return out;
83
+ }
84
+
85
+ /**
86
+ * Recursively list text-source files under `dir` (absolute paths), skipping
87
+ * `node_modules` / `dist` and bounded by {@link MAX_FILES}.
88
+ *
89
+ * @param {string} dir
90
+ * @param {{ readdir: (d: string) => Array<{ name: string, isDir: boolean }> }} deps
91
+ * @returns {string[]}
92
+ */
93
+ export function walkSource(dir, deps) {
94
+ /** @type {string[]} */
95
+ const files = [];
96
+ /** @type {string[]} */
97
+ const stack = [dir];
98
+ while (stack.length && files.length < MAX_FILES) {
99
+ const d = stack.pop();
100
+ let entries = [];
101
+ try { entries = deps.readdir(d); } catch { continue; }
102
+ for (const e of entries) {
103
+ if (e.isDir) {
104
+ if (e.name === 'node_modules' || e.name === 'dist' || e.name === '.git') continue;
105
+ stack.push(join(d, e.name));
106
+ } else if (TEXT_EXT.test(e.name)) {
107
+ files.push(join(d, e.name));
108
+ if (files.length >= MAX_FILES) break;
109
+ }
110
+ }
111
+ }
112
+ return files.sort();
113
+ }
114
+
115
+ /**
116
+ * No-args / `package` mode: list the resolved packages and their `src/`
117
+ * top-level files (the entry points), so the agent has a map to grep or read.
118
+ *
119
+ * @param {{ roots: Array<{ pkg: string, root: string, src: string }>, readdir: Function }} deps
120
+ * @param {string} [pkgFilter]
121
+ * @returns {string}
122
+ */
123
+ export function listSources(deps, pkgFilter) {
124
+ const roots = pkgFilter ? deps.roots.filter((r) => r.pkg === pkgFilter) : deps.roots;
125
+ if (!roots.length) {
126
+ return pkgFilter
127
+ ? `@webjsdev/${pkgFilter} is not installed/resolvable here. Resolvable: ${deps.roots.map((r) => r.pkg).join(', ') || '(none)'}`
128
+ : 'No @webjsdev/* packages resolvable from here (run inside a webjs app or the monorepo).';
129
+ }
130
+ const lines = ['webjs framework authored source (buildless; server-side this runs directly, and core ships a built browser dist/ that is excluded here). Read with `source({ path })` or search with `source({ query })`.', ''];
131
+ for (const r of roots) {
132
+ const dirName = r.src.split(sep).pop(); // 'src' for most, 'lib' for cli
133
+ let entries = [];
134
+ try { entries = deps.readdir(r.src); } catch { entries = []; }
135
+ const top = entries.filter((e) => !e.isDir && TEXT_EXT.test(e.name)).map((e) => e.name).sort();
136
+ const dirs = entries.filter((e) => e.isDir).map((e) => e.name).sort();
137
+ lines.push(`@webjsdev/${r.pkg}/${dirName}:`);
138
+ if (top.length) lines.push(` files: ${top.map((f) => `${r.pkg}/${dirName}/${f}`).join(', ')}`);
139
+ if (dirs.length) lines.push(` subdirs: ${dirs.join(', ')}`);
140
+ }
141
+ return lines.join('\n');
142
+ }
143
+
144
+ /**
145
+ * `query` mode: grep every resolved `src/` tree for the (case-insensitive)
146
+ * substring, returning bounded `[<pkg>/src/<rel>:<line>] <text>` hits. Discloses
147
+ * truncation rather than silently capping.
148
+ *
149
+ * @param {{ roots: Array<{ pkg: string, root: string, src: string }>, readFile: Function, readdir: Function }} deps
150
+ * @param {string} query
151
+ * @returns {Promise<string>}
152
+ */
153
+ export async function grepSources(deps, query) {
154
+ const q = String(query).toLowerCase();
155
+ if (!q) return 'Provide a non-empty `query`.';
156
+ /** @type {string[]} */
157
+ const hits = [];
158
+ let capped = false;
159
+ outer: for (const r of deps.roots) {
160
+ for (const file of walkSource(r.src, deps)) {
161
+ let text = '';
162
+ try { text = await deps.readFile(file, 'utf8'); } catch { continue; }
163
+ if (!text.toLowerCase().includes(q)) continue; // fast skip whole file
164
+ const lines = text.split('\n');
165
+ const rel = relative(r.root, file).split(sep).join('/');
166
+ for (let i = 0; i < lines.length; i++) {
167
+ if (!lines[i].toLowerCase().includes(q)) continue;
168
+ if (hits.length >= MAX_HITS) { capped = true; break outer; }
169
+ hits.push(`[@webjsdev/${r.pkg}/${rel}:${i + 1}] ${lines[i].trim()}`);
170
+ }
171
+ }
172
+ }
173
+ if (!hits.length) return `No matches for "${query}" in the @webjsdev/* source.`;
174
+ if (capped) hits.push(`... (truncated at ${MAX_HITS} matches; narrow the query or read a file with \`path\`)`);
175
+ return hits.join('\n');
176
+ }
177
+
178
+ /** True when `p` is `base` itself or a descendant of it. */
179
+ function within(base, p) {
180
+ return p === base || p.startsWith(base + sep);
181
+ }
182
+
183
+ /**
184
+ * `path` mode: read one AUTHORED-source file. Accepts `<pkg>/...` or
185
+ * `@webjsdev/<pkg>/...`. Scoped to the package's SOURCE dir (`src/`, or `lib/`
186
+ * for cli), so it serves only the authored source and NOT the built `dist/`
187
+ * browser bundle, `node_modules`, etc. Refuses any path that escapes the source
188
+ * dir lexically (`..`/absolute), and (when `deps.realpath` is provided)
189
+ * re-checks the symlink-resolved path so a symlink inside `src/` cannot reach
190
+ * outside. Read-only.
191
+ *
192
+ * @param {{ roots: Array<{ pkg: string, root: string, src: string }>, readFile: Function, realpath?: Function }} deps
193
+ * @param {string} path
194
+ * @returns {Promise<string>}
195
+ */
196
+ export async function readSource(deps, path) {
197
+ const cleaned = String(path).replace(/^@webjsdev\//, '');
198
+ const segs = cleaned.split('/').filter(Boolean);
199
+ const pkg = segs[0];
200
+ const entry = deps.roots.find((r) => r.pkg === pkg);
201
+ if (!entry) {
202
+ return `Unknown or unresolvable package "${pkg || path}". Resolvable: ${deps.roots.map((r) => r.pkg).join(', ') || '(none)'}. Pass a path like server/src/ssr.js.`;
203
+ }
204
+ const abs = resolve(entry.root, segs.slice(1).join('/'));
205
+ const srcLabel = entry.src.split(sep).pop();
206
+ // Scope to the authored source dir, so dist/ (the built core browser bundle),
207
+ // package.json, node_modules, etc. are not readable; only `src/` (or cli `lib/`).
208
+ if (!within(entry.src, abs)) {
209
+ return `Refusing to read outside the @webjsdev/${pkg} authored source (only ${srcLabel}/ is exposed; the built dist/ is not).`;
210
+ }
211
+ // Defense in depth: a symlink inside the source dir must not resolve outside it.
212
+ if (deps.realpath) {
213
+ try {
214
+ if (!within(deps.realpath(entry.src), deps.realpath(abs))) {
215
+ return `Refusing to read outside the @webjsdev/${pkg} authored source (a symlink escapes ${srcLabel}/).`;
216
+ }
217
+ } catch { /* abs does not exist; the readFile below returns the not-a-file message */ }
218
+ }
219
+ // Legacy guard kept as a belt-and-suspenders against a root escape too.
220
+ if (abs !== entry.root && !abs.startsWith(entry.root + sep)) {
221
+ return `Refusing to read outside @webjsdev/${pkg} (path escapes the package root).`;
222
+ }
223
+ try {
224
+ return await deps.readFile(abs, 'utf8');
225
+ } catch {
226
+ return `Could not read ${path} (not a file under @webjsdev/${pkg}).`;
227
+ }
228
+ }
229
+
230
+ /**
231
+ * The `source` tool entry point. Dispatches on the args: `path` reads a file,
232
+ * `query` greps, otherwise (or with `package`) lists the packages. PURE given
233
+ * `deps` (`{ roots, readFile, readdir }`).
234
+ *
235
+ * @param {object} deps
236
+ * @param {{ query?: string, path?: string, package?: string }} [args]
237
+ * @returns {Promise<string>}
238
+ */
239
+ export async function runSourceTool(deps, args) {
240
+ const a = args || {};
241
+ if (a.path) return readSource(deps, a.path);
242
+ if (a.query) return grepSources(deps, a.query);
243
+ return listSources(deps, a.package);
244
+ }
package/lib/mcp.js CHANGED
@@ -23,6 +23,17 @@
23
23
  import { createInterface } from 'node:readline';
24
24
  import { relative } from 'node:path';
25
25
 
26
+ import {
27
+ resolveDocsLocation,
28
+ listResources,
29
+ readResource,
30
+ initText,
31
+ searchDocs,
32
+ PROMPTS,
33
+ getPrompt,
34
+ } from './mcp-docs.js';
35
+ import { resolveFrameworkRoots, runSourceTool } from './mcp-source.js';
36
+
26
37
  const PROTOCOL_VERSION = '2024-11-05';
27
38
 
28
39
  /**
@@ -31,41 +42,109 @@ const PROTOCOL_VERSION = '2024-11-05';
31
42
  * into an agent-friendly shape. Descriptions are crisp so a model picks the
32
43
  * right tool without reading source.
33
44
  */
45
+ /** The shared input schema for the introspection tools: an optional appDir override. */
46
+ const APPDIR_SCHEMA = {
47
+ type: 'object',
48
+ properties: {
49
+ appDir: {
50
+ type: 'string',
51
+ description: 'App directory to introspect. Defaults to the server cwd.',
52
+ },
53
+ },
54
+ required: [],
55
+ };
56
+
57
+ /** `init` takes no input. */
58
+ const INIT_SCHEMA = { type: 'object', properties: {}, required: [] };
59
+
60
+ /** `docs` takes an optional topic OR a free-text query. */
61
+ const DOCS_SCHEMA = {
62
+ type: 'object',
63
+ properties: {
64
+ topic: {
65
+ type: 'string',
66
+ description: 'A doc name (e.g. components, recipes, lit-muscle-memory-gotchas, AGENTS). Returns the full doc.',
67
+ },
68
+ query: {
69
+ type: 'string',
70
+ description: 'Free-text search across all webjs docs. Returns matching lines with their source.',
71
+ },
72
+ },
73
+ required: [],
74
+ };
75
+
76
+ /** `source` reads the framework source: a `path` to read, a `query` to grep, or a `package` to list. */
77
+ const SOURCE_SCHEMA = {
78
+ type: 'object',
79
+ properties: {
80
+ path: {
81
+ type: 'string',
82
+ description: 'A framework source file to read, e.g. server/src/ssr.js or @webjsdev/core/src/render-client.js.',
83
+ },
84
+ query: {
85
+ type: 'string',
86
+ description: 'Grep the @webjsdev/* src trees for this substring. Returns file:line hits.',
87
+ },
88
+ package: {
89
+ type: 'string',
90
+ description: 'Limit a no-args listing to one package (core, server, cli, ts-plugin, ui).',
91
+ },
92
+ },
93
+ required: [],
94
+ };
95
+
96
+ /**
97
+ * The tools. The four introspection tools project an EXISTING @webjsdev/server
98
+ * function (read-only, appDir-scoped). `init` + `docs` (#376) surface the
99
+ * framework knowledge: `init` is the "read first" mental-model primer, `docs`
100
+ * retrieves a doc by topic or searches the corpus. Descriptions are crisp so a
101
+ * model picks the right tool without reading source.
102
+ */
34
103
  const TOOL_DEFS = [
104
+ {
105
+ name: 'init',
106
+ description:
107
+ 'READ THIS FIRST before writing or editing a webjs app. Returns the webjs mental model (NOT React/Next: no RSC, components hydrate but pages do not, signals-default state, the .server boundary) plus the invariants and the doc index. Read-only.',
108
+ inputSchema: INIT_SCHEMA,
109
+ },
110
+ {
111
+ name: 'docs',
112
+ description:
113
+ 'Retrieve webjs framework docs: pass `topic` for a full doc (components, recipes, styling, built-ins, configuration, advanced, metadata, typescript, testing, lit-muscle-memory-gotchas, AGENTS, ...) or `query` to search the corpus. No args returns the topic index. Read-only.',
114
+ inputSchema: DOCS_SCHEMA,
115
+ },
116
+ {
117
+ name: 'source',
118
+ description:
119
+ 'Read the FRAMEWORK authored source (webjs is buildless: node_modules/@webjsdev/*/src is the JSDoc source, run directly server-side; only the core browser bundle is built into dist/, which this skips). Pass `path` to read a file (e.g. server/src/ssr.js), `query` to grep the @webjsdev/* src trees, or no args to list the packages + entry points. Use when the docs do not answer something. Read-only.',
120
+ inputSchema: SOURCE_SCHEMA,
121
+ },
35
122
  {
36
123
  name: 'list_routes',
37
124
  description:
38
125
  'List the app route table: SSR pages (path, file, dynamic flag, param names) and route.{js,ts} API handlers (path, file, HTTP methods). Read-only.',
126
+ inputSchema: APPDIR_SCHEMA,
39
127
  },
40
128
  {
41
129
  name: 'list_actions',
42
130
  description:
43
131
  'List registered server actions (the .server.{js,ts} files with "use server"): file, exported function name, and the /__webjs/action/<hash>/<fn> RPC endpoint. Read-only.',
132
+ inputSchema: APPDIR_SCHEMA,
44
133
  },
45
134
  {
46
135
  name: 'list_components',
47
136
  description:
48
137
  'List registered custom-element tags: tag name, defining file, and class name. Read-only.',
138
+ inputSchema: APPDIR_SCHEMA,
49
139
  },
50
140
  {
51
141
  name: 'check',
52
142
  description:
53
143
  'Run webjs check (correctness rules) and return the structured violations { rule, file, message, fix } plus a summary count and per-rule breakdown. Read-only.',
144
+ inputSchema: APPDIR_SCHEMA,
54
145
  },
55
146
  ];
56
147
 
57
- /** The shared input schema: every tool takes an optional appDir override. */
58
- const TOOL_INPUT_SCHEMA = {
59
- type: 'object',
60
- properties: {
61
- appDir: {
62
- type: 'string',
63
- description: 'App directory to introspect. Defaults to the server cwd.',
64
- },
65
- },
66
- required: [],
67
- };
68
-
69
148
  /**
70
149
  * Lexically extract the names exported from a module source. Recognises the
71
150
  * common forms a server-action / route file uses without LOADING the module
@@ -302,6 +381,39 @@ export async function runMcpServer(opts) {
302
381
  }
303
382
  const runners = makeToolRunners(deps);
304
383
 
384
+ // The docs corpus deps for the knowledge layer (#376): resources / prompts /
385
+ // init / docs. Injectable for tests; otherwise resolved from the bundled
386
+ // (published) or repo-root (dev) docs and node fs.
387
+ let docsDeps = opts.docsDeps;
388
+ if (!docsDeps) {
389
+ const loc = resolveDocsLocation(import.meta.url);
390
+ const { readFile } = await import('node:fs/promises');
391
+ const { readdirSync, existsSync } = await import('node:fs');
392
+ docsDeps = {
393
+ docsDir: loc.docsDir,
394
+ agentsPath: loc.agentsPath,
395
+ listDir: readdirSync,
396
+ exists: existsSync,
397
+ readFile,
398
+ };
399
+ }
400
+
401
+ // The `source` tool (#378): read the framework's own source from
402
+ // node_modules/@webjsdev/*/src (no-build, so it is the real JSDoc). Roots are
403
+ // resolved once from the server cwd. Injectable for tests.
404
+ let sourceDeps = opts.sourceDeps;
405
+ if (!sourceDeps) {
406
+ const { readFile } = await import('node:fs/promises');
407
+ const { readdirSync, existsSync, realpathSync } = await import('node:fs');
408
+ const readdir = (d) => readdirSync(d, { withFileTypes: true }).map((e) => ({ name: e.name, isDir: e.isDirectory() }));
409
+ sourceDeps = {
410
+ roots: resolveFrameworkRoots(cwd, { exists: existsSync }),
411
+ readFile,
412
+ readdir,
413
+ realpath: realpathSync,
414
+ };
415
+ }
416
+
305
417
  /** Write one JSON-RPC frame as a single line to stdout. */
306
418
  const send = (frame) => {
307
419
  stdout.write(JSON.stringify(frame) + '\n');
@@ -323,7 +435,7 @@ export async function runMcpServer(opts) {
323
435
  if (method === 'initialize') {
324
436
  return rpcResult(id, {
325
437
  protocolVersion: PROTOCOL_VERSION,
326
- capabilities: { tools: {} },
438
+ capabilities: { tools: {}, resources: {}, prompts: {} },
327
439
  serverInfo: { name: 'webjs', version },
328
440
  });
329
441
  }
@@ -336,24 +448,61 @@ export async function runMcpServer(opts) {
336
448
  tools: TOOL_DEFS.map((t) => ({
337
449
  name: t.name,
338
450
  description: t.description,
339
- inputSchema: TOOL_INPUT_SCHEMA,
451
+ inputSchema: t.inputSchema,
340
452
  })),
341
453
  });
342
454
  }
343
455
 
456
+ // Knowledge layer (#376): the framework docs as MCP resources.
457
+ if (method === 'resources/list') {
458
+ return rpcResult(id, { resources: listResources(docsDeps) });
459
+ }
460
+ if (method === 'resources/read') {
461
+ const uri = ((msg && msg.params) || {}).uri;
462
+ try {
463
+ const r = await readResource(docsDeps, uri);
464
+ return rpcResult(id, { contents: [r] });
465
+ } catch (e) {
466
+ return rpcError(id, -32602, e && e.message ? e.message : String(e));
467
+ }
468
+ }
469
+
470
+ // Knowledge layer (#376): the recipes as guided-workflow prompts.
471
+ if (method === 'prompts/list') {
472
+ return rpcResult(id, { prompts: PROMPTS });
473
+ }
474
+ if (method === 'prompts/get') {
475
+ const params = (msg && msg.params) || {};
476
+ try {
477
+ return rpcResult(id, getPrompt(params.name, params.arguments));
478
+ } catch (e) {
479
+ return rpcError(id, -32602, e && e.message ? e.message : String(e));
480
+ }
481
+ }
482
+
344
483
  if (method === 'tools/call') {
345
484
  const params = (msg && msg.params) || {};
346
485
  const name = params.name;
347
486
  const args = params.arguments || {};
348
- const runner = runners[name];
349
- if (!runner) {
487
+ // The knowledge tools route to the docs / source layer; they return text.
488
+ const isKnowledgeTool = name === 'init' || name === 'docs' || name === 'source';
489
+ if (!isKnowledgeTool && !runners[name]) {
350
490
  return rpcError(id, -32602, `Unknown tool: ${String(name)}`);
351
491
  }
352
492
  const appDir = typeof args.appDir === 'string' && args.appDir ? args.appDir : cwd;
353
493
  try {
354
- const result = await runner(appDir);
494
+ const result = isKnowledgeTool
495
+ ? name === 'init'
496
+ ? await initText(docsDeps)
497
+ : name === 'docs'
498
+ ? await searchDocs(docsDeps, args)
499
+ : await runSourceTool(sourceDeps, args)
500
+ : await runners[name](appDir);
501
+ // Knowledge tools return a markdown string; introspection tools return
502
+ // a JSON-serialisable object.
503
+ const text = typeof result === 'string' ? result : JSON.stringify(result, null, 2);
355
504
  return rpcResult(id, {
356
- content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
505
+ content: [{ type: 'text', text }],
357
506
  });
358
507
  } catch (e) {
359
508
  // A tool failure is an MCP tool-result error (isError), not a transport
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webjsdev/cli",
3
- "version": "0.10.10",
3
+ "version": "0.10.12",
4
4
  "type": "module",
5
5
  "description": "webjs CLI - dev, start, create, db",
6
6
  "bin": {
@@ -10,7 +10,8 @@
10
10
  "bin",
11
11
  "lib",
12
12
  "templates",
13
- "README.md"
13
+ "README.md",
14
+ "resources"
14
15
  ],
15
16
  "dependencies": {
16
17
  "@webjsdev/server": "^0.8.0",
@@ -35,5 +36,9 @@
35
36
  ],
36
37
  "engines": {
37
38
  "node": ">=24.0.0"
39
+ },
40
+ "scripts": {
41
+ "prepack": "node scripts/copy-mcp-resources.js",
42
+ "postpack": "node scripts/clean-mcp-resources.js"
38
43
  }
39
44
  }