@agiflowai/scaffold-mcp 0.6.0 → 1.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 (30) hide show
  1. package/README.md +11 -71
  2. package/dist/ScaffoldConfigLoader-CI0T6zdG.js +142 -0
  3. package/dist/{ScaffoldConfigLoader-DzcV5a_c.cjs → ScaffoldConfigLoader-DQMCLVGD.cjs} +1 -1
  4. package/dist/ScaffoldConfigLoader-DhthV6xq.js +3 -0
  5. package/dist/ScaffoldService-B3En_m4t.cjs +3 -0
  6. package/dist/{ScaffoldService-BgFWAOLQ.cjs → ScaffoldService-BwDmXt83.cjs} +17 -8
  7. package/dist/ScaffoldService-CJ3vNmAj.js +3 -0
  8. package/dist/ScaffoldService-DB7-Cyod.js +293 -0
  9. package/dist/TemplateService-BZRt3NI8.cjs +3 -0
  10. package/dist/TemplateService-CiZJA06s.js +79 -0
  11. package/dist/TemplateService-DropYdp8.js +3 -0
  12. package/dist/VariableReplacementService-BAwTGv_R.js +3 -0
  13. package/dist/{VariableReplacementService-YUpL5nAC.cjs → VariableReplacementService-CroHkMha.cjs} +1 -1
  14. package/dist/{VariableReplacementService-ClshNY_C.cjs → VariableReplacementService-D0QnWKUW.cjs} +2 -2
  15. package/dist/VariableReplacementService-DRxd9ILB.js +66 -0
  16. package/dist/cli.cjs +779 -0
  17. package/dist/cli.d.cts +1 -0
  18. package/dist/cli.d.ts +1 -0
  19. package/dist/cli.js +774 -0
  20. package/dist/index.cjs +38 -3208
  21. package/dist/index.d.cts +814 -0
  22. package/dist/index.d.ts +815 -0
  23. package/dist/index.js +137 -0
  24. package/dist/stdio-Bxn4A1IU.js +2073 -0
  25. package/dist/stdio-TGsG8akc.cjs +2178 -0
  26. package/package.json +19 -5
  27. package/dist/ScaffoldService-BvD9WvRi.cjs +0 -3
  28. package/dist/TemplateService-B5EZjPB0.cjs +0 -3
  29. /package/dist/{ScaffoldConfigLoader-1Pcv9cxm.cjs → ScaffoldConfigLoader-BrmvENTo.cjs} +0 -0
  30. /package/dist/{TemplateService-_KpkoLfZ.cjs → TemplateService-DRubcvS9.cjs} +0 -0
@@ -0,0 +1,2073 @@
1
+ import { ScaffoldConfigLoader } from "./ScaffoldConfigLoader-CI0T6zdG.js";
2
+ import { ScaffoldService } from "./ScaffoldService-DB7-Cyod.js";
3
+ import { TemplateService } from "./TemplateService-CiZJA06s.js";
4
+ import { VariableReplacementService } from "./VariableReplacementService-DRxd9ILB.js";
5
+ import { ProjectConfigResolver, log } from "@agiflowai/aicode-utils";
6
+ import * as path$1 from "node:path";
7
+ import path from "node:path";
8
+ import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
9
+ import * as fs$1 from "fs-extra";
10
+ import fs from "fs-extra";
11
+ import * as yaml$1 from "js-yaml";
12
+ import yaml from "js-yaml";
13
+ import { z } from "zod";
14
+ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
15
+ import { randomUUID } from "node:crypto";
16
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
17
+ import express from "express";
18
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
19
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
20
+
21
+ //#region src/services/FileSystemService.ts
22
+ var FileSystemService = class {
23
+ async pathExists(path$2) {
24
+ return fs.pathExists(path$2);
25
+ }
26
+ async readFile(path$2, encoding = "utf8") {
27
+ return fs.readFile(path$2, encoding);
28
+ }
29
+ async readJson(path$2) {
30
+ return fs.readJson(path$2);
31
+ }
32
+ async writeFile(path$2, content, encoding = "utf8") {
33
+ return fs.writeFile(path$2, content, encoding);
34
+ }
35
+ async ensureDir(path$2) {
36
+ return fs.ensureDir(path$2);
37
+ }
38
+ async copy(src, dest) {
39
+ return fs.copy(src, dest);
40
+ }
41
+ async readdir(path$2) {
42
+ return fs.readdir(path$2);
43
+ }
44
+ async stat(path$2) {
45
+ return fs.stat(path$2);
46
+ }
47
+ };
48
+
49
+ //#endregion
50
+ //#region src/services/BoilerplateService.ts
51
+ var BoilerplateService = class {
52
+ templatesPath;
53
+ templateService;
54
+ scaffoldService;
55
+ constructor(templatesPath) {
56
+ this.templatesPath = templatesPath;
57
+ this.templateService = new TemplateService();
58
+ const fileSystemService = new FileSystemService();
59
+ this.scaffoldService = new ScaffoldService(fileSystemService, new ScaffoldConfigLoader(fileSystemService, this.templateService), new VariableReplacementService(fileSystemService, this.templateService), templatesPath);
60
+ }
61
+ /**
62
+ * Scans all scaffold.yaml files and returns available boilerplates
63
+ */
64
+ async listBoilerplates() {
65
+ const boilerplates = [];
66
+ const templateDirs = await this.discoverTemplateDirectories();
67
+ for (const templatePath of templateDirs) {
68
+ const scaffoldYamlPath = path$1.join(this.templatesPath, templatePath, "scaffold.yaml");
69
+ if (fs$1.existsSync(scaffoldYamlPath)) try {
70
+ const scaffoldContent = fs$1.readFileSync(scaffoldYamlPath, "utf8");
71
+ const scaffoldConfig = yaml$1.load(scaffoldContent);
72
+ if (scaffoldConfig.boilerplate) for (const boilerplate of scaffoldConfig.boilerplate) {
73
+ if (!boilerplate.targetFolder) {
74
+ log.warn(`Skipping boilerplate '${boilerplate.name}' in ${templatePath}: targetFolder is required in scaffold.yaml`);
75
+ continue;
76
+ }
77
+ boilerplates.push({
78
+ name: boilerplate.name,
79
+ description: boilerplate.description,
80
+ instruction: boilerplate.instruction,
81
+ variables_schema: boilerplate.variables_schema,
82
+ template_path: templatePath,
83
+ target_folder: boilerplate.targetFolder,
84
+ includes: boilerplate.includes
85
+ });
86
+ }
87
+ } catch (error) {
88
+ log.warn(`Failed to load scaffold.yaml for ${templatePath}:`, error);
89
+ }
90
+ }
91
+ return { boilerplates };
92
+ }
93
+ /**
94
+ * Dynamically discovers template directories by finding all directories
95
+ * that contain both package.json and scaffold.yaml files
96
+ */
97
+ async discoverTemplateDirectories() {
98
+ const templateDirs = [];
99
+ const findTemplates = (dir, baseDir = "") => {
100
+ if (!fs$1.existsSync(dir)) return;
101
+ const items = fs$1.readdirSync(dir);
102
+ const hasPackageJson = items.includes("package.json") || items.includes("package.json.liquid");
103
+ const hasScaffoldYaml = items.includes("scaffold.yaml");
104
+ if (hasPackageJson && hasScaffoldYaml) templateDirs.push(baseDir);
105
+ for (const item of items) {
106
+ const itemPath = path$1.join(dir, item);
107
+ if (fs$1.statSync(itemPath).isDirectory() && !item.startsWith(".") && item !== "node_modules") findTemplates(itemPath, baseDir ? path$1.join(baseDir, item) : item);
108
+ }
109
+ };
110
+ findTemplates(this.templatesPath);
111
+ return templateDirs;
112
+ }
113
+ /**
114
+ * Executes a specific boilerplate with provided variables
115
+ */
116
+ async useBoilerplate(request) {
117
+ let { boilerplateName, variables, monolith, targetFolderOverride } = request;
118
+ if (monolith === void 0) try {
119
+ const config = await ProjectConfigResolver.resolveProjectConfig(process.cwd());
120
+ monolith = config.type === "monolith";
121
+ log.info(`Auto-detected project type: ${config.type}`);
122
+ } catch (_error) {
123
+ monolith = false;
124
+ log.info("No project configuration found, defaulting to monorepo mode");
125
+ }
126
+ if (monolith && !boilerplateName) try {
127
+ boilerplateName = (await ProjectConfigResolver.resolveProjectConfig(process.cwd())).sourceTemplate;
128
+ log.info(`Using boilerplate from toolkit.yaml: ${boilerplateName}`);
129
+ } catch (error) {
130
+ return {
131
+ success: false,
132
+ message: `Failed to read boilerplate name from toolkit.yaml: ${error instanceof Error ? error.message : String(error)}`
133
+ };
134
+ }
135
+ if (!boilerplateName) return {
136
+ success: false,
137
+ message: "Missing required parameter: boilerplateName"
138
+ };
139
+ const boilerplateList = await this.listBoilerplates();
140
+ const boilerplate = boilerplateList.boilerplates.find((b) => b.name === boilerplateName);
141
+ if (!boilerplate) return {
142
+ success: false,
143
+ message: `Boilerplate '${boilerplateName}' not found. Available boilerplates: ${boilerplateList.boilerplates.map((b) => b.name).join(", ")}`
144
+ };
145
+ const validationResult = this.validateBoilerplateVariables(boilerplate, variables);
146
+ if (!validationResult.isValid) return {
147
+ success: false,
148
+ message: `Validation failed: ${validationResult.errors.join(", ")}`
149
+ };
150
+ const packageName = variables.packageName || variables.appName;
151
+ if (!packageName) return {
152
+ success: false,
153
+ message: "Missing required parameter: packageName or appName"
154
+ };
155
+ const folderName = packageName.includes("/") ? packageName.split("/")[1] : packageName;
156
+ const targetFolder = targetFolderOverride || (monolith ? "." : boilerplate.target_folder);
157
+ const projectNameForPath = monolith ? "" : folderName;
158
+ try {
159
+ const result = await this.scaffoldService.useBoilerplate({
160
+ projectName: projectNameForPath,
161
+ packageName,
162
+ targetFolder,
163
+ templateFolder: boilerplate.template_path,
164
+ boilerplateName,
165
+ variables: {
166
+ ...variables,
167
+ packageName,
168
+ appName: folderName,
169
+ sourceTemplate: boilerplate.template_path
170
+ }
171
+ });
172
+ if (!result.success) return result;
173
+ if (monolith) await ProjectConfigResolver.createToolkitYaml(boilerplate.template_path);
174
+ else {
175
+ const projectPath = path$1.join(targetFolder, folderName);
176
+ await ProjectConfigResolver.createProjectJson(projectPath, folderName, boilerplate.template_path);
177
+ }
178
+ return {
179
+ success: result.success,
180
+ message: result.message,
181
+ warnings: result.warnings,
182
+ createdFiles: result.createdFiles,
183
+ existingFiles: result.existingFiles
184
+ };
185
+ } catch (error) {
186
+ return {
187
+ success: false,
188
+ message: `Failed to scaffold boilerplate: ${error instanceof Error ? error.message : String(error)}`
189
+ };
190
+ }
191
+ }
192
+ /**
193
+ * Gets a specific boilerplate configuration by name with optional variable rendering
194
+ */
195
+ async getBoilerplate(name, variables) {
196
+ const boilerplate = (await this.listBoilerplates()).boilerplates.find((b) => b.name === name);
197
+ if (!boilerplate) return null;
198
+ if (variables && this.templateService.containsTemplateVariables(boilerplate.instruction)) return {
199
+ ...boilerplate,
200
+ instruction: this.templateService.renderString(boilerplate.instruction, variables)
201
+ };
202
+ return boilerplate;
203
+ }
204
+ /**
205
+ * Processes boilerplate instruction with template service
206
+ */
207
+ processBoilerplateInstruction(instruction, variables) {
208
+ if (this.templateService.containsTemplateVariables(instruction)) return this.templateService.renderString(instruction, variables);
209
+ return instruction;
210
+ }
211
+ /**
212
+ * Validates boilerplate variables against schema using Zod
213
+ */
214
+ validateBoilerplateVariables(boilerplate, variables) {
215
+ const errors = [];
216
+ try {
217
+ jsonSchemaToZod(boilerplate.variables_schema).parse(variables);
218
+ return {
219
+ isValid: true,
220
+ errors: []
221
+ };
222
+ } catch (error) {
223
+ if (error instanceof z.ZodError) {
224
+ const zodErrors = error.errors.map((err) => {
225
+ return `${err.path.length > 0 ? err.path.join(".") : "root"}: ${err.message}`;
226
+ });
227
+ errors.push(...zodErrors);
228
+ } else errors.push(`Validation error: ${error instanceof Error ? error.message : String(error)}`);
229
+ return {
230
+ isValid: false,
231
+ errors
232
+ };
233
+ }
234
+ }
235
+ };
236
+
237
+ //#endregion
238
+ //#region src/services/BoilerplateGeneratorService.ts
239
+ /**
240
+ * Service for generating boilerplate configurations in scaffold.yaml files
241
+ */
242
+ var BoilerplateGeneratorService = class {
243
+ templatesPath;
244
+ constructor(templatesPath) {
245
+ this.templatesPath = templatesPath;
246
+ }
247
+ /**
248
+ * Custom YAML dumper that forces literal block style (|) for description and instruction fields
249
+ */
250
+ dumpYamlWithLiteralBlocks(config) {
251
+ const LiteralBlockType = new yaml$1.Type("tag:yaml.org,2002:str", {
252
+ kind: "scalar",
253
+ construct: (data) => data,
254
+ represent: (data) => {
255
+ return data;
256
+ },
257
+ defaultStyle: "|"
258
+ });
259
+ const LITERAL_SCHEMA = yaml$1.DEFAULT_SCHEMA.extend([LiteralBlockType]);
260
+ const processedConfig = this.processConfigForLiteralBlocks(config);
261
+ return yaml$1.dump(processedConfig, {
262
+ schema: LITERAL_SCHEMA,
263
+ indent: 2,
264
+ lineWidth: -1,
265
+ noRefs: true,
266
+ sortKeys: false,
267
+ styles: { "!!str": "literal" },
268
+ replacer: (key, value) => {
269
+ if ((key === "description" || key === "instruction") && typeof value === "string") return value;
270
+ return value;
271
+ }
272
+ });
273
+ }
274
+ /**
275
+ * Process config to ensure description and instruction use literal block style
276
+ */
277
+ processConfigForLiteralBlocks(config) {
278
+ const processed = JSON.parse(JSON.stringify(config));
279
+ if (processed.boilerplate) processed.boilerplate = processed.boilerplate.map((bp) => {
280
+ const newBp = { ...bp };
281
+ if (newBp.description && typeof newBp.description === "string") newBp.description = this.ensureMultilineFormat(newBp.description);
282
+ if (newBp.instruction && typeof newBp.instruction === "string") newBp.instruction = this.ensureMultilineFormat(newBp.instruction);
283
+ return newBp;
284
+ });
285
+ if (processed.features) processed.features = processed.features.map((feature) => {
286
+ const newFeature = { ...feature };
287
+ if (newFeature.description && typeof newFeature.description === "string") newFeature.description = this.ensureMultilineFormat(newFeature.description);
288
+ if (newFeature.instruction && typeof newFeature.instruction === "string") newFeature.instruction = this.ensureMultilineFormat(newFeature.instruction);
289
+ return newFeature;
290
+ });
291
+ return processed;
292
+ }
293
+ /**
294
+ * Ensure string is properly formatted for YAML literal blocks
295
+ */
296
+ ensureMultilineFormat(text) {
297
+ return text.trim();
298
+ }
299
+ /**
300
+ * Generate or update a boilerplate configuration in scaffold.yaml
301
+ */
302
+ async generateBoilerplate(options) {
303
+ const { templateName, boilerplateName, description, instruction, targetFolder, variables, includes = [] } = options;
304
+ const templatePath = path$1.join(this.templatesPath, templateName);
305
+ await fs$1.ensureDir(templatePath);
306
+ const scaffoldYamlPath = path$1.join(templatePath, "scaffold.yaml");
307
+ let scaffoldConfig = {};
308
+ if (await fs$1.pathExists(scaffoldYamlPath)) {
309
+ const yamlContent$1 = await fs$1.readFile(scaffoldYamlPath, "utf-8");
310
+ scaffoldConfig = yaml$1.load(yamlContent$1);
311
+ }
312
+ if (!scaffoldConfig.boilerplate) scaffoldConfig.boilerplate = [];
313
+ if (scaffoldConfig.boilerplate.findIndex((b) => b.name === boilerplateName) !== -1) return {
314
+ success: false,
315
+ message: `Boilerplate '${boilerplateName}' already exists in ${scaffoldYamlPath}`
316
+ };
317
+ const requiredVars = variables.filter((v) => v.required).map((v) => v.name);
318
+ const boilerplateDefinition = {
319
+ name: boilerplateName,
320
+ targetFolder,
321
+ description,
322
+ variables_schema: {
323
+ type: "object",
324
+ properties: variables.reduce((acc, v) => {
325
+ acc[v.name] = {
326
+ type: v.type,
327
+ description: v.description
328
+ };
329
+ if (v.default !== void 0) acc[v.name].default = v.default;
330
+ return acc;
331
+ }, {}),
332
+ required: requiredVars,
333
+ additionalProperties: false
334
+ },
335
+ includes: includes.length > 0 ? includes : []
336
+ };
337
+ if (instruction) boilerplateDefinition.instruction = instruction;
338
+ scaffoldConfig.boilerplate.push(boilerplateDefinition);
339
+ const yamlContent = this.dumpYamlWithLiteralBlocks(scaffoldConfig);
340
+ await fs$1.writeFile(scaffoldYamlPath, yamlContent, "utf-8");
341
+ return {
342
+ success: true,
343
+ message: `Boilerplate '${boilerplateName}' added to ${scaffoldYamlPath}`,
344
+ templatePath,
345
+ scaffoldYamlPath
346
+ };
347
+ }
348
+ /**
349
+ * List all templates (directories in templates folder)
350
+ */
351
+ async listTemplates() {
352
+ return (await fs$1.readdir(this.templatesPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
353
+ }
354
+ /**
355
+ * Check if a template exists
356
+ */
357
+ async templateExists(templateName) {
358
+ const templatePath = path$1.join(this.templatesPath, templateName);
359
+ return fs$1.pathExists(templatePath);
360
+ }
361
+ /**
362
+ * Create or update a template file for a boilerplate
363
+ */
364
+ async createTemplateFile(options) {
365
+ const { templateName, filePath, content, sourceFile, header } = options;
366
+ const templatePath = path$1.join(this.templatesPath, templateName);
367
+ if (!await fs$1.pathExists(templatePath)) return {
368
+ success: false,
369
+ message: `Template directory '${templateName}' does not exist at ${templatePath}`
370
+ };
371
+ let fileContent = content || "";
372
+ if (sourceFile) {
373
+ if (!await fs$1.pathExists(sourceFile)) return {
374
+ success: false,
375
+ message: `Source file '${sourceFile}' does not exist`
376
+ };
377
+ fileContent = await fs$1.readFile(sourceFile, "utf-8");
378
+ }
379
+ if (!fileContent && !sourceFile) return {
380
+ success: false,
381
+ message: "Either content or sourceFile must be provided"
382
+ };
383
+ const templateFilePath = filePath.endsWith(".liquid") ? filePath : `${filePath}.liquid`;
384
+ const fullPath = path$1.join(templatePath, templateFilePath);
385
+ await fs$1.ensureDir(path$1.dirname(fullPath));
386
+ let finalContent = fileContent;
387
+ if (header) finalContent = `${header}\n\n${fileContent}`;
388
+ await fs$1.writeFile(fullPath, finalContent, "utf-8");
389
+ return {
390
+ success: true,
391
+ message: "Template file created successfully",
392
+ filePath: templateFilePath,
393
+ fullPath
394
+ };
395
+ }
396
+ };
397
+
398
+ //#endregion
399
+ //#region src/tools/GenerateBoilerplateFileTool.ts
400
+ /**
401
+ * Tool to generate template files for boilerplates and features
402
+ */
403
+ var GenerateBoilerplateFileTool = class GenerateBoilerplateFileTool {
404
+ static TOOL_NAME = "generate-boilerplate-file";
405
+ boilerplateGeneratorService;
406
+ isMonolith;
407
+ constructor(templatesPath, isMonolith = false) {
408
+ this.boilerplateGeneratorService = new BoilerplateGeneratorService(templatesPath);
409
+ this.isMonolith = isMonolith;
410
+ }
411
+ /**
412
+ * Get the tool definition for MCP
413
+ */
414
+ getDefinition() {
415
+ const properties = {};
416
+ if (!this.isMonolith) properties.templateName = {
417
+ type: "string",
418
+ description: "Name of the template folder (must already exist)"
419
+ };
420
+ Object.assign(properties, {
421
+ filePath: {
422
+ type: "string",
423
+ description: "Path of the file to create within the template (e.g., \"package.json\", \"src/app/page.tsx\")"
424
+ },
425
+ content: {
426
+ type: "string",
427
+ description: `Content of the template file using Liquid template syntax.
428
+
429
+ LIQUID SYNTAX:
430
+ - Variables: {{ variableName }} - Replaced with actual values
431
+ - Conditionals: {% if condition %}...{% endif %} - Conditional rendering
432
+ - Else: {% if condition %}...{% else %}...{% endif %}
433
+ - Elsif: {% if condition %}...{% elsif other %}...{% endif %}
434
+ - Equality: {% if var == 'value' %}...{% endif %}
435
+
436
+ AVAILABLE FILTERS:
437
+ You can transform variables using these filters with the pipe (|) syntax:
438
+
439
+ Case Conversion:
440
+ - {{ name | camelCase }} - Convert to camelCase (myVariableName)
441
+ - {{ name | pascalCase }} - Convert to PascalCase (MyVariableName)
442
+ - {{ name | titleCase }} - Convert to TitleCase (alias for pascalCase)
443
+ - {{ name | kebabCase }} - Convert to kebab-case (my-variable-name)
444
+ - {{ name | snakeCase }} - Convert to snake_case (my_variable_name)
445
+ - {{ name | upperCase }} - Convert to UPPER_CASE (MY_VARIABLE_NAME)
446
+ - {{ name | lower }} or {{ name | downcase }} - Convert to lowercase
447
+ - {{ name | upper }} or {{ name | upcase }} - Convert to UPPERCASE
448
+
449
+ String Manipulation:
450
+ - {{ name | strip }} - Remove leading/trailing whitespace
451
+ - {{ name | replace: "old", "new" }} - Replace text (e.g., replace: "Tool", "")
452
+ - {{ name | pluralize }} - Add plural suffix (simple: book → books, class → classes)
453
+ - {{ name | singularize }} - Remove plural suffix (simple: books → book)
454
+
455
+ Chaining Filters:
456
+ - {{ toolName | downcase | replace: "tool", "" | strip }} - Combine multiple filters
457
+
458
+ Example with variables and conditionals:
459
+ {
460
+ "name": "{{ packageName }}",{% if withFeature %}
461
+ "feature": "enabled",{% endif %}
462
+ "dependencies": {
463
+ "core": "1.0.0"{% if withOptional %},
464
+ "optional": "2.0.0"{% endif %}
465
+ }
466
+ }
467
+
468
+ Example with filters:
469
+ export class {{ serviceName | pascalCase }} {
470
+ private {{ serviceName | camelCase }}: string;
471
+
472
+ constructor() {
473
+ this.{{ serviceName | camelCase }} = "{{ serviceName | kebabCase }}";
474
+ }
475
+ }
476
+
477
+ IMPORTANT - Keep content minimal and business-agnostic:
478
+ - Focus on structure and patterns, not specific business logic
479
+ - Use placeholder data and generic examples
480
+ - Include only essential boilerplate code
481
+ - Demonstrate the pattern, not a complete implementation
482
+ - Let AI fill in business-specific logic later
483
+
484
+ Example (good - minimal):
485
+ export function {{ functionName }}() {
486
+ // TODO: Implement logic
487
+ return null;
488
+ }
489
+
490
+ Example (bad - too specific):
491
+ export function calculateTax(income: number) {
492
+ const federalRate = 0.22;
493
+ const stateRate = 0.05;
494
+ return income * (federalRate + stateRate);
495
+ }`
496
+ },
497
+ sourceFile: {
498
+ type: "string",
499
+ description: "Optional: Path to a source file to copy and convert to a template"
500
+ },
501
+ header: {
502
+ type: "string",
503
+ description: `Optional: Header comment to add at the top of the file to provide AI hints about design patterns, coding standards, and best practices.
504
+
505
+ Example format for TypeScript/JavaScript files:
506
+ /**
507
+ * {{ componentName }} Component
508
+ *
509
+ * DESIGN PATTERNS:
510
+ * - Component pattern description
511
+ * - Architecture decisions
512
+ *
513
+ * CODING STANDARDS:
514
+ * - Naming conventions
515
+ * - Required elements
516
+ *
517
+ * AVOID:
518
+ * - Common pitfalls
519
+ * - Anti-patterns
520
+ */
521
+
522
+ The header helps AI understand and follow established patterns when working with generated code.`
523
+ }
524
+ });
525
+ const required = ["filePath"];
526
+ if (!this.isMonolith) required.unshift("templateName");
527
+ return {
528
+ name: GenerateBoilerplateFileTool.TOOL_NAME,
529
+ description: `Create or update template files for boilerplates or features in the specified template directory.
530
+
531
+ This tool:
532
+ - Creates template files with .liquid extension for variable substitution
533
+ - Supports creating nested directory structures
534
+ - Can create files from source files (copying and converting to templates)
535
+ - Validates that the template directory exists
536
+ - Works for both boilerplate includes and feature scaffold includes
537
+
538
+ IMPORTANT - Always add header comments:
539
+ - For code files (*.ts, *.tsx, *.js, *.jsx), ALWAYS include a header parameter with design patterns, coding standards, and things to avoid
540
+ - Headers help AI understand and follow established patterns when working with generated code
541
+ - Use the header parameter to document the architectural decisions and best practices
542
+
543
+ Use this after generate-boilerplate or generate-feature-scaffold to create the actual template files referenced in the includes array.`,
544
+ inputSchema: {
545
+ type: "object",
546
+ properties,
547
+ required,
548
+ additionalProperties: false
549
+ }
550
+ };
551
+ }
552
+ /**
553
+ * Execute the tool
554
+ */
555
+ async execute(args) {
556
+ try {
557
+ let { templateName } = args;
558
+ if (this.isMonolith && !templateName) try {
559
+ templateName = (await ProjectConfigResolver.resolveProjectConfig(process.cwd())).sourceTemplate;
560
+ } catch (error) {
561
+ return {
562
+ content: [{
563
+ type: "text",
564
+ text: `Failed to read template name from configuration: ${error instanceof Error ? error.message : String(error)}`
565
+ }],
566
+ isError: true
567
+ };
568
+ }
569
+ if (!templateName) return {
570
+ content: [{
571
+ type: "text",
572
+ text: "Missing required parameter: templateName"
573
+ }],
574
+ isError: true
575
+ };
576
+ const result = await this.boilerplateGeneratorService.createTemplateFile({
577
+ ...args,
578
+ templateName
579
+ });
580
+ if (!result.success) return {
581
+ content: [{
582
+ type: "text",
583
+ text: result.message
584
+ }],
585
+ isError: true
586
+ };
587
+ return { content: [{
588
+ type: "text",
589
+ text: JSON.stringify({
590
+ success: true,
591
+ message: result.message,
592
+ filePath: result.filePath,
593
+ fullPath: result.fullPath,
594
+ sourceFile: args.sourceFile || null
595
+ }, null, 2)
596
+ }] };
597
+ } catch (error) {
598
+ return {
599
+ content: [{
600
+ type: "text",
601
+ text: `Error creating template file: ${error instanceof Error ? error.message : String(error)}`
602
+ }],
603
+ isError: true
604
+ };
605
+ }
606
+ }
607
+ };
608
+
609
+ //#endregion
610
+ //#region src/tools/GenerateBoilerplateTool.ts
611
+ /**
612
+ * Tool to generate a new boilerplate configuration in scaffold.yaml
613
+ */
614
+ var GenerateBoilerplateTool = class GenerateBoilerplateTool {
615
+ static TOOL_NAME = "generate-boilerplate";
616
+ boilerplateGeneratorService;
617
+ isMonolith;
618
+ constructor(templatesPath, isMonolith = false) {
619
+ this.boilerplateGeneratorService = new BoilerplateGeneratorService(templatesPath);
620
+ this.isMonolith = isMonolith;
621
+ }
622
+ /**
623
+ * Get the tool definition for MCP
624
+ */
625
+ getDefinition() {
626
+ const properties = {};
627
+ if (!this.isMonolith) properties.templateName = {
628
+ type: "string",
629
+ description: "Name of the template folder (kebab-case, e.g., \"my-framework\")"
630
+ };
631
+ Object.assign(properties, {
632
+ boilerplateName: {
633
+ type: "string",
634
+ description: "Name of the boilerplate (kebab-case, e.g., \"scaffold-my-app\")"
635
+ },
636
+ targetFolder: {
637
+ type: "string",
638
+ description: "Target folder where projects will be created (e.g., \"apps\", \"packages\")"
639
+ },
640
+ description: {
641
+ type: "string",
642
+ description: `Detailed description of what this boilerplate creates and its key features.
643
+
644
+ STRUCTURE (3-5 sentences in multiple paragraphs):
645
+ - Paragraph 1: Core technology stack and primary value proposition
646
+ - Paragraph 2: Target use cases and ideal project types
647
+ - Paragraph 3: Key integrations or special features (if applicable)
648
+
649
+ Example: "A modern React SPA template powered by Vite for lightning-fast HMR, featuring TanStack Router for type-safe routing and TanStack Query for server state management.
650
+ Perfect for building data-driven dashboards, admin panels, and interactive web applications requiring client-side routing and real-time data synchronization.
651
+
652
+ Includes Agiflow Config Management System integration with systematic environment variable naming (VITE_{CATEGORY}_{SUBCATEGORY}_{PROPERTY}) and auto-generated configuration templates for cloud deployment."`
653
+ },
654
+ instruction: {
655
+ type: "string",
656
+ description: `Optional detailed instructions about the generated files, their purposes, and how to work with them.
657
+
658
+ STRUCTURE (Multi-section guide):
659
+
660
+ 1. **File purposes** section:
661
+ List each major file/directory with its purpose
662
+ Format: "- path/to/file: Description of what this file does"
663
+
664
+ 2. **How to use the scaffolded code** section:
665
+ Step-by-step workflows for common tasks
666
+ Format: Numbered list with specific examples
667
+ - How to add routes/pages
668
+ - How to fetch data
669
+ - How to handle authentication
670
+ - How to configure environment variables
671
+
672
+ 3. **Design patterns to follow** section:
673
+ Key architectural decisions and conventions
674
+ Format: "- Pattern Name: Explanation and when to use it"
675
+ - Routing patterns
676
+ - State management patterns
677
+ - Data fetching patterns
678
+ - Error handling patterns
679
+ - Performance optimization patterns
680
+
681
+ Example: "[Framework] application template with [key technologies].
682
+
683
+ File purposes:
684
+ - package.json: NPM package configuration with [framework] and dependencies
685
+ - src/main.tsx: Application entry point with [setup details]
686
+ - src/routes/: Route definitions following [pattern]
687
+ [... list all major files ...]
688
+
689
+ How to use the scaffolded code:
690
+ 1. Routes: Create new routes by [specific instructions with example]
691
+ 2. Data Fetching: Use [specific pattern] for [use case]
692
+ 3. Authentication: Use [specific components/modules] for user management
693
+ [... numbered steps for common tasks ...]
694
+
695
+ Design patterns to follow:
696
+ - File-based Routing: Use directory structure in src/routes/ to define URL paths
697
+ - Type-safe Routes: Leverage [framework] type inference for params
698
+ - State Management: Use [library] for server state, [library] for client state
699
+ [... list key patterns with explanations ...]"`
700
+ },
701
+ variables: {
702
+ type: "array",
703
+ description: "Array of variable definitions for the boilerplate",
704
+ items: {
705
+ type: "object",
706
+ properties: {
707
+ name: {
708
+ type: "string",
709
+ description: "Variable name (camelCase)"
710
+ },
711
+ description: {
712
+ type: "string",
713
+ description: "Variable description"
714
+ },
715
+ type: {
716
+ type: "string",
717
+ enum: [
718
+ "string",
719
+ "number",
720
+ "boolean"
721
+ ],
722
+ description: "Variable type"
723
+ },
724
+ required: {
725
+ type: "boolean",
726
+ description: "Whether this variable is required"
727
+ },
728
+ default: { description: "Optional default value for the variable" }
729
+ },
730
+ required: [
731
+ "name",
732
+ "description",
733
+ "type",
734
+ "required"
735
+ ]
736
+ }
737
+ },
738
+ includes: {
739
+ type: "array",
740
+ description: `Array of specific file paths to include in the boilerplate (highly recommended to list explicitly).
741
+
742
+ Examples:
743
+ - ["package.json", "tsconfig.json", "src/index.ts"] - Explicit file list (recommended)
744
+ - ["**/*"] - Include all files (not recommended, too broad)
745
+
746
+ Best practices:
747
+ - List each file explicitly for clarity and control
748
+ - Use relative paths from the template root
749
+ - Include configuration files (package.json, tsconfig.json, etc.)
750
+ - Include source files (src/index.ts, src/app/page.tsx, etc.)
751
+ - Avoid wildcards unless you have a good reason
752
+
753
+ See templates/nextjs-15/scaffold.yaml for a good example of explicit file listing.`,
754
+ items: { type: "string" }
755
+ }
756
+ });
757
+ const required = [
758
+ "boilerplateName",
759
+ "description",
760
+ "variables"
761
+ ];
762
+ if (!this.isMonolith) {
763
+ required.unshift("templateName");
764
+ required.push("targetFolder");
765
+ }
766
+ return {
767
+ name: GenerateBoilerplateTool.TOOL_NAME,
768
+ description: `Add a new boilerplate configuration to a template's scaffold.yaml file.
769
+
770
+ This tool:
771
+ - Creates or updates scaffold.yaml in the specified template directory
772
+ - Adds a boilerplate entry with proper schema following the nextjs-15 pattern
773
+ - Validates the boilerplate name doesn't already exist
774
+ - Creates the template directory if it doesn't exist
775
+
776
+ Use this to add custom boilerplate configurations for frameworks not yet supported or for your specific project needs.`,
777
+ inputSchema: {
778
+ type: "object",
779
+ properties,
780
+ required,
781
+ additionalProperties: false
782
+ }
783
+ };
784
+ }
785
+ /**
786
+ * Execute the tool
787
+ */
788
+ async execute(args) {
789
+ try {
790
+ let { templateName, targetFolder } = args;
791
+ if (this.isMonolith && !templateName) try {
792
+ templateName = (await ProjectConfigResolver.resolveProjectConfig(process.cwd())).sourceTemplate;
793
+ } catch (error) {
794
+ return {
795
+ content: [{
796
+ type: "text",
797
+ text: `Failed to read template name from configuration: ${error instanceof Error ? error.message : String(error)}`
798
+ }],
799
+ isError: true
800
+ };
801
+ }
802
+ if (this.isMonolith && !targetFolder) targetFolder = ".";
803
+ if (!templateName) return {
804
+ content: [{
805
+ type: "text",
806
+ text: "Missing required parameter: templateName"
807
+ }],
808
+ isError: true
809
+ };
810
+ if (!targetFolder) return {
811
+ content: [{
812
+ type: "text",
813
+ text: "Missing required parameter: targetFolder"
814
+ }],
815
+ isError: true
816
+ };
817
+ const result = await this.boilerplateGeneratorService.generateBoilerplate({
818
+ ...args,
819
+ templateName,
820
+ targetFolder
821
+ });
822
+ if (!result.success) return {
823
+ content: [{
824
+ type: "text",
825
+ text: result.message
826
+ }],
827
+ isError: true
828
+ };
829
+ return { content: [{
830
+ type: "text",
831
+ text: JSON.stringify({
832
+ success: true,
833
+ message: result.message,
834
+ templatePath: result.templatePath,
835
+ scaffoldYamlPath: result.scaffoldYamlPath,
836
+ nextSteps: [
837
+ "Use generate-boilerplate-file tool to create template files for the includes array",
838
+ "Customize the template files with Liquid variable placeholders ({{ variableName }})",
839
+ `Test with: scaffold-mcp boilerplate create ${args.boilerplateName} --vars '{"appName":"test"}'`
840
+ ]
841
+ }, null, 2)
842
+ }] };
843
+ } catch (error) {
844
+ return {
845
+ content: [{
846
+ type: "text",
847
+ text: `Error generating boilerplate: ${error instanceof Error ? error.message : String(error)}`
848
+ }],
849
+ isError: true
850
+ };
851
+ }
852
+ }
853
+ };
854
+
855
+ //#endregion
856
+ //#region src/services/ScaffoldGeneratorService.ts
857
+ /**
858
+ * Service for generating feature scaffold configurations in scaffold.yaml files
859
+ */
860
+ var ScaffoldGeneratorService = class {
861
+ templatesPath;
862
+ constructor(templatesPath) {
863
+ this.templatesPath = templatesPath;
864
+ }
865
+ /**
866
+ * Custom YAML dumper that forces literal block style (|) for description and instruction fields
867
+ */
868
+ dumpYamlWithLiteralBlocks(config) {
869
+ const LiteralBlockType = new yaml$1.Type("tag:yaml.org,2002:str", {
870
+ kind: "scalar",
871
+ construct: (data) => data,
872
+ represent: (data) => {
873
+ return data;
874
+ },
875
+ defaultStyle: "|"
876
+ });
877
+ const LITERAL_SCHEMA = yaml$1.DEFAULT_SCHEMA.extend([LiteralBlockType]);
878
+ const processedConfig = this.processConfigForLiteralBlocks(config);
879
+ return yaml$1.dump(processedConfig, {
880
+ schema: LITERAL_SCHEMA,
881
+ indent: 2,
882
+ lineWidth: -1,
883
+ noRefs: true,
884
+ sortKeys: false,
885
+ styles: { "!!str": "literal" },
886
+ replacer: (key, value) => {
887
+ if ((key === "description" || key === "instruction") && typeof value === "string") return value;
888
+ return value;
889
+ }
890
+ });
891
+ }
892
+ /**
893
+ * Process config to ensure description and instruction use literal block style
894
+ */
895
+ processConfigForLiteralBlocks(config) {
896
+ const processed = JSON.parse(JSON.stringify(config));
897
+ if (processed.boilerplate) processed.boilerplate = processed.boilerplate.map((bp) => {
898
+ const newBp = { ...bp };
899
+ if (newBp.description && typeof newBp.description === "string") newBp.description = this.ensureMultilineFormat(newBp.description);
900
+ if (newBp.instruction && typeof newBp.instruction === "string") newBp.instruction = this.ensureMultilineFormat(newBp.instruction);
901
+ return newBp;
902
+ });
903
+ if (processed.features) processed.features = processed.features.map((feature) => {
904
+ const newFeature = { ...feature };
905
+ if (newFeature.description && typeof newFeature.description === "string") newFeature.description = this.ensureMultilineFormat(newFeature.description);
906
+ if (newFeature.instruction && typeof newFeature.instruction === "string") newFeature.instruction = this.ensureMultilineFormat(newFeature.instruction);
907
+ return newFeature;
908
+ });
909
+ return processed;
910
+ }
911
+ /**
912
+ * Ensure string is properly formatted for YAML literal blocks
913
+ */
914
+ ensureMultilineFormat(text) {
915
+ return text.trim();
916
+ }
917
+ /**
918
+ * Generate or update a feature configuration in scaffold.yaml
919
+ */
920
+ async generateFeatureScaffold(options) {
921
+ const { templateName, featureName, description, instruction, variables, includes = [], patterns = [] } = options;
922
+ const templatePath = path$1.join(this.templatesPath, templateName);
923
+ await fs$1.ensureDir(templatePath);
924
+ const scaffoldYamlPath = path$1.join(templatePath, "scaffold.yaml");
925
+ let scaffoldConfig = {};
926
+ if (await fs$1.pathExists(scaffoldYamlPath)) {
927
+ const yamlContent$1 = await fs$1.readFile(scaffoldYamlPath, "utf-8");
928
+ scaffoldConfig = yaml$1.load(yamlContent$1);
929
+ }
930
+ if (!scaffoldConfig.features) scaffoldConfig.features = [];
931
+ if (scaffoldConfig.features.findIndex((f) => f.name === featureName) !== -1) return {
932
+ success: false,
933
+ message: `Feature '${featureName}' already exists in ${scaffoldYamlPath}`
934
+ };
935
+ const requiredVars = variables.filter((v) => v.required).map((v) => v.name);
936
+ const featureDefinition = {
937
+ name: featureName,
938
+ description,
939
+ variables_schema: {
940
+ type: "object",
941
+ properties: variables.reduce((acc, v) => {
942
+ acc[v.name] = {
943
+ type: v.type,
944
+ description: v.description
945
+ };
946
+ if (v.default !== void 0) acc[v.name].default = v.default;
947
+ return acc;
948
+ }, {}),
949
+ required: requiredVars,
950
+ additionalProperties: false
951
+ },
952
+ includes: includes.length > 0 ? includes : []
953
+ };
954
+ if (instruction) featureDefinition.instruction = instruction;
955
+ if (patterns && patterns.length > 0) featureDefinition.patterns = patterns;
956
+ scaffoldConfig.features.push(featureDefinition);
957
+ const yamlContent = this.dumpYamlWithLiteralBlocks(scaffoldConfig);
958
+ await fs$1.writeFile(scaffoldYamlPath, yamlContent, "utf-8");
959
+ return {
960
+ success: true,
961
+ message: `Feature '${featureName}' added to ${scaffoldYamlPath}`,
962
+ templatePath,
963
+ scaffoldYamlPath
964
+ };
965
+ }
966
+ /**
967
+ * List all templates (directories in templates folder)
968
+ */
969
+ async listTemplates() {
970
+ return (await fs$1.readdir(this.templatesPath, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
971
+ }
972
+ /**
973
+ * Check if a template exists
974
+ */
975
+ async templateExists(templateName) {
976
+ const templatePath = path$1.join(this.templatesPath, templateName);
977
+ return fs$1.pathExists(templatePath);
978
+ }
979
+ };
980
+
981
+ //#endregion
982
+ //#region src/tools/GenerateFeatureScaffoldTool.ts
983
+ /**
984
+ * Tool to generate a new feature scaffold configuration in scaffold.yaml
985
+ */
986
+ var GenerateFeatureScaffoldTool = class GenerateFeatureScaffoldTool {
987
+ static TOOL_NAME = "generate-feature-scaffold";
988
+ scaffoldGeneratorService;
989
+ isMonolith;
990
+ constructor(templatesPath, isMonolith = false) {
991
+ this.scaffoldGeneratorService = new ScaffoldGeneratorService(templatesPath);
992
+ this.isMonolith = isMonolith;
993
+ }
994
+ /**
995
+ * Get the tool definition for MCP
996
+ */
997
+ getDefinition() {
998
+ const properties = {};
999
+ if (!this.isMonolith) properties.templateName = {
1000
+ type: "string",
1001
+ description: "Name of the template folder (kebab-case, e.g., \"nextjs-15\")"
1002
+ };
1003
+ Object.assign(properties, {
1004
+ featureName: {
1005
+ type: "string",
1006
+ description: "Name of the feature (kebab-case, e.g., \"scaffold-nextjs-page\")"
1007
+ },
1008
+ description: {
1009
+ type: "string",
1010
+ description: `Detailed description of what this feature creates and its key capabilities.
1011
+
1012
+ STRUCTURE (2-3 sentences):
1013
+ - Sentence 1: What type of code it generates (component, page, service, etc.)
1014
+ - Sentence 2: Key features or capabilities included
1015
+ - Sentence 3: Primary use cases or when to use it
1016
+
1017
+ Example: "Generate a new service class for TypeScript libraries following best practices. Creates a service class with interface, implementation, and unit tests. Perfect for creating reusable service modules with dependency injection patterns."`
1018
+ },
1019
+ instruction: {
1020
+ type: "string",
1021
+ description: `Optional detailed instructions about the generated files, their purposes, and how to work with them.
1022
+
1023
+ STRUCTURE (Concise multi-aspect guide):
1024
+
1025
+ 1. **Pattern explanation**: Describe the architectural pattern used
1026
+ 2. **File organization**: Where files should be placed
1027
+ 3. **Naming conventions**: How to name things (PascalCase, camelCase, etc.)
1028
+ 4. **Usage guidelines**: How to use the generated code
1029
+ 5. **Testing approach**: How to test the feature
1030
+
1031
+ Example: "Services follow a class-based pattern with optional interface separation. The service class implements business logic and can be dependency injected. Place services in src/services/ directory. For services with interfaces, define the interface in src/types/interfaces/ for better separation of concerns. Service names should be PascalCase and end with 'Service' suffix. Write comprehensive unit tests for all public methods."`
1032
+ },
1033
+ variables: {
1034
+ type: "array",
1035
+ description: "Array of variable definitions for the feature",
1036
+ items: {
1037
+ type: "object",
1038
+ properties: {
1039
+ name: {
1040
+ type: "string",
1041
+ description: "Variable name (camelCase)"
1042
+ },
1043
+ description: {
1044
+ type: "string",
1045
+ description: "Variable description"
1046
+ },
1047
+ type: {
1048
+ type: "string",
1049
+ enum: [
1050
+ "string",
1051
+ "number",
1052
+ "boolean"
1053
+ ],
1054
+ description: "Variable type"
1055
+ },
1056
+ required: {
1057
+ type: "boolean",
1058
+ description: "Whether this variable is required"
1059
+ },
1060
+ default: { description: "Optional default value for the variable" }
1061
+ },
1062
+ required: [
1063
+ "name",
1064
+ "description",
1065
+ "type",
1066
+ "required"
1067
+ ]
1068
+ }
1069
+ },
1070
+ includes: {
1071
+ type: "array",
1072
+ description: `Array of specific file paths to include in the feature (highly recommended to list explicitly).
1073
+
1074
+ Supports advanced syntax:
1075
+ - Basic: "src/app/page/page.tsx" - Always included
1076
+ - Conditional: "src/app/page/layout.tsx?withLayout=true" - Only included when withLayout variable is true
1077
+ - Multiple conditions: "file.tsx?withLayout=true&withTests=true" - Use & to combine conditions
1078
+ - Path mapping: "source.tsx->target/path.tsx" - Map source template file to different target path
1079
+ - Combined: "source.tsx->{{ pagePath }}/page.tsx?withPage=true" - Combine path mapping with variables and conditions
1080
+
1081
+ Examples:
1082
+ - ["src/components/Button.tsx", "src/components/Button.test.tsx"] - Explicit file list (recommended)
1083
+ - ["src/app/page/page.tsx", "src/app/page/layout.tsx?withLayout=true"] - Conditional include
1084
+ - ["template.tsx->src/app/{{ pagePath }}/page.tsx"] - Dynamic path with variables
1085
+
1086
+ Best practices:
1087
+ - List each file explicitly for clarity and control
1088
+ - Use relative paths from the template root
1089
+ - Use conditional includes with ?variableName=value for optional files
1090
+ - Use path mapping with -> when source and target paths differ
1091
+ - Use {{ variableName }} in target paths for dynamic file placement
1092
+ - Avoid wildcards unless you have a good reason`,
1093
+ items: { type: "string" }
1094
+ },
1095
+ patterns: {
1096
+ type: "array",
1097
+ description: `Optional array of glob patterns to match existing files that this feature works with.
1098
+
1099
+ Used to help identify where this feature can be applied in a project.
1100
+
1101
+ Examples:
1102
+ - ["src/app/**/page.tsx", "src/app/**/layout.tsx"] - Next.js app router files
1103
+ - ["src/components/**/*.tsx"] - React component files
1104
+ - ["src/services/**/*.ts"] - Service files
1105
+
1106
+ Best practices:
1107
+ - Use glob patterns that match the file types this feature works with
1108
+ - Keep patterns specific enough to be meaningful but broad enough to be useful
1109
+ - Consider both the feature's output and input files`,
1110
+ items: { type: "string" }
1111
+ }
1112
+ });
1113
+ const required = [
1114
+ "featureName",
1115
+ "description",
1116
+ "variables"
1117
+ ];
1118
+ if (!this.isMonolith) required.unshift("templateName");
1119
+ return {
1120
+ name: GenerateFeatureScaffoldTool.TOOL_NAME,
1121
+ description: `Add a new feature scaffold configuration to a template's scaffold.yaml file.
1122
+
1123
+ This tool:
1124
+ - Creates or updates scaffold.yaml in the specified template directory
1125
+ - Adds a feature entry with proper schema following the nextjs-15 pattern
1126
+ - Validates the feature name doesn't already exist
1127
+ - Creates the template directory if it doesn't exist
1128
+
1129
+ Use this to add custom feature scaffolds (pages, components, services, etc.) for frameworks not yet supported or for your specific project needs.`,
1130
+ inputSchema: {
1131
+ type: "object",
1132
+ properties,
1133
+ required,
1134
+ additionalProperties: false
1135
+ }
1136
+ };
1137
+ }
1138
+ /**
1139
+ * Execute the tool
1140
+ */
1141
+ async execute(args) {
1142
+ try {
1143
+ let { templateName } = args;
1144
+ if (this.isMonolith && !templateName) try {
1145
+ templateName = (await ProjectConfigResolver.resolveProjectConfig(process.cwd())).sourceTemplate;
1146
+ } catch (error) {
1147
+ return {
1148
+ content: [{
1149
+ type: "text",
1150
+ text: `Failed to read template name from configuration: ${error instanceof Error ? error.message : String(error)}`
1151
+ }],
1152
+ isError: true
1153
+ };
1154
+ }
1155
+ if (!templateName) return {
1156
+ content: [{
1157
+ type: "text",
1158
+ text: "Missing required parameter: templateName"
1159
+ }],
1160
+ isError: true
1161
+ };
1162
+ const result = await this.scaffoldGeneratorService.generateFeatureScaffold({
1163
+ ...args,
1164
+ templateName
1165
+ });
1166
+ if (!result.success) return {
1167
+ content: [{
1168
+ type: "text",
1169
+ text: result.message
1170
+ }],
1171
+ isError: true
1172
+ };
1173
+ return { content: [{
1174
+ type: "text",
1175
+ text: JSON.stringify({
1176
+ success: true,
1177
+ message: result.message,
1178
+ templatePath: result.templatePath,
1179
+ scaffoldYamlPath: result.scaffoldYamlPath,
1180
+ nextSteps: [
1181
+ "Use generate-boilerplate-file tool to create template files for the includes array",
1182
+ "Customize the template files with Liquid variable placeholders ({{ variableName }})",
1183
+ "Create the generator file if it uses custom logic (referenced in the generator field)",
1184
+ `Test with: scaffold-mcp feature create ${args.featureName} --vars '{"appName":"test"}'`
1185
+ ]
1186
+ }, null, 2)
1187
+ }] };
1188
+ } catch (error) {
1189
+ return {
1190
+ content: [{
1191
+ type: "text",
1192
+ text: `Error generating feature scaffold: ${error instanceof Error ? error.message : String(error)}`
1193
+ }],
1194
+ isError: true
1195
+ };
1196
+ }
1197
+ }
1198
+ };
1199
+
1200
+ //#endregion
1201
+ //#region src/instructions/tools/list-boilerplates/description.md?raw
1202
+ var description_default$2 = "{% if isMonolith %}\nNot available for monolith projects. Monolith uses a single template defined in `toolkit.yaml`.\n\nUse `list-scaffolding-methods` for available features instead.\n{% else %}\nLists all available project boilerplates for creating new applications, APIs, or packages in the monorepo.\n\nEach boilerplate includes:\n- Complete project template with starter files\n- Variable schema for customization\n- Target directory information (e.g., apps/, packages/)\n- Required and optional configuration options\n\nUse this FIRST when creating new projects to understand available templates and their requirements.\n{% endif %}\n";
1203
+
1204
+ //#endregion
1205
+ //#region src/tools/ListBoilerplatesTool.ts
1206
+ var ListBoilerplatesTool = class ListBoilerplatesTool {
1207
+ static TOOL_NAME = "list-boilerplates";
1208
+ boilerplateService;
1209
+ templateService;
1210
+ isMonolith;
1211
+ constructor(templatesPath, isMonolith = false) {
1212
+ this.boilerplateService = new BoilerplateService(templatesPath);
1213
+ this.templateService = new TemplateService();
1214
+ this.isMonolith = isMonolith;
1215
+ }
1216
+ /**
1217
+ * Get the tool definition for MCP
1218
+ */
1219
+ getDefinition() {
1220
+ const description = this.templateService.renderString(description_default$2, { isMonolith: this.isMonolith });
1221
+ return {
1222
+ name: ListBoilerplatesTool.TOOL_NAME,
1223
+ description: description.trim(),
1224
+ inputSchema: {
1225
+ type: "object",
1226
+ properties: {},
1227
+ additionalProperties: false
1228
+ }
1229
+ };
1230
+ }
1231
+ /**
1232
+ * Execute the tool
1233
+ */
1234
+ async execute(_args = {}) {
1235
+ try {
1236
+ const result = await this.boilerplateService.listBoilerplates();
1237
+ return { content: [{
1238
+ type: "text",
1239
+ text: JSON.stringify(result, null, 2)
1240
+ }] };
1241
+ } catch (error) {
1242
+ return {
1243
+ content: [{
1244
+ type: "text",
1245
+ text: `Error listing boilerplates: ${error instanceof Error ? error.message : String(error)}`
1246
+ }],
1247
+ isError: true
1248
+ };
1249
+ }
1250
+ }
1251
+ };
1252
+
1253
+ //#endregion
1254
+ //#region src/instructions/tools/list-scaffolding-methods/description.md?raw
1255
+ var description_default$1 = "Lists all available scaffolding methods (features) that can be added to an existing project{% if not isMonolith %} or for a specific template{% endif %}.\n\nThis tool:\n{% if isMonolith %}\n- Reads your project's sourceTemplate from toolkit.yaml at workspace root\n{% else %}\n- Reads the project's sourceTemplate from project.json (monorepo) or toolkit.yaml (monolith), OR\n- Directly uses the provided templateName to list available features\n{% endif %}\n- Returns available features for that template type\n- Provides variable schemas for each scaffolding method\n- Shows descriptions of what each method creates\n\nUse this FIRST when adding features to understand:\n- What scaffolding methods are available\n- What variables each method requires\n- What files/features will be generated\n\nExample methods might include:\n- Adding new React routes (for React apps)\n- Creating API endpoints (for backend projects)\n- Adding new components (for frontend projects)\n- Setting up database models (for API projects)\n";
1256
+
1257
+ //#endregion
1258
+ //#region src/services/ScaffoldingMethodsService.ts
1259
+ var ScaffoldingMethodsService = class {
1260
+ templateService;
1261
+ constructor(fileSystem, templatesRootPath) {
1262
+ this.fileSystem = fileSystem;
1263
+ this.templatesRootPath = templatesRootPath;
1264
+ this.templateService = new TemplateService();
1265
+ }
1266
+ async listScaffoldingMethods(projectPath) {
1267
+ const absoluteProjectPath = path.resolve(projectPath);
1268
+ const sourceTemplate = (await ProjectConfigResolver.resolveProjectConfig(absoluteProjectPath)).sourceTemplate;
1269
+ return this.listScaffoldingMethodsByTemplate(sourceTemplate);
1270
+ }
1271
+ async listScaffoldingMethodsByTemplate(templateName) {
1272
+ const templatePath = await this.findTemplatePath(templateName);
1273
+ if (!templatePath) throw new Error(`Template not found for sourceTemplate: ${templateName}`);
1274
+ const fullTemplatePath = path.join(this.templatesRootPath, templatePath);
1275
+ const scaffoldYamlPath = path.join(fullTemplatePath, "scaffold.yaml");
1276
+ if (!await this.fileSystem.pathExists(scaffoldYamlPath)) throw new Error(`scaffold.yaml not found at ${scaffoldYamlPath}`);
1277
+ const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
1278
+ const architectConfig = yaml.load(scaffoldContent);
1279
+ const methods = [];
1280
+ if (architectConfig.features && Array.isArray(architectConfig.features)) architectConfig.features.forEach((feature) => {
1281
+ const featureName = feature.name || `scaffold-${templateName}`;
1282
+ methods.push({
1283
+ name: featureName,
1284
+ description: feature.description || "",
1285
+ instruction: feature.instruction || "",
1286
+ variables_schema: feature.variables_schema || {
1287
+ type: "object",
1288
+ properties: {},
1289
+ required: [],
1290
+ additionalProperties: false
1291
+ },
1292
+ generator: feature.generator
1293
+ });
1294
+ });
1295
+ return {
1296
+ sourceTemplate: templateName,
1297
+ templatePath,
1298
+ methods
1299
+ };
1300
+ }
1301
+ /**
1302
+ * Gets scaffolding methods with instructions rendered using provided variables
1303
+ */
1304
+ async listScaffoldingMethodsWithVariables(projectPath, variables) {
1305
+ const result = await this.listScaffoldingMethods(projectPath);
1306
+ const processedMethods = result.methods.map((method) => ({
1307
+ ...method,
1308
+ instruction: method.instruction ? this.processScaffoldInstruction(method.instruction, variables) : void 0
1309
+ }));
1310
+ return {
1311
+ ...result,
1312
+ methods: processedMethods
1313
+ };
1314
+ }
1315
+ /**
1316
+ * Processes scaffold instruction with template service
1317
+ */
1318
+ processScaffoldInstruction(instruction, variables) {
1319
+ if (this.templateService.containsTemplateVariables(instruction)) return this.templateService.renderString(instruction, variables);
1320
+ return instruction;
1321
+ }
1322
+ async findTemplatePath(sourceTemplate) {
1323
+ const templateDirs = await this.discoverTemplateDirs();
1324
+ if (templateDirs.includes(sourceTemplate)) return sourceTemplate;
1325
+ for (const templateDir of templateDirs) {
1326
+ const templatePath = path.join(this.templatesRootPath, templateDir);
1327
+ const scaffoldYamlPath = path.join(templatePath, "scaffold.yaml");
1328
+ if (await this.fileSystem.pathExists(scaffoldYamlPath)) try {
1329
+ const scaffoldContent = await this.fileSystem.readFile(scaffoldYamlPath, "utf8");
1330
+ const architectConfig = yaml.load(scaffoldContent);
1331
+ if (architectConfig.boilerplate && Array.isArray(architectConfig.boilerplate)) {
1332
+ for (const boilerplate of architectConfig.boilerplate) if (boilerplate.name?.includes(sourceTemplate)) return templateDir;
1333
+ }
1334
+ } catch (error) {
1335
+ log.warn(`Failed to read scaffold.yaml at ${scaffoldYamlPath}:`, error);
1336
+ }
1337
+ }
1338
+ return null;
1339
+ }
1340
+ /**
1341
+ * Resolves the project path, handling both monorepo and monolith cases
1342
+ * Uses ProjectConfigResolver to find the correct workspace/project root
1343
+ */
1344
+ async resolveProjectPath(projectPath) {
1345
+ const absolutePath = path.resolve(projectPath);
1346
+ return (await ProjectConfigResolver.resolveProjectConfig(absolutePath)).workspaceRoot || absolutePath;
1347
+ }
1348
+ /**
1349
+ * Dynamically discovers all template directories
1350
+ * Supports both flat structure (templates/nextjs-15) and nested structure (templates/apps/nextjs-15)
1351
+ **/
1352
+ async discoverTemplateDirs() {
1353
+ const templateDirs = [];
1354
+ try {
1355
+ const items = await this.fileSystem.readdir(this.templatesRootPath);
1356
+ for (const item of items) {
1357
+ const itemPath = path.join(this.templatesRootPath, item);
1358
+ if (!(await this.fileSystem.stat(itemPath)).isDirectory()) continue;
1359
+ const scaffoldYamlPath = path.join(itemPath, "scaffold.yaml");
1360
+ if (await this.fileSystem.pathExists(scaffoldYamlPath)) {
1361
+ templateDirs.push(item);
1362
+ continue;
1363
+ }
1364
+ try {
1365
+ const subItems = await this.fileSystem.readdir(itemPath);
1366
+ for (const subItem of subItems) {
1367
+ const subItemPath = path.join(itemPath, subItem);
1368
+ if (!(await this.fileSystem.stat(subItemPath)).isDirectory()) continue;
1369
+ const subScaffoldYamlPath = path.join(subItemPath, "scaffold.yaml");
1370
+ if (await this.fileSystem.pathExists(subScaffoldYamlPath)) {
1371
+ const relativePath = path.join(item, subItem);
1372
+ templateDirs.push(relativePath);
1373
+ }
1374
+ }
1375
+ } catch (error) {
1376
+ log.warn(`Failed to read subdirectories in ${itemPath}:`, error);
1377
+ }
1378
+ }
1379
+ } catch (error) {
1380
+ log.warn(`Failed to read templates root directory ${this.templatesRootPath}:`, error);
1381
+ }
1382
+ return templateDirs;
1383
+ }
1384
+ async useScaffoldMethod(request) {
1385
+ const { projectPath, scaffold_feature_name, variables } = request;
1386
+ const absoluteProjectPath = await this.resolveProjectPath(projectPath);
1387
+ const scaffoldingMethods = await this.listScaffoldingMethods(absoluteProjectPath);
1388
+ const method = scaffoldingMethods.methods.find((m) => m.name === scaffold_feature_name);
1389
+ if (!method) {
1390
+ const availableMethods = scaffoldingMethods.methods.map((m) => m.name).join(", ");
1391
+ throw new Error(`Scaffold method '${scaffold_feature_name}' not found. Available methods: ${availableMethods}`);
1392
+ }
1393
+ const ScaffoldService$1 = (await import("./ScaffoldService-CJ3vNmAj.js")).ScaffoldService;
1394
+ const ScaffoldConfigLoader$1 = (await import("./ScaffoldConfigLoader-DhthV6xq.js")).ScaffoldConfigLoader;
1395
+ const VariableReplacementService$1 = (await import("./VariableReplacementService-BAwTGv_R.js")).VariableReplacementService;
1396
+ const TemplateService$1 = (await import("./TemplateService-DropYdp8.js")).TemplateService;
1397
+ const templateService = new TemplateService$1();
1398
+ const scaffoldConfigLoader = new ScaffoldConfigLoader$1(this.fileSystem, templateService);
1399
+ const variableReplacer = new VariableReplacementService$1(this.fileSystem, templateService);
1400
+ const scaffoldService = new ScaffoldService$1(this.fileSystem, scaffoldConfigLoader, variableReplacer, this.templatesRootPath);
1401
+ const projectName = path.basename(absoluteProjectPath);
1402
+ const result = await scaffoldService.useFeature({
1403
+ projectPath: absoluteProjectPath,
1404
+ templateFolder: scaffoldingMethods.templatePath,
1405
+ featureName: scaffold_feature_name,
1406
+ variables: {
1407
+ ...variables,
1408
+ appPath: absoluteProjectPath,
1409
+ appName: projectName
1410
+ }
1411
+ });
1412
+ if (!result.success) throw new Error(result.message);
1413
+ return {
1414
+ success: true,
1415
+ message: `
1416
+ Successfully scaffolded ${scaffold_feature_name} in ${projectPath}.
1417
+ Please follow this **instruction**: \n ${method.instruction}.
1418
+ -> Create or update the plan based on the instruction.
1419
+ `,
1420
+ warnings: result.warnings,
1421
+ createdFiles: result.createdFiles,
1422
+ existingFiles: result.existingFiles
1423
+ };
1424
+ }
1425
+ };
1426
+
1427
+ //#endregion
1428
+ //#region src/tools/ListScaffoldingMethodsTool.ts
1429
+ var ListScaffoldingMethodsTool = class ListScaffoldingMethodsTool {
1430
+ static TOOL_NAME = "list-scaffolding-methods";
1431
+ fileSystemService;
1432
+ scaffoldingMethodsService;
1433
+ templateService;
1434
+ isMonolith;
1435
+ constructor(templatesPath, isMonolith = false) {
1436
+ this.fileSystemService = new FileSystemService();
1437
+ this.scaffoldingMethodsService = new ScaffoldingMethodsService(this.fileSystemService, templatesPath);
1438
+ this.templateService = new TemplateService();
1439
+ this.isMonolith = isMonolith;
1440
+ }
1441
+ /**
1442
+ * Get the tool definition for MCP
1443
+ */
1444
+ getDefinition() {
1445
+ const description = this.templateService.renderString(description_default$1, { isMonolith: this.isMonolith });
1446
+ const properties = {};
1447
+ if (!this.isMonolith) {
1448
+ properties.projectPath = {
1449
+ type: "string",
1450
+ description: "Absolute path to the project directory (for monorepo: containing project.json; for monolith: workspace root with toolkit.yaml). Either projectPath or templateName is required."
1451
+ };
1452
+ properties.templateName = {
1453
+ type: "string",
1454
+ description: "Name of the template to list scaffolding methods for (e.g., \"nextjs-15\", \"typescript-mcp-package\"). Either projectPath or templateName is required."
1455
+ };
1456
+ }
1457
+ return {
1458
+ name: ListScaffoldingMethodsTool.TOOL_NAME,
1459
+ description: description.trim(),
1460
+ inputSchema: {
1461
+ type: "object",
1462
+ properties,
1463
+ additionalProperties: false
1464
+ }
1465
+ };
1466
+ }
1467
+ /**
1468
+ * Execute the tool
1469
+ */
1470
+ async execute(args) {
1471
+ try {
1472
+ const { projectPath, templateName } = args;
1473
+ let result;
1474
+ if (this.isMonolith) try {
1475
+ const resolvedTemplateName = (await ProjectConfigResolver.resolveProjectConfig(process.cwd())).sourceTemplate;
1476
+ result = await this.scaffoldingMethodsService.listScaffoldingMethodsByTemplate(resolvedTemplateName);
1477
+ } catch (error) {
1478
+ throw new Error(`Failed to read template name from configuration: ${error instanceof Error ? error.message : String(error)}`);
1479
+ }
1480
+ else {
1481
+ if (!projectPath && !templateName) throw new Error("Either projectPath or templateName must be provided");
1482
+ if (projectPath) result = await this.scaffoldingMethodsService.listScaffoldingMethods(projectPath);
1483
+ else result = await this.scaffoldingMethodsService.listScaffoldingMethodsByTemplate(templateName);
1484
+ }
1485
+ return { content: [{
1486
+ type: "text",
1487
+ text: JSON.stringify(result, null, 2)
1488
+ }] };
1489
+ } catch (error) {
1490
+ return {
1491
+ content: [{
1492
+ type: "text",
1493
+ text: `Error listing scaffolding methods: ${error instanceof Error ? error.message : String(error)}`
1494
+ }],
1495
+ isError: true
1496
+ };
1497
+ }
1498
+ }
1499
+ };
1500
+
1501
+ //#endregion
1502
+ //#region src/instructions/tools/use-boilerplate/description.md?raw
1503
+ var description_default = "{% if isMonolith %}\nThis tool is not available for monolith projects.\n\nMonolith projects use a single template specified in `toolkit.yaml` (sourceTemplate field). The template cannot be changed through this tool - it's determined by the workspace configuration.\n\nUse `list-scaffolding-methods` and `use-scaffold-method` to add features to your monolith project instead.\n{% else %}\nCreates a new project from a boilerplate template with the specified variables.\n\n**For Monorepo Projects Only:**\nThis tool creates new sub-projects (apps, packages) in your monorepo. Each project can use a different template.\n\nThis tool will:\n- Generate all necessary files from the selected boilerplate template\n- Replace template variables with provided values\n- Create the project in targetFolder/projectName (e.g., apps/my-new-app)\n- Set up initial configuration files (package.json, tsconfig.json, etc.)\n- Create project.json with sourceTemplate reference\n\nIMPORTANT:\n- Always call `list-boilerplates` first to get the exact variable schema\n- Follow the schema exactly - required fields must be provided\n- Use kebab-case for project names (e.g., \"my-new-app\", not \"MyNewApp\")\n- The tool will validate all variables against the schema before proceeding\n- Each new project can use a different boilerplate template\n{% endif %}\n";
1504
+
1505
+ //#endregion
1506
+ //#region src/tools/UseBoilerplateTool.ts
1507
+ var UseBoilerplateTool = class UseBoilerplateTool {
1508
+ static TOOL_NAME = "use-boilerplate";
1509
+ boilerplateService;
1510
+ templateService;
1511
+ isMonolith;
1512
+ constructor(templatesPath, isMonolith = false) {
1513
+ this.boilerplateService = new BoilerplateService(templatesPath);
1514
+ this.templateService = new TemplateService();
1515
+ this.isMonolith = isMonolith;
1516
+ }
1517
+ /**
1518
+ * Get the tool definition for MCP
1519
+ */
1520
+ getDefinition() {
1521
+ const description = this.templateService.renderString(description_default, { isMonolith: this.isMonolith });
1522
+ const properties = { variables: {
1523
+ type: "object",
1524
+ description: "Variables object matching the boilerplate's variables_schema exactly"
1525
+ } };
1526
+ if (!this.isMonolith) {
1527
+ properties.boilerplateName = {
1528
+ type: "string",
1529
+ description: "Exact name of the boilerplate to use (from list-boilerplates response)"
1530
+ };
1531
+ properties.targetFolderOverride = {
1532
+ type: "string",
1533
+ description: "Optional override for target folder. If not provided, uses boilerplate targetFolder (monorepo) or workspace root (monolith)"
1534
+ };
1535
+ }
1536
+ return {
1537
+ name: UseBoilerplateTool.TOOL_NAME,
1538
+ description: description.trim(),
1539
+ inputSchema: {
1540
+ type: "object",
1541
+ properties,
1542
+ required: this.isMonolith ? ["variables"] : ["boilerplateName", "variables"],
1543
+ additionalProperties: false
1544
+ }
1545
+ };
1546
+ }
1547
+ /**
1548
+ * Execute the tool
1549
+ */
1550
+ async execute(args) {
1551
+ try {
1552
+ const { boilerplateName, variables, targetFolderOverride } = args;
1553
+ const request = {
1554
+ boilerplateName,
1555
+ variables,
1556
+ targetFolderOverride,
1557
+ monolith: this.isMonolith
1558
+ };
1559
+ return { content: [{
1560
+ type: "text",
1561
+ text: `${(await this.boilerplateService.useBoilerplate(request)).message}
1562
+
1563
+ IMPORTANT - Next Steps:
1564
+ 1. READ the generated project files to understand their structure
1565
+ 2. Review the boilerplate configuration and understand what was created
1566
+ 3. If the project requires additional features, use list-scaffolding-methods to see available options
1567
+ 4. Install dependencies (pnpm install) before testing or building
1568
+ 5. Follow the project's README for setup instructions
1569
+
1570
+ The boilerplate provides a starting point - you may need to add features or customize the generated code based on the project requirements.`
1571
+ }] };
1572
+ } catch (error) {
1573
+ return {
1574
+ content: [{
1575
+ type: "text",
1576
+ text: `Error using boilerplate: ${error instanceof Error ? error.message : String(error)}`
1577
+ }],
1578
+ isError: true
1579
+ };
1580
+ }
1581
+ }
1582
+ };
1583
+
1584
+ //#endregion
1585
+ //#region src/tools/UseScaffoldMethodTool.ts
1586
+ var UseScaffoldMethodTool = class UseScaffoldMethodTool {
1587
+ static TOOL_NAME = "use-scaffold-method";
1588
+ fileSystemService;
1589
+ scaffoldingMethodsService;
1590
+ isMonolith;
1591
+ constructor(templatesPath, isMonolith = false) {
1592
+ this.fileSystemService = new FileSystemService();
1593
+ this.scaffoldingMethodsService = new ScaffoldingMethodsService(this.fileSystemService, templatesPath);
1594
+ this.isMonolith = isMonolith;
1595
+ }
1596
+ /**
1597
+ * Get the tool definition for MCP
1598
+ */
1599
+ getDefinition() {
1600
+ const properties = {
1601
+ scaffold_feature_name: {
1602
+ type: "string",
1603
+ description: "Exact name of the scaffold method to use (from list-scaffolding-methods response)"
1604
+ },
1605
+ variables: {
1606
+ type: "object",
1607
+ description: "Variables object matching the scaffold method's variables_schema exactly"
1608
+ }
1609
+ };
1610
+ if (!this.isMonolith) properties.projectPath = {
1611
+ type: "string",
1612
+ description: "Absolute path to the project directory (for monorepo: containing project.json; for monolith: workspace root with toolkit.yaml)"
1613
+ };
1614
+ return {
1615
+ name: UseScaffoldMethodTool.TOOL_NAME,
1616
+ description: `Generates and adds a specific feature to an existing project using a scaffolding method.
1617
+
1618
+ This tool will:
1619
+ - Generate files based on the selected scaffolding method
1620
+ - Replace template variables with provided values
1621
+ - Add files to the appropriate locations in the project
1622
+ - Follow the project's existing patterns and conventions
1623
+ - Update imports and exports as needed
1624
+
1625
+ IMPORTANT:
1626
+ - Always call \`list-scaffolding-methods\` first to see available methods and their schemas
1627
+ - Use the exact scaffold method name from the list response
1628
+ - Provide variables that match the method's variables_schema exactly
1629
+ - The tool validates all inputs before generating code
1630
+ `,
1631
+ inputSchema: {
1632
+ type: "object",
1633
+ properties,
1634
+ required: this.isMonolith ? ["scaffold_feature_name", "variables"] : [
1635
+ "projectPath",
1636
+ "scaffold_feature_name",
1637
+ "variables"
1638
+ ],
1639
+ additionalProperties: false
1640
+ }
1641
+ };
1642
+ }
1643
+ /**
1644
+ * Execute the tool
1645
+ */
1646
+ async execute(args) {
1647
+ try {
1648
+ const { projectPath, scaffold_feature_name, variables } = args;
1649
+ const resolvedProjectPath = this.isMonolith ? process.cwd() : projectPath;
1650
+ return { content: [{
1651
+ type: "text",
1652
+ text: `${(await this.scaffoldingMethodsService.useScaffoldMethod({
1653
+ projectPath: resolvedProjectPath,
1654
+ scaffold_feature_name,
1655
+ variables
1656
+ })).message}
1657
+
1658
+ IMPORTANT - Next Steps:
1659
+ 1. READ the generated files to understand their structure and template placeholders
1660
+ 2. IMPLEMENT the actual business logic according to the feature's purpose (replace TODOs and template variables)
1661
+ 3. REGISTER the feature in the appropriate files (e.g., import and register tools in server/index.ts, export from index.ts)
1662
+ 4. TEST the implementation to ensure it works correctly
1663
+ 5. Only after completing the implementation should you move to other tasks
1664
+
1665
+ Do not skip the implementation step - the scaffolded files contain templates that need actual code.`
1666
+ }] };
1667
+ } catch (error) {
1668
+ return {
1669
+ content: [{
1670
+ type: "text",
1671
+ text: `Error using scaffold method: ${error instanceof Error ? error.message : String(error)}`
1672
+ }],
1673
+ isError: true
1674
+ };
1675
+ }
1676
+ }
1677
+ };
1678
+
1679
+ //#endregion
1680
+ //#region src/tools/WriteToFileTool.ts
1681
+ var WriteToFileTool = class WriteToFileTool {
1682
+ static TOOL_NAME = "write-to-file";
1683
+ fileSystemService;
1684
+ constructor() {
1685
+ this.fileSystemService = new FileSystemService();
1686
+ }
1687
+ /**
1688
+ * Get the tool definition for MCP
1689
+ */
1690
+ getDefinition() {
1691
+ return {
1692
+ name: WriteToFileTool.TOOL_NAME,
1693
+ description: `Writes content to a file, creating the file and any necessary directories if they don't exist.
1694
+
1695
+ This tool will:
1696
+ - Create the target file if it doesn't exist
1697
+ - Create any necessary parent directories
1698
+ - Write the provided content to the file
1699
+ - Overwrite existing files with new content
1700
+
1701
+ Parameters:
1702
+ - file_path: Absolute or relative path to the target file
1703
+ - content: The content to write to the file`,
1704
+ inputSchema: {
1705
+ type: "object",
1706
+ properties: {
1707
+ file_path: {
1708
+ type: "string",
1709
+ description: "Path to the file to write (absolute or relative to current working directory)"
1710
+ },
1711
+ content: {
1712
+ type: "string",
1713
+ description: "Content to write to the file"
1714
+ }
1715
+ },
1716
+ required: ["file_path", "content"],
1717
+ additionalProperties: false
1718
+ }
1719
+ };
1720
+ }
1721
+ /**
1722
+ * Execute the tool
1723
+ */
1724
+ async execute(args) {
1725
+ try {
1726
+ const { file_path, content } = args;
1727
+ if (!file_path) throw new Error("Missing required parameter: file_path");
1728
+ if (content === void 0 || content === null) throw new Error("Missing required parameter: content");
1729
+ const resolvedPath = path.isAbsolute(file_path) ? file_path : path.resolve(process.cwd(), file_path);
1730
+ const dirPath = path.dirname(resolvedPath);
1731
+ await this.fileSystemService.ensureDir(dirPath);
1732
+ await this.fileSystemService.writeFile(resolvedPath, content);
1733
+ return { content: [{
1734
+ type: "text",
1735
+ text: `Successfully wrote content to file: ${resolvedPath}`
1736
+ }] };
1737
+ } catch (error) {
1738
+ return {
1739
+ content: [{
1740
+ type: "text",
1741
+ text: `Error writing to file: ${error instanceof Error ? error.message : String(error)}`
1742
+ }],
1743
+ isError: true
1744
+ };
1745
+ }
1746
+ }
1747
+ };
1748
+
1749
+ //#endregion
1750
+ //#region src/transports/http.ts
1751
+ /**
1752
+ * HTTP session manager
1753
+ */
1754
+ var HttpFullSessionManager = class {
1755
+ sessions = /* @__PURE__ */ new Map();
1756
+ getSession(sessionId) {
1757
+ return this.sessions.get(sessionId);
1758
+ }
1759
+ setSession(sessionId, transport, server) {
1760
+ this.sessions.set(sessionId, {
1761
+ transport,
1762
+ server
1763
+ });
1764
+ }
1765
+ deleteSession(sessionId) {
1766
+ const session = this.sessions.get(sessionId);
1767
+ if (session) session.server.close();
1768
+ this.sessions.delete(sessionId);
1769
+ }
1770
+ hasSession(sessionId) {
1771
+ return this.sessions.has(sessionId);
1772
+ }
1773
+ clear() {
1774
+ for (const session of this.sessions.values()) session.server.close();
1775
+ this.sessions.clear();
1776
+ }
1777
+ };
1778
+ /**
1779
+ * HTTP transport handler using Streamable HTTP (protocol version 2025-03-26)
1780
+ * Provides stateful session management with resumability support
1781
+ */
1782
+ var HttpTransportHandler = class {
1783
+ serverFactory;
1784
+ app;
1785
+ server = null;
1786
+ sessionManager;
1787
+ config;
1788
+ constructor(serverFactory, config) {
1789
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
1790
+ this.app = express();
1791
+ this.sessionManager = new HttpFullSessionManager();
1792
+ this.config = {
1793
+ mode: config.mode,
1794
+ port: config.port ?? 3e3,
1795
+ host: config.host ?? "localhost"
1796
+ };
1797
+ this.setupMiddleware();
1798
+ this.setupRoutes();
1799
+ }
1800
+ setupMiddleware() {
1801
+ this.app.use(express.json());
1802
+ }
1803
+ setupRoutes() {
1804
+ this.app.post("/mcp", async (req, res) => {
1805
+ await this.handlePostRequest(req, res);
1806
+ });
1807
+ this.app.get("/mcp", async (req, res) => {
1808
+ await this.handleGetRequest(req, res);
1809
+ });
1810
+ this.app.delete("/mcp", async (req, res) => {
1811
+ await this.handleDeleteRequest(req, res);
1812
+ });
1813
+ this.app.get("/health", (_req, res) => {
1814
+ res.json({
1815
+ status: "ok",
1816
+ transport: "http"
1817
+ });
1818
+ });
1819
+ }
1820
+ async handlePostRequest(req, res) {
1821
+ const sessionId = req.headers["mcp-session-id"];
1822
+ let transport;
1823
+ if (sessionId && this.sessionManager.hasSession(sessionId)) transport = this.sessionManager.getSession(sessionId).transport;
1824
+ else if (!sessionId && isInitializeRequest(req.body)) {
1825
+ const mcpServer = this.serverFactory();
1826
+ transport = new StreamableHTTPServerTransport({
1827
+ sessionIdGenerator: () => randomUUID(),
1828
+ enableJsonResponse: true,
1829
+ onsessioninitialized: (sessionId$1) => {
1830
+ this.sessionManager.setSession(sessionId$1, transport, mcpServer);
1831
+ }
1832
+ });
1833
+ transport.onclose = () => {
1834
+ if (transport.sessionId) this.sessionManager.deleteSession(transport.sessionId);
1835
+ };
1836
+ await mcpServer.connect(transport);
1837
+ } else {
1838
+ res.status(400).json({
1839
+ jsonrpc: "2.0",
1840
+ error: {
1841
+ code: -32e3,
1842
+ message: "Bad Request: No valid session ID provided"
1843
+ },
1844
+ id: null
1845
+ });
1846
+ return;
1847
+ }
1848
+ await transport.handleRequest(req, res, req.body);
1849
+ }
1850
+ async handleGetRequest(req, res) {
1851
+ const sessionId = req.headers["mcp-session-id"];
1852
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
1853
+ res.status(400).send("Invalid or missing session ID");
1854
+ return;
1855
+ }
1856
+ await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
1857
+ }
1858
+ async handleDeleteRequest(req, res) {
1859
+ const sessionId = req.headers["mcp-session-id"];
1860
+ if (!sessionId || !this.sessionManager.hasSession(sessionId)) {
1861
+ res.status(400).send("Invalid or missing session ID");
1862
+ return;
1863
+ }
1864
+ await this.sessionManager.getSession(sessionId).transport.handleRequest(req, res);
1865
+ this.sessionManager.deleteSession(sessionId);
1866
+ }
1867
+ async start() {
1868
+ return new Promise((resolve, reject) => {
1869
+ try {
1870
+ this.server = this.app.listen(this.config.port, this.config.host, () => {
1871
+ console.error(`Scaffolding MCP server started on http://${this.config.host}:${this.config.port}/mcp`);
1872
+ console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
1873
+ resolve();
1874
+ });
1875
+ this.server.on("error", (error) => {
1876
+ reject(error);
1877
+ });
1878
+ } catch (error) {
1879
+ reject(error);
1880
+ }
1881
+ });
1882
+ }
1883
+ async stop() {
1884
+ return new Promise((resolve, reject) => {
1885
+ if (this.server) {
1886
+ this.sessionManager.clear();
1887
+ this.server.close((err) => {
1888
+ if (err) reject(err);
1889
+ else {
1890
+ this.server = null;
1891
+ resolve();
1892
+ }
1893
+ });
1894
+ } else resolve();
1895
+ });
1896
+ }
1897
+ getPort() {
1898
+ return this.config.port;
1899
+ }
1900
+ getHost() {
1901
+ return this.config.host;
1902
+ }
1903
+ };
1904
+
1905
+ //#endregion
1906
+ //#region src/transports/sse.ts
1907
+ /**
1908
+ * Session manager for SSE transports
1909
+ */
1910
+ var SseSessionManager = class {
1911
+ sessions = /* @__PURE__ */ new Map();
1912
+ getSession(sessionId) {
1913
+ return this.sessions.get(sessionId)?.transport;
1914
+ }
1915
+ setSession(sessionId, transport, server) {
1916
+ this.sessions.set(sessionId, {
1917
+ transport,
1918
+ server
1919
+ });
1920
+ }
1921
+ deleteSession(sessionId) {
1922
+ const session = this.sessions.get(sessionId);
1923
+ if (session) session.server.close();
1924
+ this.sessions.delete(sessionId);
1925
+ }
1926
+ hasSession(sessionId) {
1927
+ return this.sessions.has(sessionId);
1928
+ }
1929
+ clear() {
1930
+ for (const session of this.sessions.values()) session.server.close();
1931
+ this.sessions.clear();
1932
+ }
1933
+ };
1934
+ /**
1935
+ * SSE (Server-Sent Events) transport handler
1936
+ * Legacy transport for backwards compatibility (protocol version 2024-11-05)
1937
+ * Uses separate endpoints: /sse for SSE stream (GET) and /messages for client messages (POST)
1938
+ */
1939
+ var SseTransportHandler = class {
1940
+ serverFactory;
1941
+ app;
1942
+ server = null;
1943
+ sessionManager;
1944
+ config;
1945
+ constructor(serverFactory, config) {
1946
+ this.serverFactory = typeof serverFactory === "function" ? serverFactory : () => serverFactory;
1947
+ this.app = express();
1948
+ this.sessionManager = new SseSessionManager();
1949
+ this.config = {
1950
+ mode: config.mode,
1951
+ port: config.port ?? 3e3,
1952
+ host: config.host ?? "localhost"
1953
+ };
1954
+ this.setupMiddleware();
1955
+ this.setupRoutes();
1956
+ }
1957
+ setupMiddleware() {
1958
+ this.app.use(express.json());
1959
+ }
1960
+ setupRoutes() {
1961
+ this.app.get("/sse", async (req, res) => {
1962
+ await this.handleSseConnection(req, res);
1963
+ });
1964
+ this.app.post("/messages", async (req, res) => {
1965
+ await this.handlePostMessage(req, res);
1966
+ });
1967
+ this.app.get("/health", (_req, res) => {
1968
+ res.json({
1969
+ status: "ok",
1970
+ transport: "sse"
1971
+ });
1972
+ });
1973
+ }
1974
+ async handleSseConnection(_req, res) {
1975
+ try {
1976
+ const mcpServer = this.serverFactory();
1977
+ const transport = new SSEServerTransport("/messages", res);
1978
+ this.sessionManager.setSession(transport.sessionId, transport, mcpServer);
1979
+ res.on("close", () => {
1980
+ this.sessionManager.deleteSession(transport.sessionId);
1981
+ });
1982
+ await mcpServer.connect(transport);
1983
+ console.error(`SSE session established: ${transport.sessionId}`);
1984
+ } catch (error) {
1985
+ console.error("Error handling SSE connection:", error);
1986
+ if (!res.headersSent) res.status(500).send("Internal Server Error");
1987
+ }
1988
+ }
1989
+ async handlePostMessage(req, res) {
1990
+ const sessionId = req.query.sessionId;
1991
+ if (!sessionId) {
1992
+ res.status(400).send("Missing sessionId query parameter");
1993
+ return;
1994
+ }
1995
+ const transport = this.sessionManager.getSession(sessionId);
1996
+ if (!transport) {
1997
+ res.status(404).send("No transport found for sessionId");
1998
+ return;
1999
+ }
2000
+ try {
2001
+ await transport.handlePostMessage(req, res, req.body);
2002
+ } catch (error) {
2003
+ console.error("Error handling post message:", error);
2004
+ if (!res.headersSent) res.status(500).send("Internal Server Error");
2005
+ }
2006
+ }
2007
+ async start() {
2008
+ return new Promise((resolve, reject) => {
2009
+ try {
2010
+ this.server = this.app.listen(this.config.port, this.config.host, () => {
2011
+ console.error(`Scaffolding MCP server started with SSE transport on http://${this.config.host}:${this.config.port}`);
2012
+ console.error(`SSE endpoint: http://${this.config.host}:${this.config.port}/sse`);
2013
+ console.error(`Messages endpoint: http://${this.config.host}:${this.config.port}/messages`);
2014
+ console.error(`Health check: http://${this.config.host}:${this.config.port}/health`);
2015
+ resolve();
2016
+ });
2017
+ this.server.on("error", (error) => {
2018
+ reject(error);
2019
+ });
2020
+ } catch (error) {
2021
+ reject(error);
2022
+ }
2023
+ });
2024
+ }
2025
+ async stop() {
2026
+ return new Promise((resolve, reject) => {
2027
+ if (this.server) {
2028
+ this.sessionManager.clear();
2029
+ this.server.close((err) => {
2030
+ if (err) reject(err);
2031
+ else {
2032
+ this.server = null;
2033
+ resolve();
2034
+ }
2035
+ });
2036
+ } else resolve();
2037
+ });
2038
+ }
2039
+ getPort() {
2040
+ return this.config.port;
2041
+ }
2042
+ getHost() {
2043
+ return this.config.host;
2044
+ }
2045
+ };
2046
+
2047
+ //#endregion
2048
+ //#region src/transports/stdio.ts
2049
+ /**
2050
+ * Stdio transport handler for MCP server
2051
+ * Used for command-line and direct integrations
2052
+ */
2053
+ var StdioTransportHandler = class {
2054
+ server;
2055
+ transport = null;
2056
+ constructor(server) {
2057
+ this.server = server;
2058
+ }
2059
+ async start() {
2060
+ this.transport = new StdioServerTransport();
2061
+ await this.server.connect(this.transport);
2062
+ console.error("Scaffolding MCP server started on stdio");
2063
+ }
2064
+ async stop() {
2065
+ if (this.transport) {
2066
+ await this.transport.close();
2067
+ this.transport = null;
2068
+ }
2069
+ }
2070
+ };
2071
+
2072
+ //#endregion
2073
+ export { BoilerplateGeneratorService, BoilerplateService, FileSystemService, GenerateBoilerplateFileTool, GenerateBoilerplateTool, GenerateFeatureScaffoldTool, HttpTransportHandler, ListBoilerplatesTool, ListScaffoldingMethodsTool, ScaffoldGeneratorService, ScaffoldingMethodsService, SseTransportHandler, StdioTransportHandler, UseBoilerplateTool, UseScaffoldMethodTool, WriteToFileTool };