@specverse/engines 4.3.4 → 5.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.
Files changed (66) hide show
  1. package/assets/examples/10-api/README.md +3 -3
  2. package/assets/prompts/core/README.md +1 -1
  3. package/dist/inference/core/rule-engine.d.ts +0 -12
  4. package/dist/inference/core/rule-engine.d.ts.map +1 -1
  5. package/dist/inference/core/rule-engine.js +99 -968
  6. package/dist/inference/core/rule-engine.js.map +1 -1
  7. package/dist/inference/core/template-helpers.d.ts +56 -0
  8. package/dist/inference/core/template-helpers.d.ts.map +1 -0
  9. package/dist/inference/core/template-helpers.js +87 -0
  10. package/dist/inference/core/template-helpers.js.map +1 -0
  11. package/dist/inference/logical/generators/service-generator.d.ts.map +1 -1
  12. package/dist/inference/logical/generators/service-generator.js +0 -4
  13. package/dist/inference/logical/generators/service-generator.js.map +1 -1
  14. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
  15. package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +1 -1
  16. package/dist/libs/instance-factories/tools/README.md +1 -1
  17. package/dist/libs/instance-factories/tools/mcp.yaml +1 -1
  18. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +336 -116
  19. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +172 -8
  20. package/dist/libs/instance-factories/tools/vscode.yaml +1 -1
  21. package/libs/instance-factories/cli/templates/commander/command-generator.ts +27 -5
  22. package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +10 -6
  23. package/libs/instance-factories/tools/README.md +1 -1
  24. package/libs/instance-factories/tools/mcp.yaml +1 -1
  25. package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +386 -141
  26. package/libs/instance-factories/tools/templates/vscode/static/extension.ts +9 -2
  27. package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +246 -10
  28. package/libs/instance-factories/tools/vscode.yaml +1 -1
  29. package/package.json +5 -4
  30. package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +0 -630
  31. package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +0 -330
  32. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +0 -552
  33. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +0 -164
  34. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +0 -247
  35. package/libs/instance-factories/tools/templates/mcp/static/package.json +0 -94
  36. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +0 -284
  37. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +0 -139
  38. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +0 -74
  39. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +0 -156
  40. package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +0 -41
  41. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +0 -259
  42. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +0 -231
  43. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +0 -196
  44. package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +0 -293
  45. package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +0 -90
  46. package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +0 -24
  47. package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +0 -15
  48. package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +0 -106
  49. package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +0 -75
  50. package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +0 -239
  51. package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +0 -1501
  52. package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +0 -211
  53. package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +0 -308
  54. package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +0 -210
  55. package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +0 -356
  56. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +0 -522
  57. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +0 -530
  58. package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +0 -594
  59. package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +0 -170
  60. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +0 -544
  61. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +0 -189
  62. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +0 -89
  63. package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +0 -110
  64. package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +0 -28
  65. package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +0 -4279
  66. /package/libs/instance-factories/tools/templates/vscode/static/themes/{specverse-complete-theme.json → specverse-dark-theme.json} +0 -0
@@ -1,148 +1,368 @@
1
- import { existsSync, mkdirSync, writeFileSync, readdirSync, statSync, copyFileSync } from "fs";
2
- import { join, dirname } from "path";
3
- import { fileURLToPath } from "url";
4
- const __generatorDir = dirname(fileURLToPath(import.meta.url));
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { join } from "path";
5
3
  function generateMCPServer(context) {
6
4
  const { spec, outputDir } = context;
7
5
  const mcpDir = join(outputDir || ".", "tools", "specverse-mcp");
8
- if (!existsSync(mcpDir)) mkdirSync(mcpDir, { recursive: true });
9
- let staticDir = join(__generatorDir, "static");
10
- if (!existsSync(staticDir)) {
11
- staticDir = __generatorDir.replace("/dist/libs/", "/libs/");
12
- staticDir = join(staticDir, "static");
13
- }
14
- if (existsSync(staticDir)) {
15
- copyRecursive(staticDir, mcpDir);
16
- } else {
17
- console.warn(`[MCP Generator] Static assets not found at ${staticDir}`);
18
- }
19
- const tools = extractMCPTools(spec);
20
- const resources = extractMCPResources(spec);
21
- const cliCommands = extractCLICommands(spec);
22
- const registryCode = generateToolRegistry(tools, resources, cliCommands);
23
- const registryDir = join(mcpDir, "src", "generated");
24
- if (!existsSync(registryDir)) mkdirSync(registryDir, { recursive: true });
25
- writeFileSync(join(registryDir, "spec-registry.ts"), registryCode);
6
+ const srcDir = join(mcpDir, "src");
7
+ if (!existsSync(srcDir)) mkdirSync(srcDir, { recursive: true });
8
+ const distribution = extractMCPDistribution(spec);
9
+ const version = distribution?.version || spec?.metadata?.version || spec?.version || "5.0.0";
10
+ const description = distribution?.description || "SpecVerse MCP server \u2014 exposes the specverse CLI as MCP tools and the live spec schema + docs as MCP resources.";
11
+ const displayName = distribution?.displayName || "SpecVerse MCP";
12
+ const cliCommands = extractCLITools(spec);
13
+ writeFileSync(join(mcpDir, "package.json"), generatePackageJson(version, description));
14
+ writeFileSync(join(mcpDir, "tsconfig.json"), generateTsconfig());
15
+ writeFileSync(join(srcDir, "server.ts"), generateServer(displayName, version));
16
+ writeFileSync(join(srcDir, "cli-runner.ts"), generateCliRunner());
17
+ writeFileSync(join(srcDir, "resources.ts"), generateResources());
18
+ writeFileSync(join(srcDir, "tools.ts"), generateTools(cliCommands));
26
19
  return `MCP server generated in: ${mcpDir}
27
- ${tools.length} tools, ${resources.length} resources, ${cliCommands.length} CLI commands`;
20
+ ${cliCommands.length} tools (one per CLI command), 2 resources (schema + docs)`;
28
21
  }
29
- function extractMCPTools(spec) {
30
- const tools = [];
22
+ function extractCLITools(spec) {
23
+ const out = [];
31
24
  const components = spec?.components || {};
32
- const componentList = Array.isArray(components) ? components : Object.values(components);
33
- if (spec?.services && !Array.isArray(components)) {
34
- componentList.push({ services: spec.services, models: spec.models });
35
- }
36
- for (const component of componentList) {
37
- const comp = component;
38
- const services = comp?.services;
39
- if (services) {
40
- const serviceList = Array.isArray(services) ? services : Object.entries(services).map(([n, d]) => ({ name: n, ...d }));
41
- for (const service of serviceList) {
42
- const operations = service.operations;
43
- if (operations) {
44
- const opEntries = Array.isArray(operations) ? operations : Object.entries(operations).map(([n, d]) => ({ name: n, ...d }));
45
- for (const op of opEntries) {
46
- tools.push({
47
- name: `${service.name.replace(/Service$/, "").toLowerCase()}-${op.name}`,
48
- description: op.description || `${op.name} via ${service.name}`,
49
- isEntityTool: false
50
- });
51
- }
52
- }
53
- }
54
- }
55
- const models = comp?.models;
56
- if (models) {
57
- const modelList = Array.isArray(models) ? models : Object.entries(models).map(([n, d]) => ({ name: n, ...d }));
58
- for (const model of modelList) {
59
- const behaviors = model.behaviors;
60
- if (behaviors) {
61
- const behaviorEntries = typeof behaviors === "object" && !Array.isArray(behaviors) ? Object.entries(behaviors).map(([n, d]) => ({ name: n, ...d })) : [];
62
- for (const behavior of behaviorEntries) {
63
- tools.push({
64
- name: `${model.name.toLowerCase()}-${behavior.name}`,
65
- description: behavior.description || `${behavior.name} on ${model.name}`,
66
- isEntityTool: true
67
- });
25
+ const componentList = Array.isArray(components) ? components : Object.entries(components).map(([name, data]) => ({ name, ...data }));
26
+ for (const comp of componentList) {
27
+ const cliCommands = comp?.commands;
28
+ if (!cliCommands) continue;
29
+ for (const [, rootDef] of Object.entries(cliCommands)) {
30
+ const subcommands = rootDef?.subcommands || {};
31
+ for (const [subName, subDef] of Object.entries(subcommands)) {
32
+ const sub = subDef;
33
+ const nestedSubs = sub?.subcommands;
34
+ if (nestedSubs && Object.keys(nestedSubs).length > 0) {
35
+ for (const [nestedName, nestedDef] of Object.entries(nestedSubs)) {
36
+ out.push(buildCLITool([subName, nestedName], nestedDef));
68
37
  }
38
+ } else {
39
+ out.push(buildCLITool([subName], sub));
69
40
  }
70
41
  }
71
42
  }
72
43
  }
73
- const cliCmds = extractCLICommands(spec);
74
- for (const cmd of cliCmds) {
75
- tools.push({
76
- name: `specverse-${cmd}`,
77
- description: `Execute specverse ${cmd} command`,
78
- isEntityTool: false
79
- });
44
+ return out;
45
+ }
46
+ function buildCLITool(cliArgs, def) {
47
+ const name = cliArgs.join("_");
48
+ const description = def?.description || `Run specverse ${cliArgs.join(" ")}`;
49
+ const properties = {};
50
+ const required = [];
51
+ const args = def?.arguments || {};
52
+ const positionalEntries = Object.entries(args).filter(([, a]) => a.positional).sort(([, a], [, b]) => (a.position || 0) - (b.position || 0));
53
+ const positional = positionalEntries.map(([k]) => k);
54
+ for (const [argName, arg] of Object.entries(args)) {
55
+ properties[argName] = {
56
+ type: mapArgTypeToJsonSchema(arg.type),
57
+ description: arg.description || `${argName} argument`
58
+ };
59
+ if (arg.required) required.push(argName);
80
60
  }
81
- if (tools.length === 0) {
82
- tools.push(
83
- { name: "specverse-validate", description: "Validate a specification", isEntityTool: false },
84
- { name: "specverse-create", description: "Create a specification", isEntityTool: false }
85
- );
61
+ const flags = def?.flags || {};
62
+ for (const [flagName, flag] of Object.entries(flags)) {
63
+ const propName = flagName.replace(/^--/, "").replace(/-([a-z])/g, (_, c) => c.toUpperCase());
64
+ properties[propName] = {
65
+ type: mapArgTypeToJsonSchema(flag.type),
66
+ description: flag.description || `${flagName} flag`
67
+ };
68
+ if (flag.default !== void 0) {
69
+ properties[propName].default = flag.default;
70
+ }
86
71
  }
87
- return tools;
88
- }
89
- function extractMCPResources(spec) {
90
- return [
91
- { uri: "specverse://schema", name: "SpecVerse Schema", description: "JSON Schema for .specly validation" },
92
- { uri: "specverse://conventions", name: "Convention Reference", description: "Convention syntax patterns" },
93
- { uri: "specverse://library-catalog", name: "Library Catalog", description: "Available SpecVerse libraries" },
94
- { uri: "specverse://prompts", name: "Prompt Templates", description: "AI prompt template versions" }
95
- ];
96
- }
97
- function extractCLICommands(spec) {
98
- const commands = [];
99
- const components = spec?.components || [];
100
- for (const component of Array.isArray(components) ? components : Object.values(components)) {
101
- const cliCommands = component?.commands;
102
- if (!cliCommands) continue;
103
- for (const [, rootDef] of Object.entries(cliCommands)) {
104
- const subs = rootDef?.subcommands || {};
105
- for (const subName of Object.keys(subs)) {
106
- commands.push(subName);
107
- }
72
+ return {
73
+ name,
74
+ description,
75
+ cliArgs,
76
+ positional,
77
+ inputSchema: {
78
+ type: "object",
79
+ properties,
80
+ ...required.length > 0 ? { required } : {}
108
81
  }
82
+ };
83
+ }
84
+ function mapArgTypeToJsonSchema(type) {
85
+ if (!type) return "string";
86
+ const t = type.toLowerCase();
87
+ if (t === "boolean") return "boolean";
88
+ if (t === "integer" || t === "number") return "number";
89
+ return "string";
90
+ }
91
+ function extractMCPDistribution(spec) {
92
+ const all = [];
93
+ if (spec?.distributions) {
94
+ all.push(...Array.isArray(spec.distributions) ? spec.distributions : Object.entries(spec.distributions).map(([name, data]) => ({ name, ...data })));
95
+ }
96
+ const components = spec?.components || {};
97
+ const componentList = Array.isArray(components) ? components : Object.values(components);
98
+ for (const comp of componentList) {
99
+ const distributions = comp?.distributions;
100
+ if (!distributions) continue;
101
+ all.push(...Array.isArray(distributions) ? distributions : Object.entries(distributions).map(([name, data]) => ({ name, ...data })));
109
102
  }
110
- return commands;
103
+ for (const dist of all) if (dist.type === "mcp") return dist;
104
+ return null;
105
+ }
106
+ function generatePackageJson(version, description) {
107
+ return JSON.stringify({
108
+ name: "@specverse/mcp",
109
+ version,
110
+ description,
111
+ type: "module",
112
+ main: "dist/server.js",
113
+ bin: { "specverse-mcp": "dist/server.js" },
114
+ scripts: {
115
+ build: "tsc",
116
+ start: "node dist/server.js",
117
+ clean: "rm -rf dist"
118
+ },
119
+ dependencies: {
120
+ "@modelcontextprotocol/sdk": "^1.17.4",
121
+ "@specverse/entities": "^5.0.0"
122
+ },
123
+ devDependencies: {
124
+ "@types/node": "^20.19.11",
125
+ typescript: "^5.9.2"
126
+ },
127
+ files: ["dist", "README.md"],
128
+ engines: { node: ">=18.0.0" },
129
+ publishConfig: { access: "public" },
130
+ license: "MIT"
131
+ }, null, 2) + "\n";
132
+ }
133
+ function generateTsconfig() {
134
+ return JSON.stringify({
135
+ compilerOptions: {
136
+ target: "ES2022",
137
+ module: "ESNext",
138
+ moduleResolution: "bundler",
139
+ esModuleInterop: true,
140
+ strict: true,
141
+ skipLibCheck: true,
142
+ declaration: true,
143
+ outDir: "./dist",
144
+ rootDir: "./src",
145
+ resolveJsonModule: true
146
+ },
147
+ include: ["src/**/*"],
148
+ exclude: ["node_modules", "dist"]
149
+ }, null, 2) + "\n";
150
+ }
151
+ function generateServer(displayName, version) {
152
+ return `#!/usr/bin/env node
153
+ /**
154
+ * SpecVerse MCP Server \u2014 stdio transport.
155
+ *
156
+ * Wires the MCP protocol to the generated tool registry + live resources.
157
+ * No handwritten business logic \u2014 everything is derived from the spec
158
+ * (tools from CLI commands) or from @specverse/entities (schema + docs).
159
+ */
160
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
161
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
162
+ import {
163
+ ListToolsRequestSchema,
164
+ CallToolRequestSchema,
165
+ ListResourcesRequestSchema,
166
+ ReadResourceRequestSchema,
167
+ } from '@modelcontextprotocol/sdk/types.js';
168
+ import { TOOLS, callTool } from './tools.js';
169
+ import { RESOURCES, readResource } from './resources.js';
170
+
171
+ const server = new Server(
172
+ { name: ${JSON.stringify(displayName)}, version: ${JSON.stringify(version)} },
173
+ { capabilities: { tools: {}, resources: {} } },
174
+ );
175
+
176
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
177
+ tools: TOOLS.map(t => ({
178
+ name: t.name,
179
+ description: t.description,
180
+ inputSchema: t.inputSchema,
181
+ })),
182
+ }));
183
+
184
+ server.setRequestHandler(CallToolRequestSchema, async (req) => {
185
+ const { name, arguments: args } = req.params;
186
+ return callTool(name, args ?? {});
187
+ });
188
+
189
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
190
+ resources: RESOURCES.map(r => ({
191
+ uri: r.uri,
192
+ name: r.name,
193
+ description: r.description,
194
+ mimeType: r.mimeType,
195
+ })),
196
+ }));
197
+
198
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
199
+ return readResource(req.params.uri);
200
+ });
201
+
202
+ const transport = new StdioServerTransport();
203
+ await server.connect(transport);
204
+ `;
111
205
  }
112
- function generateToolRegistry(tools, resources, cliCommands) {
206
+ function generateCliRunner() {
113
207
  return `/**
114
- * Spec-Driven MCP Registry
115
- * Generated from SpecVerse self-specification.
208
+ * Thin specverse CLI runner.
116
209
  *
117
- * Tools, resources, and CLI command mappings derived from the spec.
210
+ * Each MCP tool maps to a specverse CLI command + a set of user-supplied
211
+ * arguments. The tool registry tells us which argument keys are positional
212
+ * (declared in the spec as \`positional: true\`) \u2014 those get emitted in
213
+ * order before any flags, matching the CLI's expected argv shape. The
214
+ * specverse binary must be on PATH (the AI client that launches the
215
+ * server inherits env from its parent).
118
216
  */
217
+ import { spawn } from 'child_process';
218
+
219
+ export interface CliResult {
220
+ stdout: string;
221
+ stderr: string;
222
+ code: number | null;
223
+ }
119
224
 
120
- export const SPEC_TOOLS = ${JSON.stringify(tools, null, 2)};
225
+ export function runCli(
226
+ cliArgs: string[],
227
+ positional: string[],
228
+ userArgs: Record<string, any>,
229
+ cwd?: string,
230
+ ): Promise<CliResult> {
231
+ const argv = [...cliArgs];
121
232
 
122
- export const SPEC_RESOURCES = ${JSON.stringify(resources, null, 2)};
233
+ // 1. Positional args first, in spec-declared order. Undefined values
234
+ // are skipped \u2014 commander will surface missing-required errors.
235
+ for (const key of positional) {
236
+ const v = userArgs[key];
237
+ if (v === undefined || v === null) continue;
238
+ argv.push(String(v));
239
+ }
123
240
 
124
- export const CLI_COMMANDS = ${JSON.stringify(cliCommands, null, 2)};
241
+ // 2. Remaining keys become --flag or --flag value.
242
+ const positionalSet = new Set(positional);
243
+ for (const [k, v] of Object.entries(userArgs)) {
244
+ if (positionalSet.has(k)) continue;
245
+ if (v === undefined || v === null) continue;
246
+ if (typeof v === 'boolean') {
247
+ if (v) argv.push(\`--\${k}\`);
248
+ } else {
249
+ argv.push(\`--\${k}\`, String(v));
250
+ }
251
+ }
125
252
 
126
- export function getToolByName(name: string) {
127
- return SPEC_TOOLS.find(t => t.name === name);
253
+ return new Promise(resolve => {
254
+ const child = spawn('specverse', argv, { cwd: cwd || process.cwd(), env: process.env });
255
+ let stdout = '';
256
+ let stderr = '';
257
+ child.stdout.on('data', d => { stdout += d.toString(); });
258
+ child.stderr.on('data', d => { stderr += d.toString(); });
259
+ child.on('close', code => resolve({ stdout, stderr, code }));
260
+ child.on('error', err => resolve({ stdout, stderr: stderr + '\\n' + err.message, code: -1 }));
261
+ });
262
+ }
263
+ `;
128
264
  }
265
+ function generateTools(tools) {
266
+ return `/**
267
+ * Tool registry \u2014 one MCP tool per specverse CLI subcommand.
268
+ * Generated from the spec at realize time.
269
+ */
270
+ import { runCli } from './cli-runner.js';
129
271
 
130
- export function getResourceByUri(uri: string) {
131
- return SPEC_RESOURCES.find(r => r.uri === uri);
272
+ export interface Tool {
273
+ name: string;
274
+ description: string;
275
+ cliArgs: string[];
276
+ positional: string[];
277
+ inputSchema: Record<string, any>;
278
+ }
279
+
280
+ export const TOOLS: Tool[] = ${JSON.stringify(tools, null, 2)};
281
+
282
+ const BY_NAME = new Map<string, Tool>(TOOLS.map(t => [t.name, t]));
283
+
284
+ export async function callTool(name: string, args: Record<string, any>) {
285
+ const tool = BY_NAME.get(name);
286
+ if (!tool) {
287
+ return {
288
+ isError: true,
289
+ content: [{ type: 'text', text: \`Unknown tool: \${name}\` }],
290
+ };
291
+ }
292
+ const { stdout, stderr, code } = await runCli(tool.cliArgs, tool.positional, args);
293
+ if (code !== 0) {
294
+ return {
295
+ isError: true,
296
+ content: [{ type: 'text', text: \`specverse \${tool.cliArgs.join(' ')} exited \${code}\\n\\n\${stderr || stdout}\` }],
297
+ };
298
+ }
299
+ return {
300
+ content: [{ type: 'text', text: stdout || '(no output)' }],
301
+ };
132
302
  }
133
303
  `;
134
304
  }
135
- function copyRecursive(src, dest) {
136
- for (const entry of readdirSync(src)) {
137
- const srcPath = join(src, entry);
138
- const destPath = join(dest, entry);
139
- if (statSync(srcPath).isDirectory()) {
140
- if (!existsSync(destPath)) mkdirSync(destPath, { recursive: true });
141
- copyRecursive(srcPath, destPath);
142
- } else {
143
- copyFileSync(srcPath, destPath);
144
- }
305
+ function generateResources() {
306
+ return `/**
307
+ * Resource registry \u2014 exposes the live SpecVerse schema + user guide as
308
+ * MCP resources. Read lazily from @specverse/entities at request time so
309
+ * the contents always match the installed entities version.
310
+ */
311
+ import { readFileSync } from 'fs';
312
+ import { createRequire } from 'module';
313
+ import { dirname, join } from 'path';
314
+
315
+ const require = createRequire(import.meta.url);
316
+
317
+ export interface Resource {
318
+ uri: string;
319
+ name: string;
320
+ description: string;
321
+ mimeType: string;
322
+ resolve: () => { text: string; mimeType: string };
323
+ }
324
+
325
+ function resolveEntitiesFile(relative: string): string {
326
+ const pkg = require.resolve('@specverse/entities/package.json');
327
+ return join(dirname(pkg), relative);
328
+ }
329
+
330
+ export const RESOURCES: Resource[] = [
331
+ {
332
+ uri: 'specverse://schema',
333
+ name: 'SpecVerse JSON Schema',
334
+ description: 'JSON Schema (draft 2020-12) for validating .specly files \u2014 composed from entity-module fragments.',
335
+ mimeType: 'application/json',
336
+ resolve: () => ({
337
+ text: readFileSync(resolveEntitiesFile('schema/SPECVERSE-SCHEMA.json'), 'utf8'),
338
+ mimeType: 'application/json',
339
+ }),
340
+ },
341
+ {
342
+ uri: 'specverse://guide',
343
+ name: 'SpecVerse Complete Guide',
344
+ description: 'The canonical user guide \u2014 spec language, convention patterns, CLI reference.',
345
+ mimeType: 'text/markdown',
346
+ resolve: () => ({
347
+ text: readFileSync(resolveEntitiesFile('schema/SPECVERSE-COMPLETE-GUIDE.md'), 'utf8'),
348
+ mimeType: 'text/markdown',
349
+ }),
350
+ },
351
+ ];
352
+
353
+ const BY_URI = new Map<string, Resource>(RESOURCES.map(r => [r.uri, r]));
354
+
355
+ export async function readResource(uri: string) {
356
+ const resource = BY_URI.get(uri);
357
+ if (!resource) {
358
+ throw new Error(\`Unknown resource URI: \${uri}\`);
145
359
  }
360
+ const { text, mimeType } = resource.resolve();
361
+ return {
362
+ contents: [{ uri, mimeType, text }],
363
+ };
364
+ }
365
+ `;
146
366
  }
147
367
  export {
148
368
  generateMCPServer as default