@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.
- 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/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts +9 -7
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.d.ts.map +1 -1
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js +27 -9
- package/dist/inference/ui-contracts/rules/lifecycle-state-visible-in-detail.js.map +1 -1
- package/dist/libs/instance-factories/cli/templates/commander/command-generator.js +27 -5
- 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 +342 -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/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 +392 -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,374 @@
|
|
|
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
|
+
// 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
|
|
212
|
+
function generateCliRunner() {
|
|
113
213
|
return `/**
|
|
114
|
-
*
|
|
115
|
-
* Generated from SpecVerse self-specification.
|
|
214
|
+
* Thin specverse CLI runner.
|
|
116
215
|
*
|
|
117
|
-
*
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
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
|
|
131
|
-
|
|
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
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|