@webjsdev/mcp 0.1.18 → 0.1.19
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 +18 -99
- package/package.json +5 -10
- package/src/brief.js +52 -0
- package/src/catalog.js +82 -0
- package/src/mcp-init.js +139 -0
- package/src/mcp-source.js +94 -8
- package/src/mcp.js +32 -144
- package/resources/AGENTS.md +0 -704
- package/resources/SKILL.md +0 -287
- package/resources/corpus.json +0 -6
- package/resources/references/auth-and-sessions.md +0 -300
- package/resources/references/built-ins.md +0 -346
- package/resources/references/client-router-and-streaming.md +0 -317
- package/resources/references/components.md +0 -505
- package/resources/references/data-and-actions.md +0 -325
- package/resources/references/module-structure.md +0 -229
- package/resources/references/muscle-memory-gotchas.md +0 -362
- package/resources/references/optimistic-ui.md +0 -179
- package/resources/references/routing-and-pages.md +0 -323
- package/resources/references/runtime.md +0 -113
- package/resources/references/service-worker.md +0 -80
- package/resources/references/styling.md +0 -312
- package/resources/references/testing.md +0 -223
- package/resources/references/typescript.md +0 -231
- package/resources/references/ui-kit.md +0 -161
- package/src/mcp-docs.js +0 -643
package/src/mcp.js
CHANGED
|
@@ -23,19 +23,12 @@
|
|
|
23
23
|
import { createInterface } from 'node:readline';
|
|
24
24
|
import { relative } from 'node:path';
|
|
25
25
|
|
|
26
|
-
import {
|
|
27
|
-
|
|
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';
|
|
26
|
+
import { initText } from './mcp-init.js';
|
|
27
|
+
import { resolveFrameworkRoots, runSourceTool, listDir } from './mcp-source.js';
|
|
36
28
|
// The export lookup behind the `source` tool's `export` arg, re-exported for
|
|
37
29
|
// `webjs source <Export>` in the CLI (#1623), which shells the same text.
|
|
38
|
-
export { lookupExport, resolveFrameworkRoots } from './mcp-source.js';
|
|
30
|
+
export { lookupExport, resolveFrameworkRoots, listDir, listSources } from './mcp-source.js';
|
|
31
|
+
export { featureCatalog, FEATURE_CATALOG } from './catalog.js';
|
|
39
32
|
import { projectRoutes } from './routes-report.js';
|
|
40
33
|
|
|
41
34
|
const PROTOCOL_VERSION = '2024-11-05';
|
|
@@ -56,10 +49,9 @@ const RESERVED_CONFIG = new Set(['method', 'cache', 'tags', 'invalidates', 'vali
|
|
|
56
49
|
/**
|
|
57
50
|
* The `appDir` property, shared by every tool that is scoped to one app.
|
|
58
51
|
*
|
|
59
|
-
* `init`
|
|
60
|
-
*
|
|
61
|
-
* not ADVERTISE `appDir` could never receive one from a conforming client
|
|
62
|
-
* the app-corpus rung would be reachable only from hand-written JSON-RPC.
|
|
52
|
+
* `init` carries it too: it compares the app's installed `@webjsdev/mcp` with
|
|
53
|
+
* the running server's to warn about a stale server (#1319), and a tool that did
|
|
54
|
+
* not ADVERTISE `appDir` could never receive one from a conforming client.
|
|
63
55
|
*/
|
|
64
56
|
const APPDIR_PROP = {
|
|
65
57
|
type: 'string',
|
|
@@ -73,33 +65,13 @@ const APPDIR_SCHEMA = {
|
|
|
73
65
|
required: [],
|
|
74
66
|
};
|
|
75
67
|
|
|
76
|
-
/** `init` takes only the optional appDir,
|
|
68
|
+
/** `init` takes only the optional appDir, whose installed server version it compares. */
|
|
77
69
|
const INIT_SCHEMA = {
|
|
78
70
|
type: 'object',
|
|
79
71
|
properties: {
|
|
80
72
|
appDir: {
|
|
81
73
|
...APPDIR_PROP,
|
|
82
|
-
description:
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
required: [],
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
/** `docs` takes an optional topic OR a free-text query, plus the optional appDir. */
|
|
89
|
-
const DOCS_SCHEMA = {
|
|
90
|
-
type: 'object',
|
|
91
|
-
properties: {
|
|
92
|
-
appDir: {
|
|
93
|
-
...APPDIR_PROP,
|
|
94
|
-
description: "App directory whose docs corpus to read. Defaults to the server cwd.",
|
|
95
|
-
},
|
|
96
|
-
topic: {
|
|
97
|
-
type: 'string',
|
|
98
|
-
description: 'A doc name (e.g. components, recipes, lit-muscle-memory-gotchas, AGENTS). Returns the full doc.',
|
|
99
|
-
},
|
|
100
|
-
query: {
|
|
101
|
-
type: 'string',
|
|
102
|
-
description: 'Free-text search across all webjs docs. Returns matching lines with their source.',
|
|
74
|
+
description: 'App directory the agent is editing, checked for a newer @webjsdev/mcp than this server. Defaults to the server cwd.',
|
|
103
75
|
},
|
|
104
76
|
},
|
|
105
77
|
required: [],
|
|
@@ -148,36 +120,29 @@ const SOURCE_SCHEMA = {
|
|
|
148
120
|
},
|
|
149
121
|
package: {
|
|
150
122
|
type: 'string',
|
|
151
|
-
description: '
|
|
123
|
+
description: 'List one package\'s source entry points (core, server, cli, intellisense, ui).',
|
|
152
124
|
},
|
|
153
125
|
},
|
|
154
126
|
required: [],
|
|
155
127
|
};
|
|
156
128
|
|
|
157
129
|
/**
|
|
158
|
-
* The tools. The
|
|
159
|
-
* function (read-only, appDir-scoped). `init`
|
|
160
|
-
* framework
|
|
161
|
-
*
|
|
162
|
-
* model picks the right tool without reading source.
|
|
130
|
+
* The tools. The introspection tools project an EXISTING @webjsdev/server
|
|
131
|
+
* function (read-only, appDir-scoped). `init` returns the WebJs brief, `source`
|
|
132
|
+
* reads the framework's own source, and `ui` reads the @webjsdev/ui kit.
|
|
133
|
+
* Descriptions are crisp so a model picks the right tool without reading source.
|
|
163
134
|
*/
|
|
164
135
|
const TOOL_DEFS = [
|
|
165
136
|
{
|
|
166
137
|
name: 'init',
|
|
167
138
|
description:
|
|
168
|
-
'READ THIS FIRST before writing or editing a
|
|
139
|
+
'READ THIS FIRST before writing or editing a WebJs app. Returns the WebJs brief (Next.js App Router routing plus Lit component APIs, imported from @webjsdev/*, with the few differences that matter) and what each other tool is for. Read-only.',
|
|
169
140
|
inputSchema: INIT_SCHEMA,
|
|
170
141
|
},
|
|
171
|
-
{
|
|
172
|
-
name: 'docs',
|
|
173
|
-
description:
|
|
174
|
-
'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.',
|
|
175
|
-
inputSchema: DOCS_SCHEMA,
|
|
176
|
-
},
|
|
177
142
|
{
|
|
178
143
|
name: 'source',
|
|
179
144
|
description:
|
|
180
|
-
'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 `export` for the signature plus doc comment of ONE export (the cheap way to check a contract: `export: "createAuth"`), `path` to read a file (e.g. server/src/ssr/head.js; the bare server/src/ssr.js is a barrel that re-exports from the sibling directory), `query` to grep the @webjsdev/* src trees,
|
|
145
|
+
'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 `export` for the signature plus doc comment of ONE export (the cheap way to check a contract: `export: "createAuth"`), `path` to read a file (e.g. server/src/ssr/head.js; the bare server/src/ssr.js is a barrel that re-exports from the sibling directory), `query` to grep the @webjsdev/* src trees, `package` to list one package\'s entry points, or NO ARGS for the feature catalogue: what WebJs provides by need (auth, sessions, caching, rate limits, uploads, realtime, SEO, test helpers), each name one `export` call away from its signature and example. Call it before hand-writing any of those. Read-only.',
|
|
181
146
|
inputSchema: SOURCE_SCHEMA,
|
|
182
147
|
},
|
|
183
148
|
{
|
|
@@ -204,12 +169,6 @@ const TOOL_DEFS = [
|
|
|
204
169
|
'List registered custom-element tags: tag name, defining file, and class name. Read-only.',
|
|
205
170
|
inputSchema: APPDIR_SCHEMA,
|
|
206
171
|
},
|
|
207
|
-
{
|
|
208
|
-
name: 'list_elision',
|
|
209
|
-
description:
|
|
210
|
-
'Report the display-only elision verdict: every component module with whether it is elided (the browser never downloads it) or shipped plus the evidence that produced the verdict, every page/layout route module as inert / import-only / shipped (with the first client-effecting blocker that pins it), and any ORPHAN component class, one that either has no registration call at all or registers a computed tag, which the scanner cannot see either way, so it always loses its elision verdict, its tag-to-module registry entry, and its preload hint (and never upgrades at all with no registration call, or when a computed-tag class has no importer that ships whole). Identical to `webjs elision --json`. Read-only.',
|
|
211
|
-
inputSchema: APPDIR_SCHEMA,
|
|
212
|
-
},
|
|
213
172
|
{
|
|
214
173
|
name: 'check',
|
|
215
174
|
description:
|
|
@@ -345,7 +304,7 @@ export function extractActionConfig(src) {
|
|
|
345
304
|
* JSON-serialisable projection of an existing server data function. All are
|
|
346
305
|
* read-only.
|
|
347
306
|
*
|
|
348
|
-
* @param {{ buildRouteTable: Function, buildActionIndex: Function, hashFile: Function, scanComponents: Function,
|
|
307
|
+
* @param {{ buildRouteTable: Function, buildActionIndex: Function, hashFile: Function, scanComponents: Function, checkConventions: Function, projectCheck: Function, readFile: Function }} deps
|
|
349
308
|
*/
|
|
350
309
|
export function makeToolRunners(deps) {
|
|
351
310
|
const {
|
|
@@ -353,7 +312,6 @@ export function makeToolRunners(deps) {
|
|
|
353
312
|
buildActionIndex,
|
|
354
313
|
hashFile,
|
|
355
314
|
scanComponents,
|
|
356
|
-
analyzeAppElision,
|
|
357
315
|
checkConventions,
|
|
358
316
|
projectCheck,
|
|
359
317
|
readFile,
|
|
@@ -417,15 +375,6 @@ export function makeToolRunners(deps) {
|
|
|
417
375
|
.sort((a, b) => a.tag.localeCompare(b.tag));
|
|
418
376
|
},
|
|
419
377
|
|
|
420
|
-
async list_elision(appDir) {
|
|
421
|
-
// The whole report IS the contract: `analyzeAppElision` already returns
|
|
422
|
-
// an app-relative, sorted, JSON-serializable object, so unlike
|
|
423
|
-
// `list_routes` there is no projector leaf here and nothing to keep in
|
|
424
|
-
// sync. `webjs elision --json` prints this same object, and a drift test
|
|
425
|
-
// asserts the two are equal.
|
|
426
|
-
return analyzeAppElision(appDir);
|
|
427
|
-
},
|
|
428
|
-
|
|
429
378
|
async check(appDir) {
|
|
430
379
|
const violations = await checkConventions(appDir);
|
|
431
380
|
return projectCheck(violations);
|
|
@@ -563,7 +512,6 @@ export async function runMcpServer(opts) {
|
|
|
563
512
|
buildActionIndex: server.buildActionIndex,
|
|
564
513
|
hashFile: server.hashFile,
|
|
565
514
|
scanComponents: server.scanComponents,
|
|
566
|
-
analyzeAppElision: server.analyzeAppElision,
|
|
567
515
|
checkConventions: check.checkConventions,
|
|
568
516
|
projectCheck,
|
|
569
517
|
readFile,
|
|
@@ -571,47 +519,16 @@ export async function runMcpServer(opts) {
|
|
|
571
519
|
}
|
|
572
520
|
const runners = makeToolRunners(deps);
|
|
573
521
|
|
|
574
|
-
// The
|
|
575
|
-
//
|
|
576
|
-
//
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
// Resolved PER CALL and memoized by appDir (#1319), not once at boot. `appDir`
|
|
580
|
-
// is a per-call tool argument, so a boot-time resolution would pin the corpus
|
|
581
|
-
// to the launch directory forever and defeat the app-corpus rung for any host
|
|
582
|
-
// that overrides it. `resources/list` and `resources/read` carry no appDir in
|
|
583
|
-
// the MCP protocol, so they resolve from `cwd`, which is the same value a
|
|
584
|
-
// `tools/call` with no `appDir` argument defaults to; the surfaces can only
|
|
585
|
-
// diverge when a caller deliberately asks about a different app.
|
|
586
|
-
let docsFs = null;
|
|
587
|
-
if (!opts.docsDeps) {
|
|
522
|
+
// The `init` deps: fs readers for the stale-server check, which compares the
|
|
523
|
+
// app's installed @webjsdev/mcp with this server's version (#1319). Resolved
|
|
524
|
+
// per call from the tool's `appDir`. Injectable for tests via `initFs`.
|
|
525
|
+
let initFs = opts.initFs;
|
|
526
|
+
if (!initFs) {
|
|
588
527
|
const { readFile } = await import('node:fs/promises');
|
|
589
|
-
const {
|
|
590
|
-
|
|
528
|
+
const { existsSync } = await import('node:fs');
|
|
529
|
+
initFs = { readFile, exists: existsSync };
|
|
591
530
|
}
|
|
592
|
-
|
|
593
|
-
const docsDepsCache = new Map();
|
|
594
|
-
/** The docs deps for one appDir. An injected `opts.docsDeps` wins for every appDir. */
|
|
595
|
-
const docsDepsFor = (dir) => {
|
|
596
|
-
if (opts.docsDeps) return opts.docsDeps;
|
|
597
|
-
const key = typeof dir === 'string' ? dir : '';
|
|
598
|
-
let built = docsDepsCache.get(key);
|
|
599
|
-
if (!built) {
|
|
600
|
-
const loc = resolveDocsLocation(import.meta.url, key);
|
|
601
|
-
built = {
|
|
602
|
-
docsDir: loc.docsDir,
|
|
603
|
-
agentsPath: loc.agentsPath,
|
|
604
|
-
skillPath: loc.skillPath,
|
|
605
|
-
corpusPath: loc.corpusPath,
|
|
606
|
-
corpusSource: loc.corpusSource,
|
|
607
|
-
appDir: key,
|
|
608
|
-
serverVersion: version,
|
|
609
|
-
...docsFs,
|
|
610
|
-
};
|
|
611
|
-
docsDepsCache.set(key, built);
|
|
612
|
-
}
|
|
613
|
-
return built;
|
|
614
|
-
};
|
|
531
|
+
const initDepsFor = (dir) => ({ ...initFs, appDir: dir, serverVersion: version });
|
|
615
532
|
|
|
616
533
|
// The `source` tool (#378): read the framework's own source from
|
|
617
534
|
// node_modules/@webjsdev/*/src (no-build, so it is the real JSDoc). Roots are
|
|
@@ -619,8 +536,8 @@ export async function runMcpServer(opts) {
|
|
|
619
536
|
let sourceDeps = opts.sourceDeps;
|
|
620
537
|
if (!sourceDeps) {
|
|
621
538
|
const { readFile } = await import('node:fs/promises');
|
|
622
|
-
const {
|
|
623
|
-
const readdir =
|
|
539
|
+
const { existsSync, realpathSync } = await import('node:fs');
|
|
540
|
+
const readdir = listDir;
|
|
624
541
|
sourceDeps = {
|
|
625
542
|
roots: resolveFrameworkRoots(cwd, { exists: existsSync }),
|
|
626
543
|
readFile,
|
|
@@ -657,7 +574,7 @@ export async function runMcpServer(opts) {
|
|
|
657
574
|
if (method === 'initialize') {
|
|
658
575
|
return rpcResult(id, {
|
|
659
576
|
protocolVersion: PROTOCOL_VERSION,
|
|
660
|
-
capabilities: { tools: {}
|
|
577
|
+
capabilities: { tools: {} },
|
|
661
578
|
serverInfo: { name: 'webjs', version },
|
|
662
579
|
});
|
|
663
580
|
}
|
|
@@ -675,39 +592,12 @@ export async function runMcpServer(opts) {
|
|
|
675
592
|
});
|
|
676
593
|
}
|
|
677
594
|
|
|
678
|
-
// Knowledge layer (#376): the framework docs as MCP resources.
|
|
679
|
-
if (method === 'resources/list') {
|
|
680
|
-
return rpcResult(id, { resources: listResources(docsDepsFor(cwd)) });
|
|
681
|
-
}
|
|
682
|
-
if (method === 'resources/read') {
|
|
683
|
-
const uri = ((msg && msg.params) || {}).uri;
|
|
684
|
-
try {
|
|
685
|
-
const r = await readResource(docsDepsFor(cwd), uri);
|
|
686
|
-
return rpcResult(id, { contents: [r] });
|
|
687
|
-
} catch (e) {
|
|
688
|
-
return rpcError(id, -32602, e && e.message ? e.message : String(e));
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Knowledge layer (#376): the recipes as guided-workflow prompts.
|
|
693
|
-
if (method === 'prompts/list') {
|
|
694
|
-
return rpcResult(id, { prompts: PROMPTS });
|
|
695
|
-
}
|
|
696
|
-
if (method === 'prompts/get') {
|
|
697
|
-
const params = (msg && msg.params) || {};
|
|
698
|
-
try {
|
|
699
|
-
return rpcResult(id, getPrompt(params.name, params.arguments));
|
|
700
|
-
} catch (e) {
|
|
701
|
-
return rpcError(id, -32602, e && e.message ? e.message : String(e));
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
|
|
705
595
|
if (method === 'tools/call') {
|
|
706
596
|
const params = (msg && msg.params) || {};
|
|
707
597
|
const name = params.name;
|
|
708
598
|
const args = params.arguments || {};
|
|
709
|
-
// The knowledge tools
|
|
710
|
-
const isKnowledgeTool = name === 'init' || name === '
|
|
599
|
+
// The knowledge tools (the brief, the framework source) return text.
|
|
600
|
+
const isKnowledgeTool = name === 'init' || name === 'source';
|
|
711
601
|
// `ui` is kit-scoped (not app-scoped), so it is dispatched separately.
|
|
712
602
|
const isUiTool = name === 'ui';
|
|
713
603
|
if (!isKnowledgeTool && !isUiTool && !runners[name]) {
|
|
@@ -717,10 +607,8 @@ export async function runMcpServer(opts) {
|
|
|
717
607
|
try {
|
|
718
608
|
const result = isKnowledgeTool
|
|
719
609
|
? name === 'init'
|
|
720
|
-
? await initText(
|
|
721
|
-
:
|
|
722
|
-
? await searchDocs(docsDepsFor(appDir), args)
|
|
723
|
-
: await runSourceTool(sourceDeps, args)
|
|
610
|
+
? await initText(initDepsFor(appDir))
|
|
611
|
+
: await runSourceTool(sourceDeps, args)
|
|
724
612
|
: isUiTool
|
|
725
613
|
? runUiTool(uiDeps, args)
|
|
726
614
|
: await runners[name](appDir);
|