@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.
- package/assets/examples/10-api/README.md +3 -3
- package/assets/prompts/core/README.md +1 -1
- package/dist/inference/core/rule-engine.d.ts +0 -12
- package/dist/inference/core/rule-engine.d.ts.map +1 -1
- package/dist/inference/core/rule-engine.js +99 -968
- package/dist/inference/core/rule-engine.js.map +1 -1
- package/dist/inference/core/template-helpers.d.ts +56 -0
- package/dist/inference/core/template-helpers.d.ts.map +1 -0
- package/dist/inference/core/template-helpers.js +87 -0
- package/dist/inference/core/template-helpers.js.map +1 -0
- package/dist/inference/logical/generators/service-generator.d.ts.map +1 -1
- package/dist/inference/logical/generators/service-generator.js +0 -4
- package/dist/inference/logical/generators/service-generator.js.map +1 -1
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
- package/dist/libs/instance-factories/scaffolding/templates/generic/package-json-generator.js +1 -1
- package/dist/libs/instance-factories/tools/README.md +1 -1
- package/dist/libs/instance-factories/tools/mcp.yaml +1 -1
- package/dist/libs/instance-factories/tools/templates/mcp/mcp-server-generator.js +336 -116
- package/dist/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.js +172 -8
- package/dist/libs/instance-factories/tools/vscode.yaml +1 -1
- package/libs/instance-factories/cli/templates/commander/command-generator.ts +27 -5
- package/libs/instance-factories/scaffolding/templates/generic/package-json-generator.ts +10 -6
- package/libs/instance-factories/tools/README.md +1 -1
- package/libs/instance-factories/tools/mcp.yaml +1 -1
- package/libs/instance-factories/tools/templates/mcp/mcp-server-generator.ts +386 -141
- package/libs/instance-factories/tools/templates/vscode/static/extension.ts +9 -2
- package/libs/instance-factories/tools/templates/vscode/vscode-extension-generator.ts +246 -10
- package/libs/instance-factories/tools/vscode.yaml +1 -1
- package/package.json +5 -4
- package/libs/instance-factories/tools/templates/mcp/static/docs/DEPLOYMENT_GUIDE.md +0 -630
- package/libs/instance-factories/tools/templates/mcp/static/docs/HYBRID_RESOURCE_SYSTEM.md +0 -330
- package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/EXTENSION_DEPLOYMENT.md +0 -552
- package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/LOCAL_DEPLOYMENT.md +0 -164
- package/libs/instance-factories/tools/templates/mcp/static/docs/deployments/WEB_DEPLOYMENT.md +0 -247
- package/libs/instance-factories/tools/templates/mcp/static/package.json +0 -94
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-enterprise.js +0 -284
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-extension.js +0 -139
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-local.js +0 -74
- package/libs/instance-factories/tools/templates/mcp/static/scripts/build-web.js +0 -156
- package/libs/instance-factories/tools/templates/mcp/static/scripts/copy-canonical-files.js +0 -41
- package/libs/instance-factories/tools/templates/mcp/static/scripts/test-deployments.js +0 -259
- package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-resources.js +0 -231
- package/libs/instance-factories/tools/templates/mcp/static/scripts/test-hybrid-simple.js +0 -196
- package/libs/instance-factories/tools/templates/mcp/static/src/controllers/MCPServerController.ts +0 -293
- package/libs/instance-factories/tools/templates/mcp/static/src/events/EventEmitter.ts +0 -90
- package/libs/instance-factories/tools/templates/mcp/static/src/index.ts +0 -24
- package/libs/instance-factories/tools/templates/mcp/static/src/interfaces/ResourceProvider.ts +0 -15
- package/libs/instance-factories/tools/templates/mcp/static/src/models/LibrarySuggestion.ts +0 -106
- package/libs/instance-factories/tools/templates/mcp/static/src/models/SpecVerseResource.ts +0 -75
- package/libs/instance-factories/tools/templates/mcp/static/src/server/mcp-server.ts +0 -239
- package/libs/instance-factories/tools/templates/mcp/static/src/services/CLIProxyService.ts +0 -1501
- package/libs/instance-factories/tools/templates/mcp/static/src/services/EmbeddedResourcesAdapter.ts +0 -211
- package/libs/instance-factories/tools/templates/mcp/static/src/services/EntityModuleService.ts +0 -308
- package/libs/instance-factories/tools/templates/mcp/static/src/services/HybridResourcesProvider.ts +0 -210
- package/libs/instance-factories/tools/templates/mcp/static/src/services/LibraryToolsService.ts +0 -356
- package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorBridge.ts +0 -522
- package/libs/instance-factories/tools/templates/mcp/static/src/services/OrchestratorToolsService.ts +0 -530
- package/libs/instance-factories/tools/templates/mcp/static/src/services/PromptToolsService.ts +0 -594
- package/libs/instance-factories/tools/templates/mcp/static/src/services/ResourcesProviderService.ts +0 -170
- package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.init.test.ts +0 -544
- package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/CLIProxyService.test.ts +0 -189
- package/libs/instance-factories/tools/templates/mcp/static/src/tests/unit/ResourcesProviderService.test.ts +0 -89
- package/libs/instance-factories/tools/templates/mcp/static/src/types/index.ts +0 -110
- package/libs/instance-factories/tools/templates/mcp/static/tsconfig.json +0 -28
- package/libs/instance-factories/tools/templates/vscode/static/schemas/specverse-v3-schema.json +0 -4279
- /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
|
|
2
|
-
import { join
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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
|
-
${
|
|
20
|
+
${cliCommands.length} tools (one per CLI command), 2 resources (schema + docs)`;
|
|
28
21
|
}
|
|
29
|
-
function
|
|
30
|
-
const
|
|
22
|
+
function extractCLITools(spec) {
|
|
23
|
+
const out = [];
|
|
31
24
|
const components = spec?.components || {};
|
|
32
|
-
const componentList = Array.isArray(components) ? components : Object.
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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
|
|
206
|
+
function generateCliRunner() {
|
|
113
207
|
return `/**
|
|
114
|
-
*
|
|
115
|
-
* Generated from SpecVerse self-specification.
|
|
208
|
+
* Thin specverse CLI runner.
|
|
116
209
|
*
|
|
117
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
131
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|