create-better-fullstack 1.5.1 → 1.5.3

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