@webjsdev/cli 0.10.11 → 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.
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.11",
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
  }