@agiflowai/scaffold-mcp 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -11
- package/dist/index.js +391 -54
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -154,15 +154,32 @@ Use scaffold-mcp as a standalone CLI tool for template management and scaffoldin
|
|
|
154
154
|
#### Template Management
|
|
155
155
|
|
|
156
156
|
```bash
|
|
157
|
-
# Initialize templates folder
|
|
157
|
+
# Initialize templates folder and auto-download official templates
|
|
158
158
|
scaffold-mcp init
|
|
159
|
-
scaffold-mcp init --path /path/to/templates
|
|
160
159
|
|
|
161
|
-
#
|
|
160
|
+
# Initialize at custom path
|
|
161
|
+
scaffold-mcp init --path ./custom-templates
|
|
162
|
+
|
|
163
|
+
# Initialize without downloading templates
|
|
164
|
+
scaffold-mcp init --no-download
|
|
165
|
+
|
|
166
|
+
# Add templates from repositories (full or subdirectory)
|
|
162
167
|
scaffold-mcp add --name my-template --url https://github.com/user/template
|
|
163
|
-
scaffold-mcp add --name
|
|
168
|
+
scaffold-mcp add --name nextjs-custom --url https://github.com/user/repo/tree/main/templates/nextjs
|
|
164
169
|
```
|
|
165
170
|
|
|
171
|
+
**What `init` does:**
|
|
172
|
+
1. Creates `templates/` folder in your workspace root
|
|
173
|
+
2. Automatically downloads official templates from [AgiFlow/aicode-toolkit](https://github.com/AgiFlow/aicode-toolkit/tree/main/templates)
|
|
174
|
+
3. Creates a README.md with usage instructions
|
|
175
|
+
4. Skips templates that already exist (safe to re-run)
|
|
176
|
+
|
|
177
|
+
**What `add` does:**
|
|
178
|
+
1. Parses GitHub URL to detect full repository vs subdirectory
|
|
179
|
+
2. Downloads template using git clone (full repo) or sparse checkout (subdirectory)
|
|
180
|
+
3. Validates template has required configuration files (scaffold.yaml)
|
|
181
|
+
4. Saves template to your templates folder
|
|
182
|
+
|
|
166
183
|
#### Boilerplate Commands
|
|
167
184
|
|
|
168
185
|
```bash
|
|
@@ -205,24 +222,50 @@ scaffold-mcp scaffold add scaffold-nextjs-page \
|
|
|
205
222
|
|
|
206
223
|
### 1. Initialize Templates
|
|
207
224
|
|
|
225
|
+
The `init` command sets up your templates folder and **automatically downloads official templates** from the AgiFlow repository:
|
|
226
|
+
|
|
208
227
|
```bash
|
|
209
|
-
# Initialize templates folder
|
|
228
|
+
# Initialize templates folder and download official templates
|
|
210
229
|
scaffold-mcp init
|
|
211
230
|
|
|
212
231
|
# Or specify a custom path
|
|
213
232
|
scaffold-mcp init --path ./my-templates
|
|
233
|
+
|
|
234
|
+
# Skip auto-download if you want to add templates manually
|
|
235
|
+
scaffold-mcp init --no-download
|
|
214
236
|
```
|
|
215
237
|
|
|
216
|
-
|
|
238
|
+
**What gets downloaded:**
|
|
239
|
+
- ✅ `nextjs-15-drizzle` - Next.js 15 with App Router, TypeScript, Tailwind CSS 4, Storybook, and optional Drizzle ORM
|
|
240
|
+
- ✅ More templates coming soon...
|
|
217
241
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
242
|
+
All templates from [github.com/AgiFlow/aicode-toolkit/templates](https://github.com/AgiFlow/aicode-toolkit/tree/main/templates) are automatically pulled into your workspace.
|
|
243
|
+
|
|
244
|
+
### 2. Add Custom Templates
|
|
245
|
+
|
|
246
|
+
Add additional templates from GitHub repositories or subdirectories:
|
|
221
247
|
|
|
222
|
-
|
|
223
|
-
|
|
248
|
+
```bash
|
|
249
|
+
# Add a template from a full repository
|
|
250
|
+
scaffold-mcp add --name my-template --url https://github.com/yourorg/nextjs-template
|
|
251
|
+
|
|
252
|
+
# Add a template from a repository subdirectory (NEW!)
|
|
253
|
+
scaffold-mcp add \
|
|
254
|
+
--name nextjs-15-drizzle \
|
|
255
|
+
--url https://github.com/AgiFlow/aicode-toolkit/tree/main/templates/nextjs-15-drizzle
|
|
256
|
+
|
|
257
|
+
# Add to a specific type folder
|
|
258
|
+
scaffold-mcp add \
|
|
259
|
+
--name react-component \
|
|
260
|
+
--url https://github.com/yourorg/react-component-scaffold \
|
|
261
|
+
--type scaffold
|
|
224
262
|
```
|
|
225
263
|
|
|
264
|
+
**Supported URL formats:**
|
|
265
|
+
- Full repository: `https://github.com/user/repo`
|
|
266
|
+
- Subdirectory: `https://github.com/user/repo/tree/branch/path/to/template`
|
|
267
|
+
- With `.git` extension: `https://github.com/user/repo.git`
|
|
268
|
+
|
|
226
269
|
### 3. Create a New Project
|
|
227
270
|
|
|
228
271
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -4,13 +4,13 @@ import { ScaffoldConfigLoader } from "./ScaffoldConfigLoader-CI0T6zdG.js";
|
|
|
4
4
|
import { TemplateService } from "./TemplateService-CnxvhRVW.js";
|
|
5
5
|
import { VariableReplacementService } from "./VariableReplacementService-Bq0GDhTo.js";
|
|
6
6
|
import { Command } from "commander";
|
|
7
|
-
import { exec } from "node:child_process";
|
|
8
7
|
import * as path$1 from "node:path";
|
|
9
8
|
import path from "node:path";
|
|
10
|
-
import { promisify } from "node:util";
|
|
11
9
|
import * as fs$1 from "fs-extra";
|
|
12
10
|
import fs from "fs-extra";
|
|
13
11
|
import chalk from "chalk";
|
|
12
|
+
import { exec } from "node:child_process";
|
|
13
|
+
import { promisify } from "node:util";
|
|
14
14
|
import { jsonSchemaToZod } from "@composio/json-schema-to-zod";
|
|
15
15
|
import * as yaml$1 from "js-yaml";
|
|
16
16
|
import yaml from "js-yaml";
|
|
@@ -82,7 +82,8 @@ const icons = {
|
|
|
82
82
|
download: "=�",
|
|
83
83
|
upload: "=�",
|
|
84
84
|
gear: "�",
|
|
85
|
-
clipboard: "=�"
|
|
85
|
+
clipboard: "=�",
|
|
86
|
+
skip: "⏭"
|
|
86
87
|
};
|
|
87
88
|
/**
|
|
88
89
|
* Themed message helpers
|
|
@@ -136,9 +137,92 @@ const sections = {
|
|
|
136
137
|
};
|
|
137
138
|
|
|
138
139
|
//#endregion
|
|
139
|
-
//#region src/
|
|
140
|
+
//#region src/utils/git.ts
|
|
140
141
|
const execAsync = promisify(exec);
|
|
141
142
|
/**
|
|
143
|
+
* Parse GitHub URL to detect if it's a subdirectory
|
|
144
|
+
* Supports formats:
|
|
145
|
+
* - https://github.com/user/repo
|
|
146
|
+
* - https://github.com/user/repo/tree/branch/path/to/dir
|
|
147
|
+
* - https://github.com/user/repo/tree/main/path/to/dir
|
|
148
|
+
*/
|
|
149
|
+
function parseGitHubUrl(url) {
|
|
150
|
+
const treeMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/tree\/([^/]+)\/(.+)$/);
|
|
151
|
+
const blobMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+)\/blob\/([^/]+)\/(.+)$/);
|
|
152
|
+
const rootMatch = url.match(/^https?:\/\/github\.com\/([^/]+)\/([^/]+?)(?:\.git)?$/);
|
|
153
|
+
if (treeMatch || blobMatch) {
|
|
154
|
+
const match = treeMatch || blobMatch;
|
|
155
|
+
return {
|
|
156
|
+
owner: match[1],
|
|
157
|
+
repo: match[2],
|
|
158
|
+
repoUrl: `https://github.com/${match[1]}/${match[2]}.git`,
|
|
159
|
+
branch: match[3],
|
|
160
|
+
subdirectory: match[4],
|
|
161
|
+
isSubdirectory: true
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (rootMatch) return {
|
|
165
|
+
owner: rootMatch[1],
|
|
166
|
+
repo: rootMatch[2],
|
|
167
|
+
repoUrl: `https://github.com/${rootMatch[1]}/${rootMatch[2]}.git`,
|
|
168
|
+
isSubdirectory: false
|
|
169
|
+
};
|
|
170
|
+
return {
|
|
171
|
+
repoUrl: url,
|
|
172
|
+
isSubdirectory: false
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Clone a subdirectory from a git repository using sparse checkout
|
|
177
|
+
*/
|
|
178
|
+
async function cloneSubdirectory(repoUrl, branch, subdirectory, targetFolder) {
|
|
179
|
+
const tempFolder = `${targetFolder}.tmp`;
|
|
180
|
+
try {
|
|
181
|
+
await execAsync(`git init "${tempFolder}"`);
|
|
182
|
+
await execAsync(`git -C "${tempFolder}" remote add origin ${repoUrl}`);
|
|
183
|
+
await execAsync(`git -C "${tempFolder}" config core.sparseCheckout true`);
|
|
184
|
+
const sparseCheckoutFile = path.join(tempFolder, ".git", "info", "sparse-checkout");
|
|
185
|
+
await fs$1.writeFile(sparseCheckoutFile, `${subdirectory}\n`);
|
|
186
|
+
await execAsync(`git -C "${tempFolder}" pull --depth=1 origin ${branch}`);
|
|
187
|
+
const sourceDir = path.join(tempFolder, subdirectory);
|
|
188
|
+
if (!await fs$1.pathExists(sourceDir)) throw new Error(`Subdirectory '${subdirectory}' not found in repository at branch '${branch}'`);
|
|
189
|
+
await fs$1.move(sourceDir, targetFolder);
|
|
190
|
+
await fs$1.remove(tempFolder);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
if (await fs$1.pathExists(tempFolder)) await fs$1.remove(tempFolder);
|
|
193
|
+
throw error;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Clone entire repository
|
|
198
|
+
*/
|
|
199
|
+
async function cloneRepository(repoUrl, targetFolder) {
|
|
200
|
+
await execAsync(`git clone ${repoUrl} "${targetFolder}"`);
|
|
201
|
+
const gitFolder = path.join(targetFolder, ".git");
|
|
202
|
+
if (await fs$1.pathExists(gitFolder)) await fs$1.remove(gitFolder);
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Fetch directory listing from GitHub API
|
|
206
|
+
*/
|
|
207
|
+
async function fetchGitHubDirectoryContents(owner, repo, path$2, branch = "main") {
|
|
208
|
+
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path$2}?ref=${branch}`;
|
|
209
|
+
const response = await fetch(url, { headers: {
|
|
210
|
+
Accept: "application/vnd.github.v3+json",
|
|
211
|
+
"User-Agent": "scaffold-mcp"
|
|
212
|
+
} });
|
|
213
|
+
if (!response.ok) throw new Error(`Failed to fetch directory contents: ${response.statusText}`);
|
|
214
|
+
const data = await response.json();
|
|
215
|
+
if (!Array.isArray(data)) throw new Error("Expected directory but got file");
|
|
216
|
+
return data.map((item) => ({
|
|
217
|
+
name: item.name,
|
|
218
|
+
type: item.type,
|
|
219
|
+
path: item.path
|
|
220
|
+
}));
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
//#endregion
|
|
224
|
+
//#region src/cli/add.ts
|
|
225
|
+
/**
|
|
142
226
|
* Add command - add a template to templates folder
|
|
143
227
|
*/
|
|
144
228
|
const addCommand = new Command("add").description("Add a template to templates folder").requiredOption("--name <name>", "Template name").requiredOption("--url <url>", "URL of the template repository to download").option("--path <path>", "Path to templates folder", "./templates").option("--type <type>", "Template type: boilerplate or scaffold", "boilerplate").action(async (options) => {
|
|
@@ -158,10 +242,12 @@ const addCommand = new Command("add").description("Add a template to templates f
|
|
|
158
242
|
}
|
|
159
243
|
logger.info(`${icons.download} Downloading template '${templateName}' from ${templateUrl}...`);
|
|
160
244
|
await fs$1.ensureDir(path.dirname(targetFolder));
|
|
245
|
+
const parsedUrl = parseGitHubUrl(templateUrl);
|
|
161
246
|
try {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
247
|
+
if (parsedUrl.isSubdirectory && parsedUrl.branch && parsedUrl.subdirectory) {
|
|
248
|
+
logger.info(`${icons.folder} Detected subdirectory: ${parsedUrl.subdirectory} (branch: ${parsedUrl.branch})`);
|
|
249
|
+
await cloneSubdirectory(parsedUrl.repoUrl, parsedUrl.branch, parsedUrl.subdirectory, targetFolder);
|
|
250
|
+
} else await cloneRepository(parsedUrl.repoUrl, targetFolder);
|
|
165
251
|
messages.success(`Template '${templateName}' added successfully!`);
|
|
166
252
|
logger.header(`\n${icons.folder} Template location:`);
|
|
167
253
|
logger.indent(targetFolder);
|
|
@@ -531,27 +617,60 @@ async function findWorkspaceRoot(startPath = process.cwd()) {
|
|
|
531
617
|
currentPath = path.dirname(currentPath);
|
|
532
618
|
}
|
|
533
619
|
}
|
|
620
|
+
const DEFAULT_TEMPLATE_REPO = {
|
|
621
|
+
owner: "AgiFlow",
|
|
622
|
+
repo: "aicode-toolkit",
|
|
623
|
+
branch: "main",
|
|
624
|
+
path: "templates"
|
|
625
|
+
};
|
|
626
|
+
/**
|
|
627
|
+
* Download templates from GitHub repository
|
|
628
|
+
*/
|
|
629
|
+
async function downloadTemplates(templatesPath) {
|
|
630
|
+
try {
|
|
631
|
+
logger.info(`${icons.download} Fetching templates from ${DEFAULT_TEMPLATE_REPO.owner}/${DEFAULT_TEMPLATE_REPO.repo}...`);
|
|
632
|
+
const templateDirs = (await fetchGitHubDirectoryContents(DEFAULT_TEMPLATE_REPO.owner, DEFAULT_TEMPLATE_REPO.repo, DEFAULT_TEMPLATE_REPO.path, DEFAULT_TEMPLATE_REPO.branch)).filter((item) => item.type === "dir");
|
|
633
|
+
if (templateDirs.length === 0) {
|
|
634
|
+
messages.warning("No templates found in repository");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
logger.info(`${icons.folder} Found ${templateDirs.length} template(s)`);
|
|
638
|
+
for (const template of templateDirs) {
|
|
639
|
+
const targetFolder = path.join(templatesPath, template.name);
|
|
640
|
+
if (await fs$1.pathExists(targetFolder)) {
|
|
641
|
+
logger.info(`${icons.skip} Skipping ${template.name} (already exists)`);
|
|
642
|
+
continue;
|
|
643
|
+
}
|
|
644
|
+
logger.info(`${icons.download} Downloading ${template.name}...`);
|
|
645
|
+
const repoUrl = `https://github.com/${DEFAULT_TEMPLATE_REPO.owner}/${DEFAULT_TEMPLATE_REPO.repo}.git`;
|
|
646
|
+
await cloneSubdirectory(repoUrl, DEFAULT_TEMPLATE_REPO.branch, template.path, targetFolder);
|
|
647
|
+
logger.success(`${icons.check} Downloaded ${template.name}`);
|
|
648
|
+
}
|
|
649
|
+
logger.success(`\n${icons.check} All templates downloaded successfully!`);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
throw new Error(`Failed to download templates: ${error.message}`);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
534
654
|
/**
|
|
535
655
|
* Init command - initialize templates folder
|
|
536
656
|
*/
|
|
537
|
-
const initCommand = new Command("init").description("Initialize templates folder structure at workspace root").action(async () => {
|
|
657
|
+
const initCommand = new Command("init").description("Initialize templates folder structure at workspace root").option("--no-download", "Skip downloading templates from repository").option("--path <path>", "Custom path for templates folder (relative to workspace root)").action(async (options) => {
|
|
538
658
|
try {
|
|
539
659
|
const workspaceRoot = await findWorkspaceRoot();
|
|
540
|
-
const templatesPath = path.join(workspaceRoot, "templates");
|
|
660
|
+
const templatesPath = options.path ? path.join(workspaceRoot, options.path) : path.join(workspaceRoot, "templates");
|
|
541
661
|
logger.info(`${icons.rocket} Initializing templates folder at: ${templatesPath}`);
|
|
542
662
|
await fs$1.ensureDir(templatesPath);
|
|
543
|
-
await fs$1.ensureDir(path.join(templatesPath, "boilerplates"));
|
|
544
|
-
await fs$1.ensureDir(path.join(templatesPath, "scaffolds"));
|
|
545
663
|
await fs$1.writeFile(path.join(templatesPath, "README.md"), `# Templates
|
|
546
664
|
|
|
547
665
|
This folder contains boilerplate templates and scaffolding methods for your projects.
|
|
548
666
|
|
|
549
|
-
##
|
|
667
|
+
## Templates
|
|
550
668
|
|
|
551
|
-
|
|
552
|
-
-
|
|
669
|
+
Templates are organized by framework/technology and include configuration files (\`scaffold.yaml\`) that define:
|
|
670
|
+
- Boilerplates: Full project starter templates
|
|
671
|
+
- Features: Code scaffolding methods for adding new features to existing projects
|
|
553
672
|
|
|
554
|
-
## Adding Templates
|
|
673
|
+
## Adding More Templates
|
|
555
674
|
|
|
556
675
|
Use the \`add\` command to add templates from remote repositories:
|
|
557
676
|
|
|
@@ -559,31 +678,38 @@ Use the \`add\` command to add templates from remote repositories:
|
|
|
559
678
|
scaffold-mcp add --name my-template --url https://github.com/user/template
|
|
560
679
|
\`\`\`
|
|
561
680
|
|
|
562
|
-
|
|
681
|
+
Or add templates from subdirectories:
|
|
563
682
|
|
|
564
|
-
|
|
683
|
+
\`\`\`bash
|
|
684
|
+
scaffold-mcp add --name nextjs-template --url https://github.com/user/repo/tree/main/templates/nextjs
|
|
685
|
+
\`\`\`
|
|
565
686
|
|
|
566
|
-
|
|
567
|
-
- \`boilerplate.yaml\` - Configuration file
|
|
568
|
-
- Template files with variable placeholders using Liquid syntax (\`{{ variableName }}\`)
|
|
687
|
+
## Creating Custom Templates
|
|
569
688
|
|
|
570
|
-
|
|
689
|
+
Each template should have a \`scaffold.yaml\` configuration file defining:
|
|
690
|
+
- \`boilerplate\`: Array of boilerplate configurations
|
|
691
|
+
- \`features\`: Array of feature scaffold configurations
|
|
571
692
|
|
|
572
|
-
|
|
573
|
-
- \`scaffold.yaml\` - Configuration file
|
|
574
|
-
- Template files organized by project type
|
|
693
|
+
Template files use Liquid syntax for variable placeholders: \`{{ variableName }}\`
|
|
575
694
|
|
|
576
|
-
See documentation for more details
|
|
695
|
+
See existing templates for examples and documentation for more details.
|
|
577
696
|
`);
|
|
578
|
-
logger.success(`${icons.check} Templates folder
|
|
579
|
-
|
|
580
|
-
logger.
|
|
581
|
-
logger.
|
|
582
|
-
logger.indent(
|
|
583
|
-
|
|
584
|
-
|
|
697
|
+
logger.success(`${icons.check} Templates folder created!`);
|
|
698
|
+
if (options.download !== false) await downloadTemplates(templatesPath);
|
|
699
|
+
else logger.info(`${icons.skip} Skipping template download (use --download to enable)`);
|
|
700
|
+
logger.header(`\n${icons.folder} Templates location:`);
|
|
701
|
+
logger.indent(templatesPath);
|
|
702
|
+
const nextSteps = [];
|
|
703
|
+
if (options.download === false) {
|
|
704
|
+
nextSteps.push(`Download templates: scaffold-mcp init --download`);
|
|
705
|
+
nextSteps.push(`Add templates manually: scaffold-mcp add --name <name> --url <url>`);
|
|
706
|
+
} else {
|
|
707
|
+
nextSteps.push(`List available boilerplates: scaffold-mcp boilerplate list`);
|
|
708
|
+
nextSteps.push(`Add more templates: scaffold-mcp add --name <name> --url <url>`);
|
|
709
|
+
}
|
|
710
|
+
sections.nextSteps(nextSteps);
|
|
585
711
|
} catch (error) {
|
|
586
|
-
|
|
712
|
+
messages.error(`Error initializing templates folder: ${error.message}`);
|
|
587
713
|
process.exit(1);
|
|
588
714
|
}
|
|
589
715
|
});
|
|
@@ -804,6 +930,213 @@ Template File Content Guidelines:
|
|
|
804
930
|
}
|
|
805
931
|
};
|
|
806
932
|
|
|
933
|
+
//#endregion
|
|
934
|
+
//#region src/prompts/ScaffoldApplicationPrompt.ts
|
|
935
|
+
/**
|
|
936
|
+
* Prompt for scaffolding a new application using boilerplate templates
|
|
937
|
+
*/
|
|
938
|
+
var ScaffoldApplicationPrompt = class ScaffoldApplicationPrompt {
|
|
939
|
+
static PROMPT_NAME = "scaffold-application";
|
|
940
|
+
/**
|
|
941
|
+
* Get the prompt definition for MCP
|
|
942
|
+
*/
|
|
943
|
+
getDefinition() {
|
|
944
|
+
return {
|
|
945
|
+
name: ScaffoldApplicationPrompt.PROMPT_NAME,
|
|
946
|
+
description: "Scaffold a new application from a boilerplate template",
|
|
947
|
+
arguments: [{
|
|
948
|
+
name: "request",
|
|
949
|
+
description: "Describe the application you want to create (optional)",
|
|
950
|
+
required: false
|
|
951
|
+
}]
|
|
952
|
+
};
|
|
953
|
+
}
|
|
954
|
+
/**
|
|
955
|
+
* Get the prompt messages
|
|
956
|
+
*/
|
|
957
|
+
getMessages(args) {
|
|
958
|
+
const userRequest = args?.request || "";
|
|
959
|
+
return [{
|
|
960
|
+
role: "user",
|
|
961
|
+
content: {
|
|
962
|
+
type: "text",
|
|
963
|
+
text: `You are helping create a new application using the scaffold-mcp MCP tools.
|
|
964
|
+
|
|
965
|
+
${userRequest ? `User request: ${userRequest}\n` : ""}
|
|
966
|
+
Your task is to scaffold a new application by following this workflow:
|
|
967
|
+
|
|
968
|
+
## Step 1: List Available Boilerplates
|
|
969
|
+
Use the \`list-boilerplates\` tool to see all available project templates.
|
|
970
|
+
|
|
971
|
+
**What to look for:**
|
|
972
|
+
- Boilerplate name (e.g., "scaffold-nextjs-app", "scaffold-vite-app")
|
|
973
|
+
- Description of what the boilerplate creates
|
|
974
|
+
- Target folder where projects will be created (e.g., "apps", "packages")
|
|
975
|
+
- Required and optional variables in the variables_schema
|
|
976
|
+
|
|
977
|
+
## Step 2: Gather Required Information
|
|
978
|
+
Based on the selected boilerplate's variables_schema, collect:
|
|
979
|
+
- **Project name**: Must be kebab-case (e.g., "my-new-app", not "MyNewApp")
|
|
980
|
+
- **Required variables**: All variables marked as required: true
|
|
981
|
+
- **Optional variables**: Variables with required: false (ask user if needed)
|
|
982
|
+
|
|
983
|
+
Common variables:
|
|
984
|
+
- \`appName\` or \`packageName\`: The project name (kebab-case)
|
|
985
|
+
- \`description\`: Brief description of what the project does
|
|
986
|
+
- \`author\`: Author name
|
|
987
|
+
|
|
988
|
+
## Step 3: Execute the Boilerplate
|
|
989
|
+
Use the \`use-boilerplate\` tool with:
|
|
990
|
+
- \`boilerplateName\`: Exact name from list-boilerplates response
|
|
991
|
+
- \`variables\`: Object matching the variables_schema exactly
|
|
992
|
+
|
|
993
|
+
**Example:**
|
|
994
|
+
\`\`\`json
|
|
995
|
+
{
|
|
996
|
+
"boilerplateName": "scaffold-nextjs-app",
|
|
997
|
+
"variables": {
|
|
998
|
+
"appName": "my-dashboard",
|
|
999
|
+
"description": "Admin dashboard for managing users",
|
|
1000
|
+
"author": "John Doe"
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
\`\`\`
|
|
1004
|
+
|
|
1005
|
+
## Important Guidelines:
|
|
1006
|
+
- **Always call \`list-boilerplates\` first** to see available options and their schemas
|
|
1007
|
+
- **Use exact variable names** from the schema (case-sensitive)
|
|
1008
|
+
- **Provide all required variables** - the tool will fail if any are missing
|
|
1009
|
+
- **Use kebab-case for project names** (e.g., "user-dashboard", not "UserDashboard")
|
|
1010
|
+
- The tool will create the project in the appropriate directory automatically
|
|
1011
|
+
- After creation, inform the user where the project was created
|
|
1012
|
+
|
|
1013
|
+
## Example Workflow:
|
|
1014
|
+
1. Call \`list-boilerplates\` → See available templates
|
|
1015
|
+
2. Ask user which template to use (or infer from request)
|
|
1016
|
+
3. Collect required variables based on schema
|
|
1017
|
+
4. Call \`use-boilerplate\` with boilerplateName and variables
|
|
1018
|
+
5. Report success and next steps to the user`
|
|
1019
|
+
}
|
|
1020
|
+
}];
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
|
|
1024
|
+
//#endregion
|
|
1025
|
+
//#region src/prompts/ScaffoldFeaturePrompt.ts
|
|
1026
|
+
/**
|
|
1027
|
+
* Prompt for scaffolding a new feature in an existing project
|
|
1028
|
+
*/
|
|
1029
|
+
var ScaffoldFeaturePrompt = class ScaffoldFeaturePrompt {
|
|
1030
|
+
static PROMPT_NAME = "scaffold-feature";
|
|
1031
|
+
/**
|
|
1032
|
+
* Get the prompt definition for MCP
|
|
1033
|
+
*/
|
|
1034
|
+
getDefinition() {
|
|
1035
|
+
return {
|
|
1036
|
+
name: ScaffoldFeaturePrompt.PROMPT_NAME,
|
|
1037
|
+
description: "Scaffold a new feature (page, component, service, etc.) in an existing project",
|
|
1038
|
+
arguments: [{
|
|
1039
|
+
name: "request",
|
|
1040
|
+
description: "Describe the feature you want to add (optional)",
|
|
1041
|
+
required: false
|
|
1042
|
+
}, {
|
|
1043
|
+
name: "projectPath",
|
|
1044
|
+
description: "Path to the project (e.g., \"apps/my-app\") - optional if can be inferred",
|
|
1045
|
+
required: false
|
|
1046
|
+
}]
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Get the prompt messages
|
|
1051
|
+
*/
|
|
1052
|
+
getMessages(args) {
|
|
1053
|
+
const userRequest = args?.request || "";
|
|
1054
|
+
const projectPath = args?.projectPath || "";
|
|
1055
|
+
return [{
|
|
1056
|
+
role: "user",
|
|
1057
|
+
content: {
|
|
1058
|
+
type: "text",
|
|
1059
|
+
text: `You are helping add a new feature to an existing project using the scaffold-mcp MCP tools.
|
|
1060
|
+
|
|
1061
|
+
${userRequest ? `User request: ${userRequest}\n` : ""}${projectPath ? `Project path: ${projectPath}\n` : ""}
|
|
1062
|
+
Your task is to scaffold a new feature by following this workflow:
|
|
1063
|
+
|
|
1064
|
+
## Step 1: Identify the Project
|
|
1065
|
+
Determine the project path where the feature will be added:
|
|
1066
|
+
- If projectPath is provided, use it
|
|
1067
|
+
- Otherwise, ask the user or infer from context (e.g., "apps/my-app", "packages/my-lib")
|
|
1068
|
+
- The path should point to a directory containing a \`project.json\` file
|
|
1069
|
+
|
|
1070
|
+
## Step 2: List Available Scaffolding Methods
|
|
1071
|
+
Use the \`list-scaffolding-methods\` tool with the projectPath.
|
|
1072
|
+
|
|
1073
|
+
**What to look for:**
|
|
1074
|
+
- Feature name (e.g., "scaffold-nextjs-page", "scaffold-react-component")
|
|
1075
|
+
- Description of what files/code it generates
|
|
1076
|
+
- Required and optional variables in the variables_schema
|
|
1077
|
+
- The template type (derived from project's sourceTemplate)
|
|
1078
|
+
|
|
1079
|
+
**Example:**
|
|
1080
|
+
\`\`\`json
|
|
1081
|
+
{
|
|
1082
|
+
"projectPath": "apps/my-dashboard"
|
|
1083
|
+
}
|
|
1084
|
+
\`\`\`
|
|
1085
|
+
|
|
1086
|
+
## Step 3: Gather Required Information
|
|
1087
|
+
Based on the selected scaffolding method's variables_schema, collect:
|
|
1088
|
+
- **Feature-specific variables**: Name, path, type, etc.
|
|
1089
|
+
- **Required variables**: All variables marked as required: true
|
|
1090
|
+
- **Optional variables**: Variables with required: false (ask user if needed)
|
|
1091
|
+
|
|
1092
|
+
Common variables:
|
|
1093
|
+
- \`componentName\` / \`pageName\` / \`serviceName\`: Name in PascalCase
|
|
1094
|
+
- \`componentPath\` / \`pagePath\`: Where to place the file (may use kebab-case)
|
|
1095
|
+
- Boolean flags: \`withTests\`, \`withLayout\`, \`withStyles\`, etc.
|
|
1096
|
+
|
|
1097
|
+
## Step 4: Execute the Scaffolding Method
|
|
1098
|
+
Use the \`use-scaffold-method\` tool with:
|
|
1099
|
+
- \`projectPath\`: Same path from step 1
|
|
1100
|
+
- \`scaffold_feature_name\`: Exact name from list-scaffolding-methods response
|
|
1101
|
+
- \`variables\`: Object matching the variables_schema exactly
|
|
1102
|
+
|
|
1103
|
+
**Example:**
|
|
1104
|
+
\`\`\`json
|
|
1105
|
+
{
|
|
1106
|
+
"projectPath": "apps/my-dashboard",
|
|
1107
|
+
"scaffold_feature_name": "scaffold-nextjs-page",
|
|
1108
|
+
"variables": {
|
|
1109
|
+
"pageName": "UserProfile",
|
|
1110
|
+
"pagePath": "user/profile",
|
|
1111
|
+
"withLayout": true,
|
|
1112
|
+
"withTests": false
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
\`\`\`
|
|
1116
|
+
|
|
1117
|
+
## Important Guidelines:
|
|
1118
|
+
- **Always call \`list-scaffolding-methods\` first** with the projectPath
|
|
1119
|
+
- **Use exact variable names** from the schema (case-sensitive)
|
|
1120
|
+
- **Provide all required variables** - the tool will fail if any are missing
|
|
1121
|
+
- **Follow naming conventions**:
|
|
1122
|
+
- Component/Page/Service names: PascalCase (e.g., "UserProfile")
|
|
1123
|
+
- File paths: kebab-case or as specified in schema (e.g., "user/profile")
|
|
1124
|
+
- **Conditional files**: Files with \`?condition=true\` are only included when the variable is true
|
|
1125
|
+
- The tool will create files in the appropriate locations automatically
|
|
1126
|
+
- After creation, inform the user what files were created
|
|
1127
|
+
|
|
1128
|
+
## Example Workflow:
|
|
1129
|
+
1. Identify project path (provided or ask user)
|
|
1130
|
+
2. Call \`list-scaffolding-methods\` → See available features for this project
|
|
1131
|
+
3. Ask user which feature to add (or infer from request)
|
|
1132
|
+
4. Collect required variables based on schema
|
|
1133
|
+
5. Call \`use-scaffold-method\` with projectPath, scaffold_feature_name, and variables
|
|
1134
|
+
6. Report success and list created files`
|
|
1135
|
+
}
|
|
1136
|
+
}];
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
|
|
807
1140
|
//#endregion
|
|
808
1141
|
//#region src/services/BoilerplateGeneratorService.ts
|
|
809
1142
|
/**
|
|
@@ -1552,9 +1885,7 @@ Best practices:
|
|
|
1552
1885
|
- Use conditional includes with ?variableName=value for optional files
|
|
1553
1886
|
- Use path mapping with -> when source and target paths differ
|
|
1554
1887
|
- Use {{ variableName }} in target paths for dynamic file placement
|
|
1555
|
-
- Avoid wildcards unless you have a good reason
|
|
1556
|
-
|
|
1557
|
-
See templates/nextjs-15/scaffold.yaml features section for examples.`,
|
|
1888
|
+
- Avoid wildcards unless you have a good reason`,
|
|
1558
1889
|
items: { type: "string" }
|
|
1559
1890
|
},
|
|
1560
1891
|
patterns: {
|
|
@@ -2150,6 +2481,8 @@ function createServer(options = {}) {
|
|
|
2150
2481
|
const generateFeatureScaffoldTool = adminEnabled ? new GenerateFeatureScaffoldTool(templatesPath) : null;
|
|
2151
2482
|
const generateBoilerplatePrompt = adminEnabled ? new GenerateBoilerplatePrompt() : null;
|
|
2152
2483
|
const generateFeatureScaffoldPrompt = adminEnabled ? new GenerateFeatureScaffoldPrompt() : null;
|
|
2484
|
+
const scaffoldApplicationPrompt = new ScaffoldApplicationPrompt();
|
|
2485
|
+
const scaffoldFeaturePrompt = new ScaffoldFeaturePrompt();
|
|
2153
2486
|
const server = new Server({
|
|
2154
2487
|
name: "scaffold-mcp",
|
|
2155
2488
|
version: "1.0.0"
|
|
@@ -2255,26 +2588,30 @@ Example workflow for feature:
|
|
|
2255
2588
|
}
|
|
2256
2589
|
throw new Error(`Unknown tool: ${name}`);
|
|
2257
2590
|
});
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2591
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
2592
|
+
const prompts = [];
|
|
2593
|
+
prompts.push(scaffoldApplicationPrompt.getDefinition());
|
|
2594
|
+
prompts.push(scaffoldFeaturePrompt.getDefinition());
|
|
2595
|
+
if (adminEnabled) {
|
|
2261
2596
|
if (generateBoilerplatePrompt) prompts.push(generateBoilerplatePrompt.getDefinition());
|
|
2262
2597
|
if (generateFeatureScaffoldPrompt) prompts.push(generateFeatureScaffoldPrompt.getDefinition());
|
|
2263
|
-
|
|
2264
|
-
}
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
if (
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
throw new Error(
|
|
2276
|
-
|
|
2277
|
-
|
|
2598
|
+
}
|
|
2599
|
+
return { prompts };
|
|
2600
|
+
});
|
|
2601
|
+
server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
2602
|
+
const { name, arguments: args } = request.params;
|
|
2603
|
+
if (name === ScaffoldApplicationPrompt.PROMPT_NAME) return { messages: scaffoldApplicationPrompt.getMessages(args) };
|
|
2604
|
+
if (name === ScaffoldFeaturePrompt.PROMPT_NAME) return { messages: scaffoldFeaturePrompt.getMessages(args) };
|
|
2605
|
+
if (name === GenerateBoilerplatePrompt.PROMPT_NAME) {
|
|
2606
|
+
if (!generateBoilerplatePrompt) throw new Error("Prompt not available");
|
|
2607
|
+
return { messages: generateBoilerplatePrompt.getMessages(args) };
|
|
2608
|
+
}
|
|
2609
|
+
if (name === GenerateFeatureScaffoldPrompt.PROMPT_NAME) {
|
|
2610
|
+
if (!generateFeatureScaffoldPrompt) throw new Error("Prompt not available");
|
|
2611
|
+
return { messages: generateFeatureScaffoldPrompt.getMessages(args) };
|
|
2612
|
+
}
|
|
2613
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
2614
|
+
});
|
|
2278
2615
|
return server;
|
|
2279
2616
|
}
|
|
2280
2617
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agiflowai/scaffold-mcp",
|
|
3
3
|
"description": "MCP server for scaffolding applications with boilerplate templates",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"author": "AgiflowIO",
|
|
7
7
|
"repository": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"js-yaml": "4.1.0",
|
|
43
43
|
"liquidjs": "10.21.1",
|
|
44
44
|
"zod": "3.25.76",
|
|
45
|
-
"@agiflowai/scaffold-generator": "0.
|
|
45
|
+
"@agiflowai/scaffold-generator": "0.3.0"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/express": "^5.0.0",
|