@specverse/engines 4.3.5 → 5.0.1

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 (68) 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/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts +9 -7
  15. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts.map +1 -1
  16. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js +27 -9
  17. package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js.map +1 -1
  18. package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
  19. package/dist/libs/instance-factories/tools/README.md +1 -1
  20. package/dist/libs/instance-factories/tools/mcp.yaml +1 -1
  21. package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +342 -116
  22. package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +172 -8
  23. package/dist/libs/instance-factories/tools/vscode.yaml +1 -1
  24. package/libs/instance-factories/cli/templates/commander/command-generator.ts +27 -5
  25. package/libs/instance-factories/tools/README.md +1 -1
  26. package/libs/instance-factories/tools/mcp.yaml +1 -1
  27. package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +392 -141
  28. package/libs/instance-factories/tools/templates/vscode/static/extension.ts +9 -2
  29. package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +246 -10
  30. package/libs/instance-factories/tools/vscode.yaml +1 -1
  31. package/package.json +5 -4
  32. package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +0 -630
  33. package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +0 -330
  34. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +0 -552
  35. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +0 -164
  36. package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +0 -247
  37. package/libs/instance-factories/tools/templates/mcp/static/package.json +0 -94
  38. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +0 -284
  39. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +0 -139
  40. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +0 -74
  41. package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +0 -156
  42. package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +0 -41
  43. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +0 -259
  44. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +0 -231
  45. package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +0 -196
  46. package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +0 -293
  47. package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +0 -90
  48. package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +0 -24
  49. package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +0 -15
  50. package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +0 -106
  51. package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +0 -75
  52. package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +0 -239
  53. package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +0 -1501
  54. package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +0 -211
  55. package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +0 -308
  56. package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +0 -210
  57. package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +0 -356
  58. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +0 -522
  59. package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +0 -530
  60. package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +0 -594
  61. package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +0 -170
  62. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +0 -544
  63. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +0 -189
  64. package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +0 -89
  65. package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +0 -110
  66. package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +0 -28
  67. package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +0 -4279
  68. /package/libs/instance-factories/tools/templates/vscode/static/themes/{specverse-complete-theme.json → specverse-dark-theme.json} +0 -0
@@ -1,148 +1,374 @@
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
+ // Under moduleResolution: 'bundler' tsc's auto-inclusion of
147
+ // @types/* packages from node_modules/@types is environment-
148
+ // dependent — Node 20 on CI doesn't pick up @types/node while
149
+ // Node 24 locally does. Listing it explicitly forces consistent
150
+ // resolution everywhere.
151
+ types: ["node"]
152
+ },
153
+ include: ["src/**/*"],
154
+ exclude: ["node_modules", "dist"]
155
+ }, null, 2) + "\n";
156
+ }
157
+ function generateServer(displayName, version) {
158
+ return `#!/usr/bin/env node
159
+ /**
160
+ * SpecVerse MCP Server \u2014 stdio transport.
161
+ *
162
+ * Wires the MCP protocol to the generated tool registry + live resources.
163
+ * No handwritten business logic \u2014 everything is derived from the spec
164
+ * (tools from CLI commands) or from @specverse/entities (schema + docs).
165
+ */
166
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
167
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
168
+ import {
169
+ ListToolsRequestSchema,
170
+ CallToolRequestSchema,
171
+ ListResourcesRequestSchema,
172
+ ReadResourceRequestSchema,
173
+ } from '@modelcontextprotocol/sdk/types.js';
174
+ import { TOOLS, callTool } from './tools.js';
175
+ import { RESOURCES, readResource } from './resources.js';
176
+
177
+ const server = new Server(
178
+ { name: ${JSON.stringify(displayName)}, version: ${JSON.stringify(version)} },
179
+ { capabilities: { tools: {}, resources: {} } },
180
+ );
181
+
182
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
183
+ tools: TOOLS.map(t => ({
184
+ name: t.name,
185
+ description: t.description,
186
+ inputSchema: t.inputSchema,
187
+ })),
188
+ }));
189
+
190
+ server.setRequestHandler(CallToolRequestSchema, async (req: { params: { name: string; arguments?: Record<string, unknown> } }) => {
191
+ const { name, arguments: args } = req.params;
192
+ return callTool(name, args ?? {});
193
+ });
194
+
195
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
196
+ resources: RESOURCES.map(r => ({
197
+ uri: r.uri,
198
+ name: r.name,
199
+ description: r.description,
200
+ mimeType: r.mimeType,
201
+ })),
202
+ }));
203
+
204
+ server.setRequestHandler(ReadResourceRequestSchema, async (req: { params: { uri: string } }) => {
205
+ return readResource(req.params.uri);
206
+ });
207
+
208
+ const transport = new StdioServerTransport();
209
+ await server.connect(transport);
210
+ `;
111
211
  }
112
- function generateToolRegistry(tools, resources, cliCommands) {
212
+ function generateCliRunner() {
113
213
  return `/**
114
- * Spec-Driven MCP Registry
115
- * Generated from SpecVerse self-specification.
214
+ * Thin specverse CLI runner.
116
215
  *
117
- * Tools, resources, and CLI command mappings derived from the spec.
216
+ * Each MCP tool maps to a specverse CLI command + a set of user-supplied
217
+ * arguments. The tool registry tells us which argument keys are positional
218
+ * (declared in the spec as \`positional: true\`) \u2014 those get emitted in
219
+ * order before any flags, matching the CLI's expected argv shape. The
220
+ * specverse binary must be on PATH (the AI client that launches the
221
+ * server inherits env from its parent).
118
222
  */
223
+ import { spawn } from 'child_process';
224
+
225
+ export interface CliResult {
226
+ stdout: string;
227
+ stderr: string;
228
+ code: number | null;
229
+ }
119
230
 
120
- export const SPEC_TOOLS = ${JSON.stringify(tools, null, 2)};
231
+ export function runCli(
232
+ cliArgs: string[],
233
+ positional: string[],
234
+ userArgs: Record<string, any>,
235
+ cwd?: string,
236
+ ): Promise<CliResult> {
237
+ const argv = [...cliArgs];
121
238
 
122
- export const SPEC_RESOURCES = ${JSON.stringify(resources, null, 2)};
239
+ // 1. Positional args first, in spec-declared order. Undefined values
240
+ // are skipped \u2014 commander will surface missing-required errors.
241
+ for (const key of positional) {
242
+ const v = userArgs[key];
243
+ if (v === undefined || v === null) continue;
244
+ argv.push(String(v));
245
+ }
123
246
 
124
- export const CLI_COMMANDS = ${JSON.stringify(cliCommands, null, 2)};
247
+ // 2. Remaining keys become --flag or --flag value.
248
+ const positionalSet = new Set(positional);
249
+ for (const [k, v] of Object.entries(userArgs)) {
250
+ if (positionalSet.has(k)) continue;
251
+ if (v === undefined || v === null) continue;
252
+ if (typeof v === 'boolean') {
253
+ if (v) argv.push(\`--\${k}\`);
254
+ } else {
255
+ argv.push(\`--\${k}\`, String(v));
256
+ }
257
+ }
125
258
 
126
- export function getToolByName(name: string) {
127
- return SPEC_TOOLS.find(t => t.name === name);
259
+ return new Promise<CliResult>(resolve => {
260
+ const child = spawn('specverse', argv, { cwd: cwd || process.cwd(), env: process.env });
261
+ let stdout = '';
262
+ let stderr = '';
263
+ child.stdout.on('data', (d: Buffer) => { stdout += d.toString(); });
264
+ child.stderr.on('data', (d: Buffer) => { stderr += d.toString(); });
265
+ child.on('close', (code: number | null) => resolve({ stdout, stderr, code }));
266
+ child.on('error', (err: Error) => resolve({ stdout, stderr: stderr + '\\n' + err.message, code: -1 }));
267
+ });
268
+ }
269
+ `;
128
270
  }
271
+ function generateTools(tools) {
272
+ return `/**
273
+ * Tool registry \u2014 one MCP tool per specverse CLI subcommand.
274
+ * Generated from the spec at realize time.
275
+ */
276
+ import { runCli } from './cli-runner.js';
129
277
 
130
- export function getResourceByUri(uri: string) {
131
- return SPEC_RESOURCES.find(r => r.uri === uri);
278
+ export interface Tool {
279
+ name: string;
280
+ description: string;
281
+ cliArgs: string[];
282
+ positional: string[];
283
+ inputSchema: Record<string, any>;
284
+ }
285
+
286
+ export const TOOLS: Tool[] = ${JSON.stringify(tools, null, 2)};
287
+
288
+ const BY_NAME = new Map<string, Tool>(TOOLS.map(t => [t.name, t]));
289
+
290
+ export async function callTool(name: string, args: Record<string, any>) {
291
+ const tool = BY_NAME.get(name);
292
+ if (!tool) {
293
+ return {
294
+ isError: true,
295
+ content: [{ type: 'text', text: \`Unknown tool: \${name}\` }],
296
+ };
297
+ }
298
+ const { stdout, stderr, code } = await runCli(tool.cliArgs, tool.positional, args);
299
+ if (code !== 0) {
300
+ return {
301
+ isError: true,
302
+ content: [{ type: 'text', text: \`specverse \${tool.cliArgs.join(' ')} exited \${code}\\n\\n\${stderr || stdout}\` }],
303
+ };
304
+ }
305
+ return {
306
+ content: [{ type: 'text', text: stdout || '(no output)' }],
307
+ };
132
308
  }
133
309
  `;
134
310
  }
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
- }
311
+ function generateResources() {
312
+ return `/**
313
+ * Resource registry \u2014 exposes the live SpecVerse schema + user guide as
314
+ * MCP resources. Read lazily from @specverse/entities at request time so
315
+ * the contents always match the installed entities version.
316
+ */
317
+ import { readFileSync } from 'fs';
318
+ import { createRequire } from 'module';
319
+ import { dirname, join } from 'path';
320
+
321
+ const require = createRequire(import.meta.url);
322
+
323
+ export interface Resource {
324
+ uri: string;
325
+ name: string;
326
+ description: string;
327
+ mimeType: string;
328
+ resolve: () => { text: string; mimeType: string };
329
+ }
330
+
331
+ function resolveEntitiesFile(relative: string): string {
332
+ const pkg = require.resolve('@specverse/entities/package.json');
333
+ return join(dirname(pkg), relative);
334
+ }
335
+
336
+ export const RESOURCES: Resource[] = [
337
+ {
338
+ uri: 'specverse://schema',
339
+ name: 'SpecVerse JSON Schema',
340
+ description: 'JSON Schema (draft 2020-12) for validating .specly files \u2014 composed from entity-module fragments.',
341
+ mimeType: 'application/json',
342
+ resolve: () => ({
343
+ text: readFileSync(resolveEntitiesFile('schema/SPECVERSE-SCHEMA.json'), 'utf8'),
344
+ mimeType: 'application/json',
345
+ }),
346
+ },
347
+ {
348
+ uri: 'specverse://guide',
349
+ name: 'SpecVerse Complete Guide',
350
+ description: 'The canonical user guide \u2014 spec language, convention patterns, CLI reference.',
351
+ mimeType: 'text/markdown',
352
+ resolve: () => ({
353
+ text: readFileSync(resolveEntitiesFile('schema/SPECVERSE-COMPLETE-GUIDE.md'), 'utf8'),
354
+ mimeType: 'text/markdown',
355
+ }),
356
+ },
357
+ ];
358
+
359
+ const BY_URI = new Map<string, Resource>(RESOURCES.map(r => [r.uri, r]));
360
+
361
+ export async function readResource(uri: string) {
362
+ const resource = BY_URI.get(uri);
363
+ if (!resource) {
364
+ throw new Error(\`Unknown resource URI: \${uri}\`);
145
365
  }
366
+ const { text, mimeType } = resource.resolve();
367
+ return {
368
+ contents: [{ uri, mimeType, text }],
369
+ };
370
+ }
371
+ `;
146
372
  }
147
373
  export {
148
374
  generateMCPServer as default