create-better-fullstack 1.5.1 → 1.5.2
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/dist/addons-setup-1jZXP0D3.mjs +1342 -0
- package/dist/addons-setup-AbN693PV.mjs +5 -0
- package/dist/bts-config-BCe8SqYV.mjs +308 -0
- package/dist/cli.mjs +2 -3
- package/dist/index.d.mts +5 -2
- package/dist/index.mjs +7931 -1
- package/dist/mcp-C_X1WfCg.mjs +5 -0
- package/dist/mcp-entry.d.mts +1 -0
- package/dist/mcp-entry.mjs +836 -0
- package/package.json +8 -3
- package/dist/src-hfdQPH0o.mjs +0 -9536
- /package/dist/{chunk-DPg_XC7m.mjs → chunk-CCII7kTE.mjs} +0 -0
|
@@ -0,0 +1,836 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as getLatestCLIVersion, r as writeBtsConfig, t as readBtsConfig } from "./bts-config-BCe8SqYV.mjs";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
import { AISchema, APISchema, AddonsSchema, AnalyticsSchema, AnimationSchema, AstroIntegrationSchema, AuthSchema, BackendSchema, CMSSchema, CSSFrameworkSchema, CachingSchema, DatabaseSchema, DatabaseSetupSchema, EcosystemSchema, EffectSchema, EmailSchema, ExamplesSchema, FeatureFlagsSchema, FileStorageSchema, FileUploadSchema, FormsSchema, FrontendSchema, GoApiSchema, GoCliSchema, GoLoggingSchema, GoOrmSchema, GoWebFrameworkSchema, JobQueueSchema, LoggingSchema, ORMSchema, ObservabilitySchema, PackageManagerSchema, PaymentsSchema, PythonAiSchema, PythonOrmSchema, PythonQualitySchema, PythonTaskQueueSchema, PythonValidationSchema, PythonWebFrameworkSchema, RealtimeSchema, RuntimeSchema, RustApiSchema, RustCliSchema, RustFrontendSchema, RustLibrariesSchema, RustOrmSchema, RustWebFrameworkSchema, SearchSchema, ServerDeploySchema, StateManagementSchema, TestingSchema, UILibrarySchema, ValidationSchema, WebDeploySchema, analyzeStackCompatibility } from "@better-fullstack/types";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
|
+
|
|
8
|
+
//#region src/mcp.ts
|
|
9
|
+
const INSTRUCTIONS = `Better-Fullstack scaffolds fullstack projects across TypeScript, Rust, Go, and Python ecosystems with 270+ configurable options.
|
|
10
|
+
|
|
11
|
+
RECOMMENDED WORKFLOW:
|
|
12
|
+
1. Call bfs_get_guidance to understand field semantics, required fields, and workflow rules.
|
|
13
|
+
2. Read the "docs://compatibility-rules" resource for valid stack combinations.
|
|
14
|
+
3. Call bfs_check_compatibility to validate your planned stack before creating.
|
|
15
|
+
4. Call bfs_plan_project to preview (dry-run) — no files are written.
|
|
16
|
+
5. Call bfs_create_project to scaffold the project on disk.
|
|
17
|
+
|
|
18
|
+
For existing projects:
|
|
19
|
+
1. Call bfs_plan_addition to validate proposed changes.
|
|
20
|
+
2. Call bfs_add_feature to apply changes.
|
|
21
|
+
|
|
22
|
+
CRITICAL RULES:
|
|
23
|
+
- Dependency installation is ALWAYS skipped in MCP mode (timeout risk). After scaffolding, tell the user to run install manually.
|
|
24
|
+
- "frontend" is an ARRAY (multiple frontends in one monorepo). All other fields are strings.
|
|
25
|
+
- "none" means "skip this feature entirely", not "use the default".
|
|
26
|
+
- Always specify "ecosystem" first — it determines which other fields are relevant.
|
|
27
|
+
- TypeScript-specific fields (frontend, backend, orm, etc.) are IGNORED for rust/python/go ecosystems.
|
|
28
|
+
- The compatibility engine auto-adjusts invalid combinations — always call bfs_check_compatibility first to see adjustments.`;
|
|
29
|
+
function getGuidance() {
|
|
30
|
+
return {
|
|
31
|
+
workflow: [
|
|
32
|
+
"Call bfs_get_guidance (this tool) to understand field semantics and rules.",
|
|
33
|
+
"Call bfs_get_schema to see valid values for each category.",
|
|
34
|
+
"Call bfs_check_compatibility to validate your planned stack before creation.",
|
|
35
|
+
"Call bfs_plan_project to preview the generated project (dry-run, no files written).",
|
|
36
|
+
"Call bfs_create_project to scaffold the project on disk.",
|
|
37
|
+
"For existing projects: call bfs_plan_addition, then bfs_add_feature."
|
|
38
|
+
],
|
|
39
|
+
ecosystems: {
|
|
40
|
+
typescript: "Full-featured: frontend + backend + database + ORM + auth + payments + 20+ feature categories.",
|
|
41
|
+
rust: "Backend/CLI: web framework (axum/actix-web), ORM (sea-orm/sqlx), gRPC, GraphQL, CLI tools.",
|
|
42
|
+
python: "Backend/AI: web framework (fastapi/django), ORM (sqlalchemy/sqlmodel), AI/ML integrations, task queues.",
|
|
43
|
+
go: "Backend/CLI: web framework (gin/echo), ORM (gorm/sqlc), gRPC, CLI tools, logging."
|
|
44
|
+
},
|
|
45
|
+
fieldRules: {
|
|
46
|
+
projectName: "kebab-case directory name. Required for bfs_create_project.",
|
|
47
|
+
ecosystem: "Must be set first. Determines which other fields are relevant.",
|
|
48
|
+
frontend: "ARRAY of strings. TypeScript only. Supports multiple frontends in one monorepo. Use [] for API-only.",
|
|
49
|
+
backend: "String. \"self\" means fullstack mode (Next.js/TanStack Start/Nuxt/Astro API routes). \"none\" for frontend-only.",
|
|
50
|
+
runtime: "\"bun\" or \"node\". Must be \"none\" when backend is \"self\" or \"convex\".",
|
|
51
|
+
addons: "ARRAY of strings. Monorepo tools, code quality, desktop (tauri), browser extensions (wxt), etc."
|
|
52
|
+
},
|
|
53
|
+
ambiguityRules: [
|
|
54
|
+
"If the user request leaves major stack choices unspecified, ASK the user before proceeding. Do not guess.",
|
|
55
|
+
"Do not infer addons, examples, or optional features the user did not mention. Default to \"none\".",
|
|
56
|
+
"When the user says 'fullstack Next.js', use backend='self', frontend=['next'], runtime='none'.",
|
|
57
|
+
"When the user says 'React + Hono', use frontend=['tanstack-router'] (or ask which React framework), backend='hono'."
|
|
58
|
+
],
|
|
59
|
+
criticalConstraints: [
|
|
60
|
+
"tRPC (api='trpc') only works with React-based frontends: next, react-router, tanstack-router, tanstack-start.",
|
|
61
|
+
"Use api='orpc' for svelte, solid, nuxt.",
|
|
62
|
+
"Angular: use api='none' (has built-in HttpClient).",
|
|
63
|
+
"Qwik: use backend='none', api='none' (built-in server).",
|
|
64
|
+
"NestJS and AdonisJS backends require runtime='node'.",
|
|
65
|
+
"Elysia backend requires runtime='bun'.",
|
|
66
|
+
"backend='self' only works with: next, tanstack-start, astro, nuxt, svelte, solid-start.",
|
|
67
|
+
"backend='convex' overrides: runtime=none, database=none, orm=none, api=none.",
|
|
68
|
+
"TypeORM + better-auth: unsupported (no adapter). Use auth='none' or orm='drizzle'.",
|
|
69
|
+
"Sequelize + better-auth: unsupported (no adapter). Use auth='none' or orm='drizzle'."
|
|
70
|
+
]
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
const SCHEMA_MAP = {
|
|
74
|
+
ecosystem: EcosystemSchema,
|
|
75
|
+
database: DatabaseSchema,
|
|
76
|
+
orm: ORMSchema,
|
|
77
|
+
backend: BackendSchema,
|
|
78
|
+
runtime: RuntimeSchema,
|
|
79
|
+
frontend: FrontendSchema,
|
|
80
|
+
api: APISchema,
|
|
81
|
+
auth: AuthSchema,
|
|
82
|
+
payments: PaymentsSchema,
|
|
83
|
+
email: EmailSchema,
|
|
84
|
+
fileUpload: FileUploadSchema,
|
|
85
|
+
effect: EffectSchema,
|
|
86
|
+
ai: AISchema,
|
|
87
|
+
stateManagement: StateManagementSchema,
|
|
88
|
+
forms: FormsSchema,
|
|
89
|
+
validation: ValidationSchema,
|
|
90
|
+
testing: TestingSchema,
|
|
91
|
+
cssFramework: CSSFrameworkSchema,
|
|
92
|
+
uiLibrary: UILibrarySchema,
|
|
93
|
+
realtime: RealtimeSchema,
|
|
94
|
+
jobQueue: JobQueueSchema,
|
|
95
|
+
animation: AnimationSchema,
|
|
96
|
+
logging: LoggingSchema,
|
|
97
|
+
observability: ObservabilitySchema,
|
|
98
|
+
featureFlags: FeatureFlagsSchema,
|
|
99
|
+
analytics: AnalyticsSchema,
|
|
100
|
+
cms: CMSSchema,
|
|
101
|
+
caching: CachingSchema,
|
|
102
|
+
search: SearchSchema,
|
|
103
|
+
fileStorage: FileStorageSchema,
|
|
104
|
+
addons: AddonsSchema,
|
|
105
|
+
examples: ExamplesSchema,
|
|
106
|
+
packageManager: PackageManagerSchema,
|
|
107
|
+
dbSetup: DatabaseSetupSchema,
|
|
108
|
+
webDeploy: WebDeploySchema,
|
|
109
|
+
serverDeploy: ServerDeploySchema,
|
|
110
|
+
astroIntegration: AstroIntegrationSchema,
|
|
111
|
+
rustWebFramework: RustWebFrameworkSchema,
|
|
112
|
+
rustFrontend: RustFrontendSchema,
|
|
113
|
+
rustOrm: RustOrmSchema,
|
|
114
|
+
rustApi: RustApiSchema,
|
|
115
|
+
rustCli: RustCliSchema,
|
|
116
|
+
rustLibraries: RustLibrariesSchema,
|
|
117
|
+
pythonWebFramework: PythonWebFrameworkSchema,
|
|
118
|
+
pythonOrm: PythonOrmSchema,
|
|
119
|
+
pythonValidation: PythonValidationSchema,
|
|
120
|
+
pythonAi: PythonAiSchema,
|
|
121
|
+
pythonTaskQueue: PythonTaskQueueSchema,
|
|
122
|
+
pythonQuality: PythonQualitySchema,
|
|
123
|
+
goWebFramework: GoWebFrameworkSchema,
|
|
124
|
+
goOrm: GoOrmSchema,
|
|
125
|
+
goApi: GoApiSchema,
|
|
126
|
+
goCli: GoCliSchema,
|
|
127
|
+
goLogging: GoLoggingSchema
|
|
128
|
+
};
|
|
129
|
+
const ECOSYSTEM_CATEGORIES = {
|
|
130
|
+
typescript: [
|
|
131
|
+
"database",
|
|
132
|
+
"orm",
|
|
133
|
+
"backend",
|
|
134
|
+
"runtime",
|
|
135
|
+
"frontend",
|
|
136
|
+
"api",
|
|
137
|
+
"auth",
|
|
138
|
+
"payments",
|
|
139
|
+
"email",
|
|
140
|
+
"fileUpload",
|
|
141
|
+
"effect",
|
|
142
|
+
"ai",
|
|
143
|
+
"stateManagement",
|
|
144
|
+
"forms",
|
|
145
|
+
"validation",
|
|
146
|
+
"testing",
|
|
147
|
+
"cssFramework",
|
|
148
|
+
"uiLibrary",
|
|
149
|
+
"realtime",
|
|
150
|
+
"jobQueue",
|
|
151
|
+
"animation",
|
|
152
|
+
"logging",
|
|
153
|
+
"observability",
|
|
154
|
+
"featureFlags",
|
|
155
|
+
"analytics",
|
|
156
|
+
"cms",
|
|
157
|
+
"caching",
|
|
158
|
+
"search",
|
|
159
|
+
"fileStorage",
|
|
160
|
+
"astroIntegration"
|
|
161
|
+
],
|
|
162
|
+
rust: [
|
|
163
|
+
"rustWebFramework",
|
|
164
|
+
"rustFrontend",
|
|
165
|
+
"rustOrm",
|
|
166
|
+
"rustApi",
|
|
167
|
+
"rustCli",
|
|
168
|
+
"rustLibraries"
|
|
169
|
+
],
|
|
170
|
+
python: [
|
|
171
|
+
"pythonWebFramework",
|
|
172
|
+
"pythonOrm",
|
|
173
|
+
"pythonValidation",
|
|
174
|
+
"pythonAi",
|
|
175
|
+
"pythonTaskQueue",
|
|
176
|
+
"pythonQuality"
|
|
177
|
+
],
|
|
178
|
+
go: [
|
|
179
|
+
"goWebFramework",
|
|
180
|
+
"goOrm",
|
|
181
|
+
"goApi",
|
|
182
|
+
"goCli",
|
|
183
|
+
"goLogging"
|
|
184
|
+
],
|
|
185
|
+
shared: [
|
|
186
|
+
"ecosystem",
|
|
187
|
+
"packageManager",
|
|
188
|
+
"addons",
|
|
189
|
+
"examples",
|
|
190
|
+
"webDeploy",
|
|
191
|
+
"serverDeploy",
|
|
192
|
+
"dbSetup"
|
|
193
|
+
]
|
|
194
|
+
};
|
|
195
|
+
function getSchemaOptions(category, ecosystem) {
|
|
196
|
+
if (category) {
|
|
197
|
+
const schema = SCHEMA_MAP[category];
|
|
198
|
+
if (!schema) return { error: `Unknown category: ${category}. Available: ${Object.keys(SCHEMA_MAP).join(", ")}` };
|
|
199
|
+
if (schema instanceof z.ZodEnum) return {
|
|
200
|
+
category,
|
|
201
|
+
options: schema.options
|
|
202
|
+
};
|
|
203
|
+
return {
|
|
204
|
+
category,
|
|
205
|
+
description: "Schema exists but is not a simple enum."
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
const allowedKeys = ecosystem && ECOSYSTEM_CATEGORIES[ecosystem] ? new Set([...ECOSYSTEM_CATEGORIES[ecosystem], ...ECOSYSTEM_CATEGORIES.shared]) : null;
|
|
209
|
+
const result = {};
|
|
210
|
+
for (const [key, schema] of Object.entries(SCHEMA_MAP)) {
|
|
211
|
+
if (allowedKeys && !allowedKeys.has(key)) continue;
|
|
212
|
+
if (schema instanceof z.ZodEnum) result[key] = schema.options;
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
function getInstallCommand(ecosystem, projectName, packageManager) {
|
|
217
|
+
switch (ecosystem) {
|
|
218
|
+
case "rust": return `cd ${projectName} && cargo build`;
|
|
219
|
+
case "python": return `cd ${projectName} && uv sync`;
|
|
220
|
+
case "go": return `cd ${projectName} && go mod tidy`;
|
|
221
|
+
default: return `cd ${projectName} && ${packageManager ?? "bun"} install`;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
function filterCompatibilityResult(result, ecosystem) {
|
|
225
|
+
const { adjustedStack, changes } = result;
|
|
226
|
+
if (!adjustedStack) return {
|
|
227
|
+
adjustedStack: null,
|
|
228
|
+
changes
|
|
229
|
+
};
|
|
230
|
+
const relevantKeys = new Set([
|
|
231
|
+
...ECOSYSTEM_CATEGORIES[ecosystem] ?? ECOSYSTEM_CATEGORIES.typescript,
|
|
232
|
+
...ECOSYSTEM_CATEGORIES.shared,
|
|
233
|
+
"projectName",
|
|
234
|
+
"git",
|
|
235
|
+
"install",
|
|
236
|
+
"aiDocs"
|
|
237
|
+
]);
|
|
238
|
+
const filtered = {};
|
|
239
|
+
for (const [key, value] of Object.entries(adjustedStack)) if (relevantKeys.has(key)) filtered[key] = value;
|
|
240
|
+
return {
|
|
241
|
+
adjustedStack: filtered,
|
|
242
|
+
changes
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function buildProjectConfig(input, overrides) {
|
|
246
|
+
const projectName = input.projectName ?? "my-project";
|
|
247
|
+
return {
|
|
248
|
+
projectName,
|
|
249
|
+
projectDir: overrides?.projectDir ?? "/virtual",
|
|
250
|
+
relativePath: overrides ? `./${projectName}` : "./virtual",
|
|
251
|
+
ecosystem: input.ecosystem ?? "typescript",
|
|
252
|
+
frontend: input.frontend ?? ["tanstack-router"],
|
|
253
|
+
backend: input.backend ?? "hono",
|
|
254
|
+
runtime: input.runtime ?? "bun",
|
|
255
|
+
database: input.database ?? "none",
|
|
256
|
+
orm: input.orm ?? "none",
|
|
257
|
+
api: input.api ?? "none",
|
|
258
|
+
auth: input.auth ?? "none",
|
|
259
|
+
payments: input.payments ?? "none",
|
|
260
|
+
email: input.email ?? "none",
|
|
261
|
+
fileUpload: input.fileUpload ?? "none",
|
|
262
|
+
effect: "none",
|
|
263
|
+
ai: input.ai ?? "none",
|
|
264
|
+
stateManagement: input.stateManagement ?? "none",
|
|
265
|
+
forms: input.forms ?? "none",
|
|
266
|
+
validation: input.validation ?? "none",
|
|
267
|
+
testing: input.testing ?? "none",
|
|
268
|
+
cssFramework: input.cssFramework ?? "tailwind",
|
|
269
|
+
uiLibrary: input.uiLibrary ?? "none",
|
|
270
|
+
shadcnBase: "radix",
|
|
271
|
+
shadcnStyle: "nova",
|
|
272
|
+
shadcnIconLibrary: "lucide",
|
|
273
|
+
shadcnColorTheme: "neutral",
|
|
274
|
+
shadcnBaseColor: "neutral",
|
|
275
|
+
shadcnFont: "inter",
|
|
276
|
+
shadcnRadius: "default",
|
|
277
|
+
realtime: input.realtime ?? "none",
|
|
278
|
+
jobQueue: input.jobQueue ?? "none",
|
|
279
|
+
animation: input.animation ?? "none",
|
|
280
|
+
logging: input.logging ?? "none",
|
|
281
|
+
observability: input.observability ?? "none",
|
|
282
|
+
featureFlags: "none",
|
|
283
|
+
analytics: "none",
|
|
284
|
+
cms: input.cms ?? "none",
|
|
285
|
+
caching: input.caching ?? "none",
|
|
286
|
+
search: input.search ?? "none",
|
|
287
|
+
fileStorage: input.fileStorage ?? "none",
|
|
288
|
+
addons: input.addons ?? [],
|
|
289
|
+
examples: input.examples ?? [],
|
|
290
|
+
packageManager: input.packageManager ?? "bun",
|
|
291
|
+
versionChannel: "stable",
|
|
292
|
+
webDeploy: input.webDeploy ?? "none",
|
|
293
|
+
serverDeploy: input.serverDeploy ?? "none",
|
|
294
|
+
dbSetup: input.dbSetup ?? "none",
|
|
295
|
+
astroIntegration: "none",
|
|
296
|
+
git: !!overrides,
|
|
297
|
+
install: false,
|
|
298
|
+
aiDocs: ["claude-md"],
|
|
299
|
+
rustWebFramework: input.rustWebFramework ?? "none",
|
|
300
|
+
rustFrontend: input.rustFrontend ?? "none",
|
|
301
|
+
rustOrm: input.rustOrm ?? "none",
|
|
302
|
+
rustApi: input.rustApi ?? "none",
|
|
303
|
+
rustCli: input.rustCli ?? "none",
|
|
304
|
+
rustLibraries: input.rustLibraries ?? [],
|
|
305
|
+
pythonWebFramework: input.pythonWebFramework ?? "none",
|
|
306
|
+
pythonOrm: input.pythonOrm ?? "none",
|
|
307
|
+
pythonValidation: input.pythonValidation ?? "none",
|
|
308
|
+
pythonAi: input.pythonAi ?? [],
|
|
309
|
+
pythonTaskQueue: input.pythonTaskQueue ?? "none",
|
|
310
|
+
pythonQuality: input.pythonQuality ?? "none",
|
|
311
|
+
goWebFramework: input.goWebFramework ?? "none",
|
|
312
|
+
goOrm: input.goOrm ?? "none",
|
|
313
|
+
goApi: input.goApi ?? "none",
|
|
314
|
+
goCli: input.goCli ?? "none",
|
|
315
|
+
goLogging: input.goLogging ?? "none"
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
function sanitizePath(input) {
|
|
319
|
+
for (const ch of input) if (ch.charCodeAt(0) < 32) throw new Error("Path contains control characters");
|
|
320
|
+
if (input.split(/[/\\]/).includes("..")) throw new Error("Path must not contain '..' components");
|
|
321
|
+
return input;
|
|
322
|
+
}
|
|
323
|
+
function buildCompatibilityInput(input) {
|
|
324
|
+
const frontend = input.frontend;
|
|
325
|
+
const addons = input.addons ?? [];
|
|
326
|
+
const codeQuality = addons.filter((a) => [
|
|
327
|
+
"biome",
|
|
328
|
+
"oxlint",
|
|
329
|
+
"ultracite",
|
|
330
|
+
"lefthook",
|
|
331
|
+
"husky",
|
|
332
|
+
"ruler"
|
|
333
|
+
].includes(a));
|
|
334
|
+
const documentation = addons.filter((a) => ["starlight", "fumadocs"].includes(a));
|
|
335
|
+
const appPlatforms = addons.filter((a) => ![
|
|
336
|
+
...codeQuality,
|
|
337
|
+
...documentation,
|
|
338
|
+
"none"
|
|
339
|
+
].includes(a));
|
|
340
|
+
return {
|
|
341
|
+
ecosystem: input.ecosystem ?? "typescript",
|
|
342
|
+
projectName: input.projectName ?? null,
|
|
343
|
+
webFrontend: frontend ?? [],
|
|
344
|
+
nativeFrontend: [],
|
|
345
|
+
astroIntegration: input.astroIntegration ?? "none",
|
|
346
|
+
runtime: input.runtime ?? "bun",
|
|
347
|
+
backend: input.backend ?? "hono",
|
|
348
|
+
database: input.database ?? "none",
|
|
349
|
+
orm: input.orm ?? "none",
|
|
350
|
+
dbSetup: input.dbSetup ?? "none",
|
|
351
|
+
auth: input.auth ?? "none",
|
|
352
|
+
payments: input.payments ?? "none",
|
|
353
|
+
email: input.email ?? "none",
|
|
354
|
+
fileUpload: input.fileUpload ?? "none",
|
|
355
|
+
logging: input.logging ?? "none",
|
|
356
|
+
observability: input.observability ?? "none",
|
|
357
|
+
featureFlags: input.featureFlags ?? "none",
|
|
358
|
+
analytics: input.analytics ?? "none",
|
|
359
|
+
backendLibraries: "none",
|
|
360
|
+
stateManagement: input.stateManagement ?? "none",
|
|
361
|
+
forms: input.forms ?? "none",
|
|
362
|
+
validation: input.validation ?? "none",
|
|
363
|
+
testing: input.testing ?? "none",
|
|
364
|
+
realtime: input.realtime ?? "none",
|
|
365
|
+
jobQueue: input.jobQueue ?? "none",
|
|
366
|
+
caching: input.caching ?? "none",
|
|
367
|
+
animation: input.animation ?? "none",
|
|
368
|
+
cssFramework: input.cssFramework ?? "tailwind",
|
|
369
|
+
uiLibrary: input.uiLibrary ?? "none",
|
|
370
|
+
cms: input.cms ?? "none",
|
|
371
|
+
search: input.search ?? "none",
|
|
372
|
+
fileStorage: input.fileStorage ?? "none",
|
|
373
|
+
codeQuality,
|
|
374
|
+
documentation,
|
|
375
|
+
appPlatforms,
|
|
376
|
+
packageManager: input.packageManager ?? "bun",
|
|
377
|
+
versionChannel: "stable",
|
|
378
|
+
examples: input.examples ?? [],
|
|
379
|
+
aiSdk: input.ai ?? "none",
|
|
380
|
+
aiDocs: input.aiDocs ?? ["claude-md"],
|
|
381
|
+
git: "true",
|
|
382
|
+
install: "false",
|
|
383
|
+
api: input.api ?? "none",
|
|
384
|
+
webDeploy: input.webDeploy ?? "none",
|
|
385
|
+
serverDeploy: input.serverDeploy ?? "none",
|
|
386
|
+
yolo: "false",
|
|
387
|
+
rustWebFramework: input.rustWebFramework ?? "none",
|
|
388
|
+
rustFrontend: input.rustFrontend ?? "none",
|
|
389
|
+
rustOrm: input.rustOrm ?? "none",
|
|
390
|
+
rustApi: input.rustApi ?? "none",
|
|
391
|
+
rustCli: input.rustCli ?? "none",
|
|
392
|
+
rustLibraries: (input.rustLibraries ?? []).join(",") || "none",
|
|
393
|
+
pythonWebFramework: input.pythonWebFramework ?? "none",
|
|
394
|
+
pythonOrm: input.pythonOrm ?? "none",
|
|
395
|
+
pythonValidation: input.pythonValidation ?? "none",
|
|
396
|
+
pythonAi: (input.pythonAi ?? []).join(",") || "none",
|
|
397
|
+
pythonTaskQueue: input.pythonTaskQueue ?? "none",
|
|
398
|
+
pythonQuality: input.pythonQuality ?? "none",
|
|
399
|
+
goWebFramework: input.goWebFramework ?? "none",
|
|
400
|
+
goOrm: input.goOrm ?? "none",
|
|
401
|
+
goApi: input.goApi ?? "none",
|
|
402
|
+
goCli: input.goCli ?? "none",
|
|
403
|
+
goLogging: input.goLogging ?? "none"
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
function summarizeTree(tree) {
|
|
407
|
+
const paths = [];
|
|
408
|
+
function walk(nodes, prefix) {
|
|
409
|
+
for (const node of nodes) {
|
|
410
|
+
const current = prefix ? `${prefix}/${node.name}` : node.name;
|
|
411
|
+
if (node.type === "directory" && node.children) walk(node.children, current);
|
|
412
|
+
else paths.push(current);
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
walk(tree.root.children, "");
|
|
416
|
+
return {
|
|
417
|
+
fileCount: tree.fileCount,
|
|
418
|
+
directoryCount: tree.directoryCount,
|
|
419
|
+
files: paths
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
const COMPATIBILITY_RULES_MD = `# Better-Fullstack Compatibility Rules
|
|
423
|
+
|
|
424
|
+
## Backend Constraints
|
|
425
|
+
- **Convex**: Forces runtime=none, database=none, orm=none, api=none, dbSetup=none, serverDeploy=none. Removes incompatible frontends (Solid, SolidStart, Astro).
|
|
426
|
+
- **No backend (none)**: Clears auth, payments, database, orm, api, serverDeploy, search, fileStorage.
|
|
427
|
+
- **Fullstack (backend='self')**: Sets runtime=none, serverDeploy=none. Only works with: next, tanstack-start, astro, nuxt, svelte, solid-start.
|
|
428
|
+
|
|
429
|
+
## Runtime Constraints
|
|
430
|
+
- NestJS and AdonisJS require runtime=node.
|
|
431
|
+
- Elysia requires runtime=bun.
|
|
432
|
+
- Cloudflare Workers runtime only works with Hono backend.
|
|
433
|
+
- backend=self or backend=convex requires runtime=none.
|
|
434
|
+
|
|
435
|
+
## API Constraints
|
|
436
|
+
- tRPC only works with React-based frontends: next, react-router, tanstack-router, tanstack-start.
|
|
437
|
+
- Use oRPC for svelte, solid, nuxt.
|
|
438
|
+
- Angular: use api=none (has built-in HttpClient).
|
|
439
|
+
- Qwik: use backend=none, api=none (built-in server, no external APIs).
|
|
440
|
+
|
|
441
|
+
## Database / ORM Constraints
|
|
442
|
+
- TypeORM + better-auth: unsupported (no adapter). Use auth=none or switch ORM.
|
|
443
|
+
- Sequelize + better-auth: unsupported (no adapter). Use auth=none or switch ORM.
|
|
444
|
+
- MongoDB requires mongoose ORM.
|
|
445
|
+
- EdgeDB has its own ORM (edgedb).
|
|
446
|
+
|
|
447
|
+
## UI Constraints
|
|
448
|
+
- shadcn-ui is incompatible with svelte and solid frontends.
|
|
449
|
+
- Redwood requires api=none and only supports daisyui or none for uiLibrary.
|
|
450
|
+
|
|
451
|
+
## Payments
|
|
452
|
+
- Polar requires better-auth and a web frontend.
|
|
453
|
+
|
|
454
|
+
## Ecosystem Isolation
|
|
455
|
+
- Rust, Python, Go ecosystems are independent — TypeScript fields are ignored.
|
|
456
|
+
- Each ecosystem generates a standalone project with its own build system.
|
|
457
|
+
`;
|
|
458
|
+
const GETTING_STARTED_MD = `# Getting Started with Better-Fullstack MCP
|
|
459
|
+
|
|
460
|
+
## Quick Start — TypeScript Project
|
|
461
|
+
1. Call bfs_create_project with:
|
|
462
|
+
- projectName: "my-app"
|
|
463
|
+
- ecosystem: "typescript"
|
|
464
|
+
- frontend: ["tanstack-router"]
|
|
465
|
+
- backend: "hono"
|
|
466
|
+
- runtime: "bun"
|
|
467
|
+
- database: "sqlite"
|
|
468
|
+
- orm: "drizzle"
|
|
469
|
+
2. Tell the user to run: cd my-app && bun install && bun run dev
|
|
470
|
+
|
|
471
|
+
## Quick Start — Rust Project
|
|
472
|
+
1. Call bfs_create_project with:
|
|
473
|
+
- projectName: "my-rust-app"
|
|
474
|
+
- ecosystem: "rust"
|
|
475
|
+
- rustWebFramework: "axum"
|
|
476
|
+
- rustOrm: "sqlx"
|
|
477
|
+
2. Tell the user to run: cd my-rust-app && cargo build
|
|
478
|
+
|
|
479
|
+
## Quick Start — Python Project
|
|
480
|
+
1. Call bfs_create_project with:
|
|
481
|
+
- projectName: "my-python-app"
|
|
482
|
+
- ecosystem: "python"
|
|
483
|
+
- pythonWebFramework: "fastapi"
|
|
484
|
+
- pythonOrm: "sqlalchemy"
|
|
485
|
+
2. Tell the user to run: cd my-python-app && uv sync
|
|
486
|
+
|
|
487
|
+
## Quick Start — Go Project
|
|
488
|
+
1. Call bfs_create_project with:
|
|
489
|
+
- projectName: "my-go-app"
|
|
490
|
+
- ecosystem: "go"
|
|
491
|
+
- goWebFramework: "gin"
|
|
492
|
+
- goOrm: "gorm"
|
|
493
|
+
2. Tell the user to run: cd my-go-app && go mod tidy && go run cmd/server/main.go
|
|
494
|
+
|
|
495
|
+
## Adding Features to Existing Projects
|
|
496
|
+
1. Call bfs_add_feature with projectDir pointing to the project root.
|
|
497
|
+
2. Provide addons array with features to add (e.g., ["biome", "turborepo"]).
|
|
498
|
+
`;
|
|
499
|
+
async function startMcpServer() {
|
|
500
|
+
const server = new McpServer({
|
|
501
|
+
name: "better-fullstack",
|
|
502
|
+
version: getLatestCLIVersion()
|
|
503
|
+
}, {
|
|
504
|
+
instructions: INSTRUCTIONS,
|
|
505
|
+
capabilities: { logging: {} }
|
|
506
|
+
});
|
|
507
|
+
server.tool("bfs_get_guidance", "Returns workflow rules, field semantics, ambiguity rules, and critical constraints. Call this FIRST before using other tools.", {}, async () => {
|
|
508
|
+
const guidance = getGuidance();
|
|
509
|
+
return { content: [{
|
|
510
|
+
type: "text",
|
|
511
|
+
text: JSON.stringify(guidance, null, 2)
|
|
512
|
+
}] };
|
|
513
|
+
});
|
|
514
|
+
server.tool("bfs_get_schema", "Returns valid options for a specific category (e.g., 'database', 'frontend', 'backend') or ALL categories. Use ecosystem to filter to relevant categories only.", {
|
|
515
|
+
category: z.string().optional().describe("Category name (e.g., 'database', 'orm', 'frontend'). Omit for all categories."),
|
|
516
|
+
ecosystem: EcosystemSchema.optional().describe("Filter categories to this ecosystem (e.g., 'rust' returns only Rust + shared categories).")
|
|
517
|
+
}, async ({ category, ecosystem }) => {
|
|
518
|
+
const result = getSchemaOptions(category, ecosystem);
|
|
519
|
+
return { content: [{
|
|
520
|
+
type: "text",
|
|
521
|
+
text: JSON.stringify(result, null, 2)
|
|
522
|
+
}] };
|
|
523
|
+
});
|
|
524
|
+
server.tool("bfs_check_compatibility", "Validates a stack combination and returns auto-adjusted selections with warnings. Call BEFORE creating a project to avoid invalid combinations.", {
|
|
525
|
+
ecosystem: EcosystemSchema.describe("Language ecosystem"),
|
|
526
|
+
frontend: z.array(z.string()).optional().describe("Web frontend frameworks (TypeScript only)"),
|
|
527
|
+
backend: z.string().optional().describe("Backend framework"),
|
|
528
|
+
runtime: z.string().optional().describe("JavaScript runtime"),
|
|
529
|
+
database: z.string().optional().describe("Database type"),
|
|
530
|
+
orm: z.string().optional().describe("ORM"),
|
|
531
|
+
api: z.string().optional().describe("API layer"),
|
|
532
|
+
auth: z.string().optional().describe("Auth provider"),
|
|
533
|
+
payments: z.string().optional().describe("Payments provider"),
|
|
534
|
+
uiLibrary: z.string().optional().describe("UI component library"),
|
|
535
|
+
cssFramework: z.string().optional().describe("CSS framework"),
|
|
536
|
+
addons: z.array(z.string()).optional().describe("Addon list")
|
|
537
|
+
}, async (input) => {
|
|
538
|
+
try {
|
|
539
|
+
const filtered = filterCompatibilityResult(analyzeStackCompatibility(buildCompatibilityInput(input)), input.ecosystem);
|
|
540
|
+
return { content: [{
|
|
541
|
+
type: "text",
|
|
542
|
+
text: JSON.stringify(filtered, null, 2)
|
|
543
|
+
}] };
|
|
544
|
+
} catch (error) {
|
|
545
|
+
return {
|
|
546
|
+
content: [{
|
|
547
|
+
type: "text",
|
|
548
|
+
text: `Compatibility check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
549
|
+
}],
|
|
550
|
+
isError: true
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
});
|
|
554
|
+
const planCreateSchema = {
|
|
555
|
+
projectName: z.string().optional().describe("Project name (kebab-case)"),
|
|
556
|
+
ecosystem: EcosystemSchema.optional().describe("Language ecosystem (default: typescript)"),
|
|
557
|
+
frontend: z.array(FrontendSchema).optional().describe("Frontend frameworks (TypeScript only)"),
|
|
558
|
+
backend: BackendSchema.optional().describe("Backend framework"),
|
|
559
|
+
runtime: RuntimeSchema.optional().describe("JavaScript runtime"),
|
|
560
|
+
database: DatabaseSchema.optional().describe("Database type"),
|
|
561
|
+
orm: ORMSchema.optional().describe("ORM"),
|
|
562
|
+
api: APISchema.optional().describe("API layer"),
|
|
563
|
+
auth: AuthSchema.optional().describe("Auth provider"),
|
|
564
|
+
payments: PaymentsSchema.optional().describe("Payments provider"),
|
|
565
|
+
email: EmailSchema.optional().describe("Email provider"),
|
|
566
|
+
addons: z.array(AddonsSchema).optional().describe("Addons"),
|
|
567
|
+
examples: z.array(ExamplesSchema).optional().describe("Example templates"),
|
|
568
|
+
packageManager: PackageManagerSchema.optional().describe("Package manager (default: bun)"),
|
|
569
|
+
cssFramework: CSSFrameworkSchema.optional().describe("CSS framework"),
|
|
570
|
+
uiLibrary: UILibrarySchema.optional().describe("UI component library"),
|
|
571
|
+
ai: AISchema.optional().describe("AI SDK"),
|
|
572
|
+
stateManagement: StateManagementSchema.optional().describe("State management"),
|
|
573
|
+
forms: FormsSchema.optional().describe("Forms library"),
|
|
574
|
+
validation: ValidationSchema.optional().describe("Validation library"),
|
|
575
|
+
testing: TestingSchema.optional().describe("Testing framework"),
|
|
576
|
+
realtime: RealtimeSchema.optional().describe("Realtime library"),
|
|
577
|
+
jobQueue: JobQueueSchema.optional().describe("Job queue"),
|
|
578
|
+
animation: AnimationSchema.optional().describe("Animation library"),
|
|
579
|
+
logging: LoggingSchema.optional().describe("Logging library"),
|
|
580
|
+
observability: ObservabilitySchema.optional().describe("Observability"),
|
|
581
|
+
search: SearchSchema.optional().describe("Search engine"),
|
|
582
|
+
caching: CachingSchema.optional().describe("Caching solution"),
|
|
583
|
+
cms: CMSSchema.optional().describe("CMS"),
|
|
584
|
+
fileStorage: FileStorageSchema.optional().describe("File storage"),
|
|
585
|
+
fileUpload: FileUploadSchema.optional().describe("File upload"),
|
|
586
|
+
webDeploy: WebDeploySchema.optional().describe("Web deployment target"),
|
|
587
|
+
serverDeploy: ServerDeploySchema.optional().describe("Server deployment target"),
|
|
588
|
+
dbSetup: DatabaseSetupSchema.optional().describe("Database hosting provider"),
|
|
589
|
+
rustWebFramework: RustWebFrameworkSchema.optional().describe("Rust web framework"),
|
|
590
|
+
rustFrontend: RustFrontendSchema.optional().describe("Rust frontend (WASM)"),
|
|
591
|
+
rustOrm: RustOrmSchema.optional().describe("Rust ORM"),
|
|
592
|
+
rustApi: RustApiSchema.optional().describe("Rust API layer"),
|
|
593
|
+
rustCli: RustCliSchema.optional().describe("Rust CLI framework"),
|
|
594
|
+
rustLibraries: z.array(RustLibrariesSchema).optional().describe("Rust libraries"),
|
|
595
|
+
pythonWebFramework: PythonWebFrameworkSchema.optional().describe("Python web framework"),
|
|
596
|
+
pythonOrm: PythonOrmSchema.optional().describe("Python ORM"),
|
|
597
|
+
pythonValidation: PythonValidationSchema.optional().describe("Python validation"),
|
|
598
|
+
pythonAi: z.array(PythonAiSchema).optional().describe("Python AI libraries"),
|
|
599
|
+
pythonTaskQueue: PythonTaskQueueSchema.optional().describe("Python task queue"),
|
|
600
|
+
pythonQuality: PythonQualitySchema.optional().describe("Python code quality"),
|
|
601
|
+
goWebFramework: GoWebFrameworkSchema.optional().describe("Go web framework"),
|
|
602
|
+
goOrm: GoOrmSchema.optional().describe("Go ORM"),
|
|
603
|
+
goApi: GoApiSchema.optional().describe("Go API layer"),
|
|
604
|
+
goCli: GoCliSchema.optional().describe("Go CLI framework"),
|
|
605
|
+
goLogging: GoLoggingSchema.optional().describe("Go logging library")
|
|
606
|
+
};
|
|
607
|
+
server.tool("bfs_plan_project", "Dry-run: generates a project in-memory and returns the file tree WITHOUT writing to disk. Use this to preview what would be created.", planCreateSchema, async (input) => {
|
|
608
|
+
try {
|
|
609
|
+
const { generateVirtualProject, EMBEDDED_TEMPLATES } = await import("@better-fullstack/template-generator");
|
|
610
|
+
const result = await generateVirtualProject({
|
|
611
|
+
config: buildProjectConfig(input),
|
|
612
|
+
templates: EMBEDDED_TEMPLATES
|
|
613
|
+
});
|
|
614
|
+
if (result.success && result.tree) {
|
|
615
|
+
const summary = summarizeTree(result.tree);
|
|
616
|
+
return { content: [{
|
|
617
|
+
type: "text",
|
|
618
|
+
text: JSON.stringify({
|
|
619
|
+
success: true,
|
|
620
|
+
...summary
|
|
621
|
+
}, null, 2)
|
|
622
|
+
}] };
|
|
623
|
+
}
|
|
624
|
+
return {
|
|
625
|
+
content: [{
|
|
626
|
+
type: "text",
|
|
627
|
+
text: JSON.stringify({
|
|
628
|
+
success: false,
|
|
629
|
+
error: result.error ?? "Unknown error"
|
|
630
|
+
})
|
|
631
|
+
}],
|
|
632
|
+
isError: true
|
|
633
|
+
};
|
|
634
|
+
} catch (error) {
|
|
635
|
+
return {
|
|
636
|
+
content: [{
|
|
637
|
+
type: "text",
|
|
638
|
+
text: `Plan failed: ${error instanceof Error ? error.message : String(error)}`
|
|
639
|
+
}],
|
|
640
|
+
isError: true
|
|
641
|
+
};
|
|
642
|
+
}
|
|
643
|
+
});
|
|
644
|
+
server.tool("bfs_create_project", "Creates a new fullstack project on disk. Dependencies are NOT installed (agent must tell user to install manually). Call bfs_plan_project first to preview.", {
|
|
645
|
+
...planCreateSchema,
|
|
646
|
+
projectName: z.string().describe("Project name (kebab-case). Will be the directory name.")
|
|
647
|
+
}, async (input) => {
|
|
648
|
+
try {
|
|
649
|
+
const { generateVirtualProject, EMBEDDED_TEMPLATES } = await import("@better-fullstack/template-generator");
|
|
650
|
+
const { writeTreeToFilesystem } = await import("@better-fullstack/template-generator/fs-writer");
|
|
651
|
+
const path = await import("node:path");
|
|
652
|
+
const projectName = sanitizePath(input.projectName);
|
|
653
|
+
const projectDir = path.resolve(process.cwd(), projectName);
|
|
654
|
+
const config = buildProjectConfig(input, { projectDir });
|
|
655
|
+
await (await import("node:fs/promises")).mkdir(projectDir, { recursive: true });
|
|
656
|
+
const result = await generateVirtualProject({
|
|
657
|
+
config,
|
|
658
|
+
templates: EMBEDDED_TEMPLATES
|
|
659
|
+
});
|
|
660
|
+
if (!result.success || !result.tree) return {
|
|
661
|
+
content: [{
|
|
662
|
+
type: "text",
|
|
663
|
+
text: JSON.stringify({
|
|
664
|
+
success: false,
|
|
665
|
+
error: result.error ?? "Generation failed"
|
|
666
|
+
})
|
|
667
|
+
}],
|
|
668
|
+
isError: true
|
|
669
|
+
};
|
|
670
|
+
await writeTreeToFilesystem(result.tree, projectDir);
|
|
671
|
+
await writeBtsConfig(config);
|
|
672
|
+
let addonWarnings = [];
|
|
673
|
+
if (config.addons.length > 0 && config.addons[0] !== "none") {
|
|
674
|
+
const { setupAddons } = await import("./addons-setup-AbN693PV.mjs");
|
|
675
|
+
addonWarnings = await setupAddons(config);
|
|
676
|
+
}
|
|
677
|
+
const installCmd = getInstallCommand(input.ecosystem ?? "typescript", projectName, input.packageManager);
|
|
678
|
+
return { content: [{
|
|
679
|
+
type: "text",
|
|
680
|
+
text: JSON.stringify({
|
|
681
|
+
success: true,
|
|
682
|
+
projectDirectory: projectDir,
|
|
683
|
+
fileCount: result.tree.fileCount,
|
|
684
|
+
...addonWarnings.length > 0 ? { addonWarnings } : {},
|
|
685
|
+
message: `Project created at ${projectDir}. Tell the user to run: ${installCmd}`
|
|
686
|
+
}, null, 2)
|
|
687
|
+
}] };
|
|
688
|
+
} catch (error) {
|
|
689
|
+
return {
|
|
690
|
+
content: [{
|
|
691
|
+
type: "text",
|
|
692
|
+
text: `Project creation failed: ${error instanceof Error ? error.message : String(error)}`
|
|
693
|
+
}],
|
|
694
|
+
isError: true
|
|
695
|
+
};
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
server.tool("bfs_plan_addition", "Validates what would be added to an existing project. Reads the project config (bts.jsonc) and checks which addons are new.", {
|
|
699
|
+
projectDir: z.string().describe("Absolute path to the existing project directory"),
|
|
700
|
+
addons: z.array(AddonsSchema).optional().describe("Addons to add"),
|
|
701
|
+
webDeploy: WebDeploySchema.optional().describe("Web deployment option"),
|
|
702
|
+
serverDeploy: ServerDeploySchema.optional().describe("Server deployment option")
|
|
703
|
+
}, async ({ projectDir, addons, webDeploy, serverDeploy }) => {
|
|
704
|
+
try {
|
|
705
|
+
const safePath = sanitizePath(projectDir);
|
|
706
|
+
const config = await readBtsConfig(safePath);
|
|
707
|
+
if (!config) return {
|
|
708
|
+
content: [{
|
|
709
|
+
type: "text",
|
|
710
|
+
text: JSON.stringify({
|
|
711
|
+
success: false,
|
|
712
|
+
error: `No bts.jsonc found in ${safePath}. Is this a Better-Fullstack project?`
|
|
713
|
+
})
|
|
714
|
+
}],
|
|
715
|
+
isError: true
|
|
716
|
+
};
|
|
717
|
+
const existingAddons = new Set(config.addons ?? []);
|
|
718
|
+
const newAddons = (addons ?? []).filter((a) => a !== "none" && !existingAddons.has(a));
|
|
719
|
+
const mergedAddons = [...new Set([...config.addons ?? [], ...newAddons])];
|
|
720
|
+
const compatResult = analyzeStackCompatibility(buildCompatibilityInput({
|
|
721
|
+
...config,
|
|
722
|
+
addons: mergedAddons,
|
|
723
|
+
webDeploy: webDeploy ?? config.webDeploy,
|
|
724
|
+
serverDeploy: serverDeploy ?? config.serverDeploy
|
|
725
|
+
}));
|
|
726
|
+
const compatibilityWarnings = compatResult.changes.length > 0 ? compatResult.changes.map((c) => c.message) : void 0;
|
|
727
|
+
return { content: [{
|
|
728
|
+
type: "text",
|
|
729
|
+
text: JSON.stringify({
|
|
730
|
+
success: true,
|
|
731
|
+
existingConfig: {
|
|
732
|
+
ecosystem: config.ecosystem,
|
|
733
|
+
frontend: config.frontend,
|
|
734
|
+
backend: config.backend,
|
|
735
|
+
addons: config.addons
|
|
736
|
+
},
|
|
737
|
+
proposedAdditions: {
|
|
738
|
+
newAddons,
|
|
739
|
+
webDeploy: webDeploy ?? null,
|
|
740
|
+
serverDeploy: serverDeploy ?? null
|
|
741
|
+
},
|
|
742
|
+
alreadyPresent: (addons ?? []).filter((a) => existingAddons.has(a)),
|
|
743
|
+
...compatibilityWarnings ? { compatibilityWarnings } : {}
|
|
744
|
+
}, null, 2)
|
|
745
|
+
}] };
|
|
746
|
+
} catch (error) {
|
|
747
|
+
return {
|
|
748
|
+
content: [{
|
|
749
|
+
type: "text",
|
|
750
|
+
text: `Plan addition failed: ${error instanceof Error ? error.message : String(error)}`
|
|
751
|
+
}],
|
|
752
|
+
isError: true
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
server.tool("bfs_add_feature", "Adds addons/features to an existing Better-Fullstack project. Dependencies are NOT installed. Call bfs_plan_addition first to validate.", {
|
|
757
|
+
projectDir: z.string().describe("Absolute path to the existing project directory"),
|
|
758
|
+
addons: z.array(AddonsSchema).optional().describe("Addons to add"),
|
|
759
|
+
webDeploy: WebDeploySchema.optional().describe("Web deployment option"),
|
|
760
|
+
serverDeploy: ServerDeploySchema.optional().describe("Server deployment option"),
|
|
761
|
+
packageManager: PackageManagerSchema.optional().describe("Package manager to use")
|
|
762
|
+
}, async (input) => {
|
|
763
|
+
try {
|
|
764
|
+
const safePath = sanitizePath(input.projectDir);
|
|
765
|
+
const { add } = await import("./index.mjs");
|
|
766
|
+
const result = await add({
|
|
767
|
+
addons: input.addons,
|
|
768
|
+
webDeploy: input.webDeploy,
|
|
769
|
+
serverDeploy: input.serverDeploy,
|
|
770
|
+
projectDir: safePath,
|
|
771
|
+
install: false,
|
|
772
|
+
packageManager: input.packageManager
|
|
773
|
+
});
|
|
774
|
+
if (result?.success) {
|
|
775
|
+
const installCmd = getInstallCommand((await readBtsConfig(safePath))?.ecosystem ?? "typescript", safePath.split("/").pop() ?? "project", input.packageManager);
|
|
776
|
+
return { content: [{
|
|
777
|
+
type: "text",
|
|
778
|
+
text: JSON.stringify({
|
|
779
|
+
success: true,
|
|
780
|
+
addedAddons: result.addedAddons,
|
|
781
|
+
projectDir: result.projectDir,
|
|
782
|
+
message: `Added ${result.addedAddons.join(", ")} to project. Tell the user to run: ${installCmd}`
|
|
783
|
+
}, null, 2)
|
|
784
|
+
}] };
|
|
785
|
+
}
|
|
786
|
+
return {
|
|
787
|
+
content: [{
|
|
788
|
+
type: "text",
|
|
789
|
+
text: JSON.stringify({
|
|
790
|
+
success: false,
|
|
791
|
+
error: result?.error ?? "Add command returned no result"
|
|
792
|
+
})
|
|
793
|
+
}],
|
|
794
|
+
isError: true
|
|
795
|
+
};
|
|
796
|
+
} catch (error) {
|
|
797
|
+
return {
|
|
798
|
+
content: [{
|
|
799
|
+
type: "text",
|
|
800
|
+
text: `Add feature failed: ${error instanceof Error ? error.message : String(error)}`
|
|
801
|
+
}],
|
|
802
|
+
isError: true
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
});
|
|
806
|
+
server.resource("compatibility-rules", "docs://compatibility-rules", {
|
|
807
|
+
description: "Stack compatibility rules — which frontend/backend/API/ORM combinations are valid. Read this BEFORE scaffolding.",
|
|
808
|
+
mimeType: "text/markdown"
|
|
809
|
+
}, async () => ({ contents: [{
|
|
810
|
+
uri: "docs://compatibility-rules",
|
|
811
|
+
text: COMPATIBILITY_RULES_MD
|
|
812
|
+
}] }));
|
|
813
|
+
server.resource("stack-options", "docs://stack-options", {
|
|
814
|
+
description: "All available technology options per category for every ecosystem.",
|
|
815
|
+
mimeType: "application/json"
|
|
816
|
+
}, async () => ({ contents: [{
|
|
817
|
+
uri: "docs://stack-options",
|
|
818
|
+
text: JSON.stringify(getSchemaOptions(), null, 2)
|
|
819
|
+
}] }));
|
|
820
|
+
server.resource("getting-started", "docs://getting-started", {
|
|
821
|
+
description: "Quick start guide for scaffolding projects with Better-Fullstack MCP.",
|
|
822
|
+
mimeType: "text/markdown"
|
|
823
|
+
}, async () => ({ contents: [{
|
|
824
|
+
uri: "docs://getting-started",
|
|
825
|
+
text: GETTING_STARTED_MD
|
|
826
|
+
}] }));
|
|
827
|
+
const transport = new StdioServerTransport();
|
|
828
|
+
await server.connect(transport);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
//#endregion
|
|
832
|
+
//#region src/mcp-entry.ts
|
|
833
|
+
startMcpServer();
|
|
834
|
+
|
|
835
|
+
//#endregion
|
|
836
|
+
export { startMcpServer as t };
|