create-better-fullstack 2.0.3 → 2.1.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/dist/{add-handler-BQN6pwGJ.mjs → add-handler-BjTmOwEN.mjs} +11 -9
- package/dist/addons-setup-CSmj5ClW.mjs +6 -0
- package/dist/{addons-setup-HSghQS7c.mjs → addons-setup-uSvqHagW.mjs} +4 -83
- package/dist/{bts-config-Bg1Qea9Y.mjs → bts-config-InNcw1aJ.mjs} +90 -68
- package/dist/cli.mjs +2 -2
- package/dist/doctor-Bm9qvkya.mjs +281 -0
- package/dist/errors-ns_o2OKg.mjs +86 -0
- package/dist/generated-checks-CUhFFfVo.mjs +91 -0
- package/dist/index.d.mts +85 -29
- package/dist/index.mjs +10 -6
- package/dist/{install-dependencies-D0Z1dZEx.mjs → install-dependencies-CDjTNvIV.mjs} +120 -49
- package/dist/{mcp-58r70ZcL.mjs → mcp-BaLHDRdC.mjs} +1 -1
- package/dist/mcp-entry.mjs +601 -137
- package/dist/render-title-zvyKC1ej.mjs +34 -0
- package/dist/{run-BhcXBjxh.mjs → run-Cqy-n1iT.mjs} +548 -658
- package/dist/run-DYWSKowD.mjs +10 -0
- package/dist/templates-B351F1yX.mjs +489 -0
- package/package.json +5 -4
- package/dist/addons-setup-CV5uZyhH.mjs +0 -5
- package/dist/run-DCxVLUMS.mjs +0 -7
- /package/dist/{update-deps-CLebIM70.mjs → update-deps-D5OG0KmJ.mjs} +0 -0
package/dist/mcp-entry.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { f as getEffectiveStack, g as getGraphSummary, n as previewBtsConfigUpdate, o as writeBtsConfig, r as readBtsConfig, s as getLatestCLIVersion } from "./bts-config-InNcw1aJ.mjs";
|
|
3
|
+
import { n as getTemplateDescription, r as generateReproducibleCommand, t as getTemplateConfig } from "./templates-B351F1yX.mjs";
|
|
3
4
|
import z from "zod";
|
|
4
|
-
import { AISchema, APISchema, AddonsSchema, AnalyticsSchema, AnimationSchema, AstroIntegrationSchema, AuthSchema, BackendSchema, CATEGORY_ORDER, CMSSchema, CSSFrameworkSchema, CachingSchema, DatabaseSchema, DatabaseSetupSchema, DotnetApiSchema, DotnetAuthSchema, DotnetCachingSchema, DotnetDeploySchema, DotnetJobQueueSchema, DotnetObservabilitySchema, DotnetOrmSchema, DotnetRealtimeSchema, DotnetTestingSchema, DotnetValidationSchema, DotnetWebFrameworkSchema, EcosystemSchema, EffectSchema, ElixirApiSchema, ElixirAuthSchema, ElixirCachingSchema, ElixirDeploySchema, ElixirEmailSchema, ElixirHttpSchema, ElixirJobsSchema, ElixirJsonSchema, ElixirLibrariesSchema, ElixirObservabilitySchema, ElixirOrmSchema, ElixirQualitySchema, ElixirRealtimeSchema, ElixirTestingSchema, ElixirValidationSchema, ElixirWebFrameworkSchema, EmailSchema, ExamplesSchema, FeatureFlagsSchema, FileStorageSchema, FileUploadSchema, FormsSchema, FrontendSchema, GoApiSchema, GoAuthSchema, GoCachingSchema, GoCliSchema, GoConfigSchema, GoLoggingSchema, GoMessageQueueSchema, GoObservabilitySchema, GoOrmSchema, GoRealtimeSchema, GoTestingSchema, GoWebFrameworkSchema, I18nSchema, JavaApiSchema, JavaAuthSchema, JavaBuildToolSchema, JavaLibrariesSchema, JavaLoggingSchema, JavaOrmSchema, JavaTestingLibrariesSchema, JavaWebFrameworkSchema, JobQueueSchema, LoggingSchema, MobileDeepLinkingSchema, MobileNavigationSchema, MobileOTASchema, MobilePushSchema, MobileStorageSchema, MobileTestingSchema, MobileUISchema, OPTION_CATEGORY_METADATA, ORMSchema, ObservabilitySchema, PackageManagerSchema, PaymentsSchema, PythonAiSchema, PythonApiSchema, PythonAuthSchema, PythonCachingSchema, PythonCliSchema, PythonGraphqlSchema, PythonObservabilitySchema, PythonOrmSchema, PythonQualitySchema, PythonRealtimeSchema, PythonTaskQueueSchema, PythonTestingSchema, PythonValidationSchema, PythonWebFrameworkSchema, RateLimitSchema, RealtimeSchema, RuntimeSchema, RustApiSchema, RustAuthSchema, RustCachingSchema, RustCliSchema, RustErrorHandlingSchema, RustFrontendSchema, RustLibrariesSchema, RustLoggingSchema, RustMessageQueueSchema, RustObservabilitySchema, RustOrmSchema, RustRealtimeSchema, RustTemplatingSchema, RustWebFrameworkSchema, SearchSchema, ServerDeploySchema, StateManagementSchema, TestingSchema, UILibrarySchema, ValidationSchema, WebDeploySchema, analyzeStackCompatibility, formatStackPartSpec, getCategoryOrderForEcosystem, legacyProjectConfigToStackParts } from "@better-fullstack/types";
|
|
5
|
+
import { AISchema, APISchema, AddonsSchema, AiDocsSchema, AnalyticsSchema, AnimationSchema, AstroIntegrationSchema, AuthSchema, BackendSchema, CATEGORY_ORDER, CMSSchema, CSSFrameworkSchema, CachingSchema, DatabaseSchema, DatabaseSetupSchema, DotnetApiSchema, DotnetAuthSchema, DotnetCachingSchema, DotnetDeploySchema, DotnetJobQueueSchema, DotnetObservabilitySchema, DotnetOrmSchema, DotnetRealtimeSchema, DotnetTestingSchema, DotnetValidationSchema, DotnetWebFrameworkSchema, EcosystemSchema, EffectSchema, ElixirApiSchema, ElixirAuthSchema, ElixirCachingSchema, ElixirDeploySchema, ElixirEmailSchema, ElixirHttpSchema, ElixirJobsSchema, ElixirJsonSchema, ElixirLibrariesSchema, ElixirObservabilitySchema, ElixirOrmSchema, ElixirQualitySchema, ElixirRealtimeSchema, ElixirTestingSchema, ElixirValidationSchema, ElixirWebFrameworkSchema, EmailSchema, ExamplesSchema, FeatureFlagsSchema, FileStorageSchema, FileUploadSchema, FormsSchema, FrontendSchema, GoApiSchema, GoAuthSchema, GoCachingSchema, GoCliSchema, GoConfigSchema, GoLoggingSchema, GoMessageQueueSchema, GoObservabilitySchema, GoOrmSchema, GoRealtimeSchema, GoTestingSchema, GoWebFrameworkSchema, I18nSchema, JavaApiSchema, JavaAuthSchema, JavaBuildToolSchema, JavaLibrariesSchema, JavaLoggingSchema, JavaOrmSchema, JavaTestingLibrariesSchema, JavaWebFrameworkSchema, JobQueueSchema, LoggingSchema, MobileDeepLinkingSchema, MobileNavigationSchema, MobileOTASchema, MobilePushSchema, MobileStorageSchema, MobileTestingSchema, MobileUISchema, OPTION_CATEGORY_METADATA, ORMSchema, ObservabilitySchema, PackageManagerSchema, PaymentsSchema, PythonAiSchema, PythonApiSchema, PythonAuthSchema, PythonCachingSchema, PythonCliSchema, PythonGraphqlSchema, PythonObservabilitySchema, PythonOrmSchema, PythonQualitySchema, PythonRealtimeSchema, PythonTaskQueueSchema, PythonTestingSchema, PythonValidationSchema, PythonWebFrameworkSchema, RateLimitSchema, RealtimeSchema, RuntimeSchema, RustApiSchema, RustAuthSchema, RustCachingSchema, RustCliSchema, RustErrorHandlingSchema, RustFrontendSchema, RustLibrariesSchema, RustLoggingSchema, RustMessageQueueSchema, RustObservabilitySchema, RustOrmSchema, RustRealtimeSchema, RustTemplatingSchema, RustWebFrameworkSchema, SearchSchema, ServerDeploySchema, StateManagementSchema, TEMPLATE_VALUES, TestingSchema, UILibrarySchema, ValidationSchema, VectorDbSchema, VersionChannelSchema, WebDeploySchema, analyzeStackCompatibility, evaluateCompatibility, formatStackPartSpec, getCategoryOrderForEcosystem, legacyProjectConfigToStackParts } from "@better-fullstack/types";
|
|
5
6
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
7
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
7
8
|
|
|
@@ -56,7 +57,8 @@ function getGuidance() {
|
|
|
56
57
|
addons: "ARRAY of strings. Monorepo tools, code quality, desktop (tauri), browser extensions (wxt), etc.",
|
|
57
58
|
email: "String. TypeScript supports multiple providers; Rust, Python, Go, and Java currently support resend or none.",
|
|
58
59
|
observability: "String. TypeScript supports multiple providers; Rust, Python, Go, and Java currently support sentry or none.",
|
|
59
|
-
search: "String. TypeScript supports multiple providers; Rust, Python, Go, and Java currently support meilisearch or none."
|
|
60
|
+
search: "String. TypeScript supports multiple providers; Rust, Python, Go, and Java currently support meilisearch or none.",
|
|
61
|
+
vectorDb: "String. TypeScript-only vector database for AI embeddings: pgvector, qdrant, chroma, pinecone, or none. Each provider is a standalone service (pgvector connects to a dedicated Postgres+pgvector instance via PGVECTOR_DATABASE_URL). Requires a standalone backend (not convex/none)."
|
|
60
62
|
},
|
|
61
63
|
ambiguityRules: [
|
|
62
64
|
"If the user request leaves major stack choices unspecified, ASK the user before proceeding. Do not guess.",
|
|
@@ -245,6 +247,7 @@ const MCP_COMPATIBILITY_DEFAULTS = {
|
|
|
245
247
|
shadcnRadius: "default",
|
|
246
248
|
cms: "none",
|
|
247
249
|
search: "none",
|
|
250
|
+
vectorDb: "none",
|
|
248
251
|
fileStorage: "none",
|
|
249
252
|
mobileUI: "none",
|
|
250
253
|
mobileStorage: "none",
|
|
@@ -255,7 +258,7 @@ const MCP_COMPATIBILITY_DEFAULTS = {
|
|
|
255
258
|
versionChannel: "stable",
|
|
256
259
|
examples: [],
|
|
257
260
|
aiSdk: "none",
|
|
258
|
-
aiDocs: ["claude-md"],
|
|
261
|
+
aiDocs: ["claude-md", "agents-md"],
|
|
259
262
|
git: "true",
|
|
260
263
|
install: "false",
|
|
261
264
|
api: "none",
|
|
@@ -376,8 +379,6 @@ function buildProjectConfig(input, overrides) {
|
|
|
376
379
|
cssFramework: input.cssFramework ?? (ecosystem === "react-native" ? "none" : defaults.cssFramework),
|
|
377
380
|
mobileNavigation: input.mobileNavigation ?? (hasMobileProject ? "expo-router" : "none"),
|
|
378
381
|
mobileDeepLinking: input.mobileDeepLinking ?? (hasMobileProject ? "expo-linking" : "none"),
|
|
379
|
-
astroIntegration: "none",
|
|
380
|
-
versionChannel: "stable",
|
|
381
382
|
shadcnBase: "radix",
|
|
382
383
|
shadcnStyle: "nova",
|
|
383
384
|
shadcnIconLibrary: "lucide",
|
|
@@ -385,7 +386,7 @@ function buildProjectConfig(input, overrides) {
|
|
|
385
386
|
shadcnBaseColor: "neutral",
|
|
386
387
|
shadcnFont: "inter",
|
|
387
388
|
shadcnRadius: "default",
|
|
388
|
-
aiDocs: ["claude-md"],
|
|
389
|
+
aiDocs: input.aiDocs ?? ["claude-md", "agents-md"],
|
|
389
390
|
git: !!overrides,
|
|
390
391
|
install: false
|
|
391
392
|
};
|
|
@@ -565,6 +566,279 @@ const GETTING_STARTED_MD = `# Getting Started with Better-Fullstack MCP
|
|
|
565
566
|
2. Provide addons array with features to add (e.g., ["biome", "turborepo"]).
|
|
566
567
|
3. Service categories such as email and observability are scaffold-time options. To add those to an existing app, inspect the generated templates from bfs_plan_project and apply the equivalent dependency, env var, and initialization changes manually.
|
|
567
568
|
`;
|
|
569
|
+
const guidanceOutputSchema = {
|
|
570
|
+
workflow: z.array(z.string()),
|
|
571
|
+
ecosystems: z.record(z.string(), z.string()),
|
|
572
|
+
fieldRules: z.record(z.string(), z.string()),
|
|
573
|
+
ambiguityRules: z.array(z.string()),
|
|
574
|
+
criticalConstraints: z.array(z.string())
|
|
575
|
+
};
|
|
576
|
+
const schemaOutputSchema = {
|
|
577
|
+
category: z.string().optional(),
|
|
578
|
+
options: z.array(z.string()).optional(),
|
|
579
|
+
categories: z.record(z.string(), z.array(z.string())).optional(),
|
|
580
|
+
error: z.string().optional()
|
|
581
|
+
};
|
|
582
|
+
const compatibilityIssueOutputSchema = z.object({
|
|
583
|
+
code: z.string(),
|
|
584
|
+
message: z.string(),
|
|
585
|
+
category: z.string().optional(),
|
|
586
|
+
optionId: z.string().optional(),
|
|
587
|
+
provided: z.record(z.string(), z.union([z.string(), z.array(z.string())])).optional(),
|
|
588
|
+
suggestions: z.array(z.string()).optional()
|
|
589
|
+
});
|
|
590
|
+
const compatibilityOutputSchema = {
|
|
591
|
+
adjustedStack: z.record(z.string(), z.unknown()).nullable(),
|
|
592
|
+
changes: z.array(z.object({
|
|
593
|
+
category: z.string(),
|
|
594
|
+
message: z.string()
|
|
595
|
+
})),
|
|
596
|
+
issues: z.array(compatibilityIssueOutputSchema),
|
|
597
|
+
hasIssues: z.boolean()
|
|
598
|
+
};
|
|
599
|
+
const graphPreviewOutputShape = {
|
|
600
|
+
graphSummary: z.string().optional(),
|
|
601
|
+
effectiveStack: z.record(z.string(), z.string()).optional(),
|
|
602
|
+
stackPartSpecs: z.array(z.string()).optional()
|
|
603
|
+
};
|
|
604
|
+
const planProjectOutputSchema = {
|
|
605
|
+
success: z.boolean(),
|
|
606
|
+
fileCount: z.number().optional(),
|
|
607
|
+
directoryCount: z.number().optional(),
|
|
608
|
+
files: z.array(z.string()).optional(),
|
|
609
|
+
...graphPreviewOutputShape
|
|
610
|
+
};
|
|
611
|
+
const createProjectOutputSchema = {
|
|
612
|
+
success: z.boolean(),
|
|
613
|
+
projectDirectory: z.string().optional(),
|
|
614
|
+
fileCount: z.number().optional(),
|
|
615
|
+
addonWarnings: z.array(z.string()).optional(),
|
|
616
|
+
message: z.string().optional(),
|
|
617
|
+
...graphPreviewOutputShape
|
|
618
|
+
};
|
|
619
|
+
const planAdditionOutputSchema = {
|
|
620
|
+
success: z.boolean(),
|
|
621
|
+
existingConfig: z.object({
|
|
622
|
+
ecosystem: z.string(),
|
|
623
|
+
frontend: z.array(z.string()).optional(),
|
|
624
|
+
backend: z.string().optional(),
|
|
625
|
+
addons: z.array(z.string()).optional(),
|
|
626
|
+
graphSummary: z.string().optional(),
|
|
627
|
+
effectiveStack: z.record(z.string(), z.string()).optional(),
|
|
628
|
+
stackPartSpecs: z.array(z.string())
|
|
629
|
+
}).optional(),
|
|
630
|
+
proposedAdditions: z.object({
|
|
631
|
+
newAddons: z.array(z.string()),
|
|
632
|
+
webDeploy: z.string().nullable(),
|
|
633
|
+
serverDeploy: z.string().nullable(),
|
|
634
|
+
graphSummary: z.string().optional(),
|
|
635
|
+
effectiveStack: z.record(z.string(), z.string()).optional(),
|
|
636
|
+
stackPartSpecs: z.array(z.string())
|
|
637
|
+
}).optional(),
|
|
638
|
+
alreadyPresent: z.array(z.string()).optional(),
|
|
639
|
+
compatibilityWarnings: z.array(z.string()).optional()
|
|
640
|
+
};
|
|
641
|
+
const addFeatureOutputSchema = {
|
|
642
|
+
success: z.boolean(),
|
|
643
|
+
addedAddons: z.array(z.string()).optional(),
|
|
644
|
+
projectDir: z.string().optional(),
|
|
645
|
+
message: z.string().optional(),
|
|
646
|
+
...graphPreviewOutputShape
|
|
647
|
+
};
|
|
648
|
+
function buildPresetStackSummary(config) {
|
|
649
|
+
const parts = [];
|
|
650
|
+
const frontend = (config.frontend ?? []).filter((item) => item !== "none");
|
|
651
|
+
if (frontend.length > 0) parts.push(`frontend: ${frontend.join("+")}`);
|
|
652
|
+
if (config.backend && config.backend !== "none") parts.push(`backend: ${config.backend}`);
|
|
653
|
+
if (config.runtime && config.runtime !== "none") parts.push(`runtime: ${config.runtime}`);
|
|
654
|
+
if (config.database && config.database !== "none") parts.push(`database: ${config.database}`);
|
|
655
|
+
if (config.orm && config.orm !== "none") parts.push(`orm: ${config.orm}`);
|
|
656
|
+
if (config.api && config.api !== "none") parts.push(`api: ${config.api}`);
|
|
657
|
+
if (config.auth && config.auth !== "none") parts.push(`auth: ${config.auth}`);
|
|
658
|
+
if (config.payments && config.payments !== "none") parts.push(`payments: ${config.payments}`);
|
|
659
|
+
const addons = (config.addons ?? []).filter((item) => item !== "none");
|
|
660
|
+
if (addons.length > 0) parts.push(`addons: ${addons.join("+")}`);
|
|
661
|
+
return parts.join(", ");
|
|
662
|
+
}
|
|
663
|
+
function listMcpPresets() {
|
|
664
|
+
const presets = [];
|
|
665
|
+
for (const id of TEMPLATE_VALUES) {
|
|
666
|
+
if (id === "none") continue;
|
|
667
|
+
const config = getTemplateConfig(id);
|
|
668
|
+
if (!config) continue;
|
|
669
|
+
const ecosystem = (config.frontend ?? []).some((item) => item.startsWith("native-")) ? "react-native" : "typescript";
|
|
670
|
+
presets.push({
|
|
671
|
+
id,
|
|
672
|
+
name: id.toUpperCase(),
|
|
673
|
+
description: getTemplateDescription(id),
|
|
674
|
+
ecosystem,
|
|
675
|
+
stackSummary: buildPresetStackSummary(config),
|
|
676
|
+
stack: config
|
|
677
|
+
});
|
|
678
|
+
}
|
|
679
|
+
return presets;
|
|
680
|
+
}
|
|
681
|
+
function briefMatches(text, keywords) {
|
|
682
|
+
const tokens = new Set(text.split(/[^a-z0-9]+/i).filter(Boolean));
|
|
683
|
+
return keywords.some((keyword) => keyword.includes(" ") ? text.includes(keyword) : tokens.has(keyword));
|
|
684
|
+
}
|
|
685
|
+
function matchNearestPreset(input) {
|
|
686
|
+
const signatureKeys = [
|
|
687
|
+
"database",
|
|
688
|
+
"backend",
|
|
689
|
+
"api",
|
|
690
|
+
"auth"
|
|
691
|
+
];
|
|
692
|
+
const inputFrontend = input.frontend?.[0];
|
|
693
|
+
let best = null;
|
|
694
|
+
for (const id of TEMPLATE_VALUES) {
|
|
695
|
+
if (id === "none") continue;
|
|
696
|
+
const config = getTemplateConfig(id);
|
|
697
|
+
if (!config) continue;
|
|
698
|
+
let score = 0;
|
|
699
|
+
for (const key of signatureKeys) {
|
|
700
|
+
const value = config[key];
|
|
701
|
+
if (value !== void 0 && value === input[key]) score += 1;
|
|
702
|
+
}
|
|
703
|
+
const presetFrontend = (config.frontend ?? [])[0];
|
|
704
|
+
if (presetFrontend && presetFrontend === inputFrontend) score += 1;
|
|
705
|
+
if (!best || score > best.score) best = {
|
|
706
|
+
id,
|
|
707
|
+
score
|
|
708
|
+
};
|
|
709
|
+
}
|
|
710
|
+
return best && best.score >= 3 ? best.id : null;
|
|
711
|
+
}
|
|
712
|
+
function recommendStackFromBrief(brief, ecosystemHint) {
|
|
713
|
+
const text = brief.toLowerCase();
|
|
714
|
+
const has = (...keywords) => briefMatches(text, keywords);
|
|
715
|
+
const rationale = [];
|
|
716
|
+
const input = {};
|
|
717
|
+
if (ecosystemHint && ecosystemHint !== "typescript") {
|
|
718
|
+
input.ecosystem = ecosystemHint;
|
|
719
|
+
rationale.push(`Ecosystem forced to ${ecosystemHint} from the provided hint; using ${ecosystemHint} defaults.`);
|
|
720
|
+
rationale.push(`Brief keyword analysis (database/auth/payments/AI feature detection) currently applies to the TypeScript ecosystem only — configure those features explicitly for ${ecosystemHint} via bfs_check_compatibility.`);
|
|
721
|
+
return {
|
|
722
|
+
input,
|
|
723
|
+
rationale,
|
|
724
|
+
matchedPreset: null
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
if (has("mobile", "ios", "android", "expo") || text.includes("react native") || text.includes("react-native") || text.includes("app store") || text.includes("play store")) {
|
|
728
|
+
input.ecosystem = "react-native";
|
|
729
|
+
input.frontend = ["native-uniwind"];
|
|
730
|
+
input.backend = "none";
|
|
731
|
+
input.runtime = "none";
|
|
732
|
+
input.api = "none";
|
|
733
|
+
input.database = "none";
|
|
734
|
+
input.orm = "none";
|
|
735
|
+
rationale.push("Mobile app detected: React Native (Expo) with the native-uniwind styling preset and no bundled backend.");
|
|
736
|
+
return {
|
|
737
|
+
input,
|
|
738
|
+
rationale,
|
|
739
|
+
matchedPreset: "uniwind"
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
input.ecosystem = "typescript";
|
|
743
|
+
input.frontend = ["tanstack-router"];
|
|
744
|
+
input.backend = "hono";
|
|
745
|
+
input.runtime = "bun";
|
|
746
|
+
input.database = "sqlite";
|
|
747
|
+
input.orm = "drizzle";
|
|
748
|
+
input.api = "trpc";
|
|
749
|
+
rationale.push("Default TypeScript fullstack baseline: TanStack Router + Hono + tRPC on SQLite/Drizzle (Bun).");
|
|
750
|
+
if (has("postgres", "postgresql", "supabase", "neon")) {
|
|
751
|
+
input.database = "postgres";
|
|
752
|
+
input.orm = "drizzle";
|
|
753
|
+
rationale.push("Postgres requested: database=postgres, orm=drizzle.");
|
|
754
|
+
} else if (has("mysql", "planetscale")) {
|
|
755
|
+
input.database = "mysql";
|
|
756
|
+
input.orm = "drizzle";
|
|
757
|
+
rationale.push("MySQL requested: database=mysql, orm=drizzle.");
|
|
758
|
+
} else if (has("mongo", "mongodb")) {
|
|
759
|
+
input.database = "mongodb";
|
|
760
|
+
input.orm = "mongoose";
|
|
761
|
+
rationale.push("MongoDB requested: database=mongodb, orm=mongoose.");
|
|
762
|
+
}
|
|
763
|
+
if (has("saas", "payment", "payments", "billing", "subscription", "subscriptions", "checkout", "stripe", "ecommerce")) {
|
|
764
|
+
input.payments = "stripe";
|
|
765
|
+
input.auth = "better-auth";
|
|
766
|
+
if (input.database === "sqlite") {
|
|
767
|
+
input.database = "postgres";
|
|
768
|
+
input.orm = "drizzle";
|
|
769
|
+
}
|
|
770
|
+
rationale.push("SaaS/payments detected: Stripe + better-auth, upgraded to Postgres/Drizzle for production data.");
|
|
771
|
+
} else if (has("auth", "login", "signin", "signup", "account", "accounts", "user", "users", "authentication", "admin", "dashboard", "portal", "members", "rbac", "permissions", "roles")) {
|
|
772
|
+
input.auth = "better-auth";
|
|
773
|
+
rationale.push("Authentication requested: auth=better-auth.");
|
|
774
|
+
}
|
|
775
|
+
if (has("ai", "chatbot", "llm", "gpt", "rag", "agent", "agents", "openai", "assistant", "copilot")) {
|
|
776
|
+
input.ai = "vercel-ai";
|
|
777
|
+
input.examples = ["ai"];
|
|
778
|
+
rationale.push("AI/chatbot detected: Vercel AI SDK with the bundled AI example.");
|
|
779
|
+
}
|
|
780
|
+
if (has("blog", "content", "cms", "marketing", "landing", "publishing")) {
|
|
781
|
+
input.cms = "sanity";
|
|
782
|
+
rationale.push("Content/marketing site detected: Sanity CMS.");
|
|
783
|
+
}
|
|
784
|
+
if (has("realtime", "collaborative", "collaboration", "multiplayer", "presence") || text.includes("real-time") || text.includes("real time")) {
|
|
785
|
+
input.realtime = "socket-io";
|
|
786
|
+
rationale.push("Realtime/collaboration detected: Socket.IO.");
|
|
787
|
+
}
|
|
788
|
+
let matchedPreset = null;
|
|
789
|
+
if (has("t3")) matchedPreset = "t3";
|
|
790
|
+
else if (has("mern")) matchedPreset = "mern";
|
|
791
|
+
else if (has("pern")) matchedPreset = "pern";
|
|
792
|
+
else matchedPreset = matchNearestPreset(input);
|
|
793
|
+
if (matchedPreset) rationale.push(`Closest ready-made preset: ${matchedPreset}.`);
|
|
794
|
+
return {
|
|
795
|
+
input,
|
|
796
|
+
rationale,
|
|
797
|
+
matchedPreset
|
|
798
|
+
};
|
|
799
|
+
}
|
|
800
|
+
function normalizeAdjustedToInput(adjusted, base) {
|
|
801
|
+
const webFrontend = adjusted.webFrontend ?? [];
|
|
802
|
+
const nativeFrontend = adjusted.nativeFrontend ?? [];
|
|
803
|
+
const frontend = [...webFrontend, ...nativeFrontend];
|
|
804
|
+
const codeQuality = adjusted.codeQuality ?? [];
|
|
805
|
+
const documentation = adjusted.documentation ?? [];
|
|
806
|
+
const appPlatforms = adjusted.appPlatforms ?? [];
|
|
807
|
+
return {
|
|
808
|
+
...adjusted,
|
|
809
|
+
projectName: base.projectName,
|
|
810
|
+
ecosystem: adjusted.ecosystem ?? base.ecosystem,
|
|
811
|
+
frontend: frontend.length > 0 ? frontend : base.frontend,
|
|
812
|
+
addons: [
|
|
813
|
+
...codeQuality,
|
|
814
|
+
...documentation,
|
|
815
|
+
...appPlatforms
|
|
816
|
+
],
|
|
817
|
+
ai: adjusted.aiSdk ?? base.ai
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
function summarizeRecommendedConfig(config) {
|
|
821
|
+
const isTsWeb = config.ecosystem === "typescript" || config.ecosystem === "react-native";
|
|
822
|
+
return {
|
|
823
|
+
projectName: config.projectName,
|
|
824
|
+
ecosystem: config.ecosystem,
|
|
825
|
+
...isTsWeb ? {
|
|
826
|
+
frontend: config.frontend,
|
|
827
|
+
backend: config.backend,
|
|
828
|
+
runtime: config.runtime,
|
|
829
|
+
api: config.api
|
|
830
|
+
} : {},
|
|
831
|
+
database: config.database,
|
|
832
|
+
orm: config.orm,
|
|
833
|
+
auth: config.auth,
|
|
834
|
+
payments: config.payments,
|
|
835
|
+
ai: config.ai,
|
|
836
|
+
cms: config.cms,
|
|
837
|
+
realtime: config.realtime,
|
|
838
|
+
examples: config.examples,
|
|
839
|
+
addons: config.addons
|
|
840
|
+
};
|
|
841
|
+
}
|
|
568
842
|
async function startMcpServer() {
|
|
569
843
|
const server = new McpServer({
|
|
570
844
|
name: "better-fullstack",
|
|
@@ -573,24 +847,120 @@ async function startMcpServer() {
|
|
|
573
847
|
instructions: INSTRUCTIONS,
|
|
574
848
|
capabilities: { logging: {} }
|
|
575
849
|
});
|
|
576
|
-
const registerTool =
|
|
577
|
-
|
|
850
|
+
const registerTool = (name, config, cb) => {
|
|
851
|
+
server.registerTool(name, config, cb);
|
|
852
|
+
};
|
|
853
|
+
registerTool("bfs_get_guidance", {
|
|
854
|
+
description: "Returns workflow rules, field semantics, ambiguity rules, and critical constraints. Call this FIRST before using other tools.",
|
|
855
|
+
inputSchema: mcpInputSchema({}),
|
|
856
|
+
outputSchema: guidanceOutputSchema,
|
|
857
|
+
annotations: {
|
|
858
|
+
title: "Get guidance",
|
|
859
|
+
readOnlyHint: true,
|
|
860
|
+
idempotentHint: true,
|
|
861
|
+
openWorldHint: false
|
|
862
|
+
}
|
|
863
|
+
}, async () => {
|
|
578
864
|
const guidance = getGuidance();
|
|
579
|
-
return {
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
865
|
+
return {
|
|
866
|
+
content: [{
|
|
867
|
+
type: "text",
|
|
868
|
+
text: JSON.stringify(guidance, null, 2)
|
|
869
|
+
}],
|
|
870
|
+
structuredContent: guidance
|
|
871
|
+
};
|
|
583
872
|
});
|
|
584
|
-
registerTool("bfs_get_schema",
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
873
|
+
registerTool("bfs_get_schema", {
|
|
874
|
+
description: "Returns valid options for a specific category (e.g., 'database', 'frontend', 'backend') or ALL categories. Use ecosystem to filter to relevant categories only.",
|
|
875
|
+
inputSchema: mcpInputSchema({
|
|
876
|
+
category: z.string().optional().describe("Category name (e.g., 'database', 'orm', 'frontend'). Omit for all categories."),
|
|
877
|
+
ecosystem: EcosystemSchema.optional().describe("Filter categories to this ecosystem (e.g., 'rust' returns only Rust + shared categories).")
|
|
878
|
+
}),
|
|
879
|
+
outputSchema: schemaOutputSchema,
|
|
880
|
+
annotations: {
|
|
881
|
+
title: "Get schema options",
|
|
882
|
+
readOnlyHint: true,
|
|
883
|
+
idempotentHint: true,
|
|
884
|
+
openWorldHint: false
|
|
885
|
+
}
|
|
886
|
+
}, async ({ category, ecosystem }) => {
|
|
588
887
|
const result = getSchemaOptions(category, ecosystem);
|
|
888
|
+
const structuredContent = "error" in result ? { error: result.error } : "category" in result ? {
|
|
889
|
+
category: result.category,
|
|
890
|
+
options: result.options
|
|
891
|
+
} : { categories: result };
|
|
892
|
+
return {
|
|
893
|
+
content: [{
|
|
894
|
+
type: "text",
|
|
895
|
+
text: JSON.stringify(result, null, 2)
|
|
896
|
+
}],
|
|
897
|
+
structuredContent
|
|
898
|
+
};
|
|
899
|
+
});
|
|
900
|
+
registerTool("bfs_list_presets", {
|
|
901
|
+
description: "Lists the ready-made stack presets available to the CLI (mern, pern, t3, uniwind) with id, name, description, ecosystem, and a stack summary. Use to discover a starting point before bfs_recommend_stack, bfs_plan_project, or bfs_create_project.",
|
|
902
|
+
inputSchema: mcpInputSchema({}),
|
|
903
|
+
annotations: {
|
|
904
|
+
title: "List presets",
|
|
905
|
+
readOnlyHint: true,
|
|
906
|
+
idempotentHint: true,
|
|
907
|
+
openWorldHint: false
|
|
908
|
+
}
|
|
909
|
+
}, async () => {
|
|
910
|
+
const presets = listMcpPresets();
|
|
589
911
|
return { content: [{
|
|
590
912
|
type: "text",
|
|
591
|
-
text: JSON.stringify(
|
|
913
|
+
text: JSON.stringify({ presets }, null, 2)
|
|
592
914
|
}] };
|
|
593
915
|
});
|
|
916
|
+
registerTool("bfs_recommend_stack", {
|
|
917
|
+
description: "Recommends a compatibility-validated stack from a natural-language brief using deterministic keyword rules (no LLM). Returns the config, rationale, any auto-applied compatibility adjustments, the nearest matching preset, and a reproducible CLI command.",
|
|
918
|
+
inputSchema: mcpInputSchema({
|
|
919
|
+
brief: z.string().describe("Natural-language description of the app to build (e.g., 'a SaaS with payments and auth')."),
|
|
920
|
+
ecosystem: EcosystemSchema.optional().describe("Force a language ecosystem. Omit to let the brief decide (defaults to TypeScript)."),
|
|
921
|
+
projectName: z.string().optional().describe("Project name (kebab-case). Default: 'my-app'.")
|
|
922
|
+
}),
|
|
923
|
+
annotations: {
|
|
924
|
+
title: "Recommend stack",
|
|
925
|
+
readOnlyHint: true,
|
|
926
|
+
idempotentHint: false,
|
|
927
|
+
openWorldHint: false
|
|
928
|
+
}
|
|
929
|
+
}, async ({ brief, ecosystem, projectName }) => {
|
|
930
|
+
try {
|
|
931
|
+
const { input: recommended, rationale, matchedPreset } = recommendStackFromBrief(brief, ecosystem);
|
|
932
|
+
const baseInput = {
|
|
933
|
+
projectName: projectName ?? "my-app",
|
|
934
|
+
...recommended
|
|
935
|
+
};
|
|
936
|
+
const compatResult = analyzeStackCompatibility(buildCompatibilityInput(baseInput));
|
|
937
|
+
const finalConfig = buildProjectConfig(compatResult.adjustedStack ? normalizeAdjustedToInput(compatResult.adjustedStack, baseInput) : baseInput, { projectDir: `/${baseInput.projectName}` });
|
|
938
|
+
const adjustments = compatResult.changes.map((change) => `${change.category}: ${change.message}`);
|
|
939
|
+
const graphPreview = getMcpGraphPreview(finalConfig);
|
|
940
|
+
const reproducibleCommand = generateReproducibleCommand(finalConfig);
|
|
941
|
+
return { content: [{
|
|
942
|
+
type: "text",
|
|
943
|
+
text: JSON.stringify({
|
|
944
|
+
brief,
|
|
945
|
+
config: summarizeRecommendedConfig(finalConfig),
|
|
946
|
+
rationale,
|
|
947
|
+
adjustments,
|
|
948
|
+
matchedPreset,
|
|
949
|
+
reproducibleCommand,
|
|
950
|
+
...graphPreview,
|
|
951
|
+
nextSteps: "Call bfs_plan_project with this config to preview the files, then bfs_create_project to scaffold it."
|
|
952
|
+
}, null, 2)
|
|
953
|
+
}] };
|
|
954
|
+
} catch (error) {
|
|
955
|
+
return {
|
|
956
|
+
content: [{
|
|
957
|
+
type: "text",
|
|
958
|
+
text: `Recommend stack failed: ${error instanceof Error ? error.message : String(error)}`
|
|
959
|
+
}],
|
|
960
|
+
isError: true
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
});
|
|
594
964
|
const mobileInputSchema = {
|
|
595
965
|
mobileNavigation: MobileNavigationSchema.optional().describe("Mobile navigation"),
|
|
596
966
|
mobileUI: MobileUISchema.optional().describe("Mobile UI"),
|
|
@@ -682,52 +1052,80 @@ async function startMcpServer() {
|
|
|
682
1052
|
elixirDeploy: ElixirDeploySchema.optional().describe("Elixir deployment target"),
|
|
683
1053
|
elixirLibraries: z.array(ElixirLibrariesSchema).optional().describe("Elixir libraries")
|
|
684
1054
|
};
|
|
685
|
-
registerTool("bfs_check_compatibility",
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
1055
|
+
registerTool("bfs_check_compatibility", {
|
|
1056
|
+
description: "Validates a stack combination and returns auto-adjusted selections with warnings. Call BEFORE creating a project to avoid invalid combinations.",
|
|
1057
|
+
outputSchema: compatibilityOutputSchema,
|
|
1058
|
+
annotations: {
|
|
1059
|
+
title: "Check stack compatibility",
|
|
1060
|
+
readOnlyHint: true,
|
|
1061
|
+
idempotentHint: true,
|
|
1062
|
+
openWorldHint: false
|
|
1063
|
+
},
|
|
1064
|
+
inputSchema: mcpInputSchema({
|
|
1065
|
+
ecosystem: EcosystemSchema.describe("Language ecosystem"),
|
|
1066
|
+
frontend: z.array(z.string()).optional().describe("Web frontend frameworks (TypeScript only)"),
|
|
1067
|
+
backend: z.string().optional().describe("Backend framework"),
|
|
1068
|
+
runtime: z.string().optional().describe("JavaScript runtime"),
|
|
1069
|
+
database: z.string().optional().describe("Database type"),
|
|
1070
|
+
orm: z.string().optional().describe("ORM"),
|
|
1071
|
+
api: z.string().optional().describe("API layer"),
|
|
1072
|
+
auth: z.string().optional().describe("Auth provider"),
|
|
1073
|
+
payments: z.string().optional().describe("Payments provider"),
|
|
1074
|
+
email: EmailSchema.optional().describe("Email provider"),
|
|
1075
|
+
fileUpload: FileUploadSchema.optional().describe("File upload provider"),
|
|
1076
|
+
ai: AISchema.optional().describe("AI SDK"),
|
|
1077
|
+
stateManagement: StateManagementSchema.optional().describe("State management"),
|
|
1078
|
+
forms: FormsSchema.optional().describe("Forms library"),
|
|
1079
|
+
validation: ValidationSchema.optional().describe("Validation library"),
|
|
1080
|
+
testing: TestingSchema.optional().describe("Testing framework"),
|
|
1081
|
+
realtime: RealtimeSchema.optional().describe("Realtime library"),
|
|
1082
|
+
jobQueue: JobQueueSchema.optional().describe("Job queue"),
|
|
1083
|
+
animation: AnimationSchema.optional().describe("Animation library"),
|
|
1084
|
+
logging: LoggingSchema.optional().describe("Logging library"),
|
|
1085
|
+
observability: ObservabilitySchema.optional().describe("Observability provider"),
|
|
1086
|
+
featureFlags: FeatureFlagsSchema.optional().describe("Feature flags provider"),
|
|
1087
|
+
analytics: AnalyticsSchema.optional().describe("Analytics provider"),
|
|
1088
|
+
cms: CMSSchema.optional().describe("CMS"),
|
|
1089
|
+
caching: CachingSchema.optional().describe("Caching solution"),
|
|
1090
|
+
rateLimit: RateLimitSchema.optional().describe("Rate limiting solution"),
|
|
1091
|
+
i18n: I18nSchema.optional().describe("Internationalization library"),
|
|
1092
|
+
search: SearchSchema.optional().describe("Search engine"),
|
|
1093
|
+
vectorDb: VectorDbSchema.optional().describe("Vector database (TypeScript only)"),
|
|
1094
|
+
fileStorage: FileStorageSchema.optional().describe("File storage"),
|
|
1095
|
+
...mobileInputSchema,
|
|
1096
|
+
...deploymentInputSchema,
|
|
1097
|
+
astroIntegration: AstroIntegrationSchema.optional().describe("Astro UI framework integration"),
|
|
1098
|
+
uiLibrary: z.string().optional().describe("UI component library"),
|
|
1099
|
+
cssFramework: z.string().optional().describe("CSS framework"),
|
|
1100
|
+
addons: z.array(AddonsSchema).optional().describe("Addon list"),
|
|
1101
|
+
examples: z.array(ExamplesSchema).optional().describe("Example templates"),
|
|
1102
|
+
packageManager: PackageManagerSchema.optional().describe("Package manager"),
|
|
1103
|
+
...crossEcosystemInputSchema
|
|
1104
|
+
})
|
|
1105
|
+
}, async (input) => {
|
|
725
1106
|
try {
|
|
726
|
-
const
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
1107
|
+
const compatInput = buildCompatibilityInput(input);
|
|
1108
|
+
const filtered = filterCompatibilityResult(analyzeStackCompatibility(compatInput), input.ecosystem);
|
|
1109
|
+
const evaluation = evaluateCompatibility(compatInput);
|
|
1110
|
+
const relevantEcosystem = isMcpEcosystem(input.ecosystem) ? input.ecosystem : "typescript";
|
|
1111
|
+
const relevantIssueKeys = new Set([...getMcpCategoryKeysForEcosystem(relevantEcosystem), ...MCP_SHARED_COMPATIBILITY_KEYS]);
|
|
1112
|
+
const issues = evaluation.issues.filter((issue) => !issue.category || relevantIssueKeys.has(issue.category));
|
|
1113
|
+
const structuredContent = {
|
|
1114
|
+
adjustedStack: filtered.adjustedStack,
|
|
1115
|
+
changes: filtered.changes,
|
|
1116
|
+
issues,
|
|
1117
|
+
hasIssues: issues.length > 0
|
|
1118
|
+
};
|
|
1119
|
+
return {
|
|
1120
|
+
content: [{
|
|
1121
|
+
type: "text",
|
|
1122
|
+
text: JSON.stringify({
|
|
1123
|
+
...filtered,
|
|
1124
|
+
issues
|
|
1125
|
+
}, null, 2)
|
|
1126
|
+
}],
|
|
1127
|
+
structuredContent
|
|
1128
|
+
};
|
|
731
1129
|
} catch (error) {
|
|
732
1130
|
return {
|
|
733
1131
|
content: [{
|
|
@@ -767,6 +1165,7 @@ async function startMcpServer() {
|
|
|
767
1165
|
observability: ObservabilitySchema.optional().describe("Observability"),
|
|
768
1166
|
featureFlags: FeatureFlagsSchema.optional().describe("Feature flag provider"),
|
|
769
1167
|
search: SearchSchema.optional().describe("Search engine"),
|
|
1168
|
+
vectorDb: VectorDbSchema.optional().describe("Vector database (TypeScript only)"),
|
|
770
1169
|
caching: CachingSchema.optional().describe("Caching solution"),
|
|
771
1170
|
rateLimit: RateLimitSchema.optional().describe("Rate limiting solution"),
|
|
772
1171
|
i18n: I18nSchema.optional().describe("Internationalization (i18n) library"),
|
|
@@ -775,9 +1174,24 @@ async function startMcpServer() {
|
|
|
775
1174
|
...mobileInputSchema,
|
|
776
1175
|
fileUpload: FileUploadSchema.optional().describe("File upload"),
|
|
777
1176
|
...deploymentInputSchema,
|
|
1177
|
+
effect: EffectSchema.optional().describe("Effect ecosystem (effect, effect-full)"),
|
|
1178
|
+
analytics: AnalyticsSchema.optional().describe("Privacy-focused analytics provider"),
|
|
1179
|
+
astroIntegration: AstroIntegrationSchema.optional().describe("Astro UI framework integration (react, vue, svelte, solid)"),
|
|
1180
|
+
aiDocs: z.array(AiDocsSchema).optional().describe("AI documentation files (claude-md, agents-md, cursorrules)"),
|
|
1181
|
+
versionChannel: VersionChannelSchema.optional().describe("Dependency version channel (stable, latest, beta)"),
|
|
778
1182
|
...crossEcosystemInputSchema
|
|
779
1183
|
};
|
|
780
|
-
registerTool("bfs_plan_project",
|
|
1184
|
+
registerTool("bfs_plan_project", {
|
|
1185
|
+
description: "Dry-run: generates a project in-memory and returns the file tree WITHOUT writing to disk. Use this to preview what would be created.",
|
|
1186
|
+
inputSchema: mcpInputSchema(planCreateSchema),
|
|
1187
|
+
outputSchema: planProjectOutputSchema,
|
|
1188
|
+
annotations: {
|
|
1189
|
+
title: "Plan project (dry run)",
|
|
1190
|
+
readOnlyHint: true,
|
|
1191
|
+
idempotentHint: true,
|
|
1192
|
+
openWorldHint: false
|
|
1193
|
+
}
|
|
1194
|
+
}, async (input) => {
|
|
781
1195
|
try {
|
|
782
1196
|
const { generateVirtualProject, EMBEDDED_TEMPLATES } = await import("@better-fullstack/template-generator");
|
|
783
1197
|
const config = buildProjectConfig(input);
|
|
@@ -788,14 +1202,18 @@ async function startMcpServer() {
|
|
|
788
1202
|
if (result.success && result.tree) {
|
|
789
1203
|
const summary = summarizeTree(result.tree);
|
|
790
1204
|
const graphPreview = getMcpGraphPreview(config);
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
1205
|
+
const payload = {
|
|
1206
|
+
success: true,
|
|
1207
|
+
...summary,
|
|
1208
|
+
...graphPreview
|
|
1209
|
+
};
|
|
1210
|
+
return {
|
|
1211
|
+
content: [{
|
|
1212
|
+
type: "text",
|
|
1213
|
+
text: JSON.stringify(payload, null, 2)
|
|
1214
|
+
}],
|
|
1215
|
+
structuredContent: payload
|
|
1216
|
+
};
|
|
799
1217
|
}
|
|
800
1218
|
return {
|
|
801
1219
|
content: [{
|
|
@@ -817,16 +1235,29 @@ async function startMcpServer() {
|
|
|
817
1235
|
};
|
|
818
1236
|
}
|
|
819
1237
|
});
|
|
820
|
-
registerTool("bfs_create_project",
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
1238
|
+
registerTool("bfs_create_project", {
|
|
1239
|
+
description: "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.",
|
|
1240
|
+
inputSchema: mcpInputSchema({
|
|
1241
|
+
...planCreateSchema,
|
|
1242
|
+
projectName: z.string().describe("Project name (kebab-case). Will be the directory name."),
|
|
1243
|
+
targetDir: z.string().optional().describe("Absolute path to the parent directory in which to create the project folder (default: current working directory).")
|
|
1244
|
+
}),
|
|
1245
|
+
outputSchema: createProjectOutputSchema,
|
|
1246
|
+
annotations: {
|
|
1247
|
+
title: "Create project",
|
|
1248
|
+
readOnlyHint: false,
|
|
1249
|
+
destructiveHint: false,
|
|
1250
|
+
idempotentHint: false,
|
|
1251
|
+
openWorldHint: false
|
|
1252
|
+
}
|
|
1253
|
+
}, async (input) => {
|
|
824
1254
|
try {
|
|
825
1255
|
const { generateVirtualProject, EMBEDDED_TEMPLATES } = await import("@better-fullstack/template-generator");
|
|
826
1256
|
const { writeTreeToFilesystem } = await import("@better-fullstack/template-generator/fs-writer");
|
|
827
1257
|
const path = await import("node:path");
|
|
828
1258
|
const projectName = sanitizePath(input.projectName);
|
|
829
|
-
const
|
|
1259
|
+
const targetDir = input.targetDir ? sanitizePath(input.targetDir) : void 0;
|
|
1260
|
+
const projectDir = path.resolve(targetDir ?? process.cwd(), projectName);
|
|
830
1261
|
const config = buildProjectConfig(input, { projectDir });
|
|
831
1262
|
await (await import("node:fs/promises")).mkdir(projectDir, { recursive: true });
|
|
832
1263
|
const result = await generateVirtualProject({
|
|
@@ -848,21 +1279,25 @@ async function startMcpServer() {
|
|
|
848
1279
|
const graphPreview = getMcpGraphPreview(config);
|
|
849
1280
|
let addonWarnings = [];
|
|
850
1281
|
if (config.addons.length > 0 && config.addons[0] !== "none") {
|
|
851
|
-
const { setupAddons } = await import("./addons-setup-
|
|
1282
|
+
const { setupAddons } = await import("./addons-setup-CSmj5ClW.mjs");
|
|
852
1283
|
addonWarnings = await setupAddons(config);
|
|
853
1284
|
}
|
|
854
1285
|
const installCmd = getInstallCommand(input.ecosystem ?? "typescript", projectName, input.packageManager, input.javaBuildTool, input.javaWebFramework);
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
1286
|
+
const payload = {
|
|
1287
|
+
success: true,
|
|
1288
|
+
projectDirectory: projectDir,
|
|
1289
|
+
fileCount: result.tree.fileCount,
|
|
1290
|
+
...graphPreview,
|
|
1291
|
+
...addonWarnings.length > 0 ? { addonWarnings } : {},
|
|
1292
|
+
message: `Project created at ${projectDir}. Tell the user to run: ${installCmd}`
|
|
1293
|
+
};
|
|
1294
|
+
return {
|
|
1295
|
+
content: [{
|
|
1296
|
+
type: "text",
|
|
1297
|
+
text: JSON.stringify(payload, null, 2)
|
|
1298
|
+
}],
|
|
1299
|
+
structuredContent: payload
|
|
1300
|
+
};
|
|
866
1301
|
} catch (error) {
|
|
867
1302
|
return {
|
|
868
1303
|
content: [{
|
|
@@ -873,12 +1308,22 @@ async function startMcpServer() {
|
|
|
873
1308
|
};
|
|
874
1309
|
}
|
|
875
1310
|
});
|
|
876
|
-
registerTool("bfs_plan_addition",
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
1311
|
+
registerTool("bfs_plan_addition", {
|
|
1312
|
+
description: "Validates what would be added to an existing project. Reads the project config (bts.jsonc) and checks which addons are new.",
|
|
1313
|
+
inputSchema: mcpInputSchema({
|
|
1314
|
+
projectDir: z.string().describe("Absolute path to the existing project directory"),
|
|
1315
|
+
addons: z.array(AddonsSchema).optional().describe("Addons to add"),
|
|
1316
|
+
webDeploy: WebDeploySchema.optional().describe("Web deployment option"),
|
|
1317
|
+
serverDeploy: ServerDeploySchema.optional().describe("Server deployment option")
|
|
1318
|
+
}),
|
|
1319
|
+
outputSchema: planAdditionOutputSchema,
|
|
1320
|
+
annotations: {
|
|
1321
|
+
title: "Plan feature addition",
|
|
1322
|
+
readOnlyHint: true,
|
|
1323
|
+
idempotentHint: true,
|
|
1324
|
+
openWorldHint: false
|
|
1325
|
+
}
|
|
1326
|
+
}, async ({ projectDir, addons, webDeploy, serverDeploy }) => {
|
|
882
1327
|
try {
|
|
883
1328
|
const safePath = sanitizePath(projectDir);
|
|
884
1329
|
const config = await readBtsConfig(safePath);
|
|
@@ -909,31 +1354,35 @@ async function startMcpServer() {
|
|
|
909
1354
|
serverDeploy: serverDeploy ?? config.serverDeploy
|
|
910
1355
|
}));
|
|
911
1356
|
const compatibilityWarnings = compatResult.changes.length > 0 ? compatResult.changes.map((c) => c.message) : void 0;
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1357
|
+
const payload = {
|
|
1358
|
+
success: true,
|
|
1359
|
+
existingConfig: {
|
|
1360
|
+
ecosystem: config.ecosystem,
|
|
1361
|
+
frontend: config.frontend,
|
|
1362
|
+
backend: config.backend,
|
|
1363
|
+
addons: config.addons,
|
|
1364
|
+
graphSummary: existingGraphPreview.graphSummary,
|
|
1365
|
+
effectiveStack: existingGraphPreview.effectiveStack,
|
|
1366
|
+
stackPartSpecs: existingGraphPreview.stackPartSpecs
|
|
1367
|
+
},
|
|
1368
|
+
proposedAdditions: {
|
|
1369
|
+
newAddons,
|
|
1370
|
+
webDeploy: webDeploy ?? null,
|
|
1371
|
+
serverDeploy: serverDeploy ?? null,
|
|
1372
|
+
graphSummary: proposedGraphPreview.graphSummary,
|
|
1373
|
+
effectiveStack: proposedGraphPreview.effectiveStack,
|
|
1374
|
+
stackPartSpecs: proposedGraphPreview.stackPartSpecs
|
|
1375
|
+
},
|
|
1376
|
+
alreadyPresent: (addons ?? []).filter((a) => existingAddons.has(a)),
|
|
1377
|
+
...compatibilityWarnings ? { compatibilityWarnings } : {}
|
|
1378
|
+
};
|
|
1379
|
+
return {
|
|
1380
|
+
content: [{
|
|
1381
|
+
type: "text",
|
|
1382
|
+
text: JSON.stringify(payload, null, 2)
|
|
1383
|
+
}],
|
|
1384
|
+
structuredContent: payload
|
|
1385
|
+
};
|
|
937
1386
|
} catch (error) {
|
|
938
1387
|
return {
|
|
939
1388
|
content: [{
|
|
@@ -944,13 +1393,24 @@ async function startMcpServer() {
|
|
|
944
1393
|
};
|
|
945
1394
|
}
|
|
946
1395
|
});
|
|
947
|
-
registerTool("bfs_add_feature",
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
1396
|
+
registerTool("bfs_add_feature", {
|
|
1397
|
+
description: "Adds addons/features to an existing Better-Fullstack project. Dependencies are NOT installed. Call bfs_plan_addition first to validate.",
|
|
1398
|
+
inputSchema: mcpInputSchema({
|
|
1399
|
+
projectDir: z.string().describe("Absolute path to the existing project directory"),
|
|
1400
|
+
addons: z.array(AddonsSchema).optional().describe("Addons to add"),
|
|
1401
|
+
webDeploy: WebDeploySchema.optional().describe("Web deployment option"),
|
|
1402
|
+
serverDeploy: ServerDeploySchema.optional().describe("Server deployment option"),
|
|
1403
|
+
packageManager: PackageManagerSchema.optional().describe("Package manager to use")
|
|
1404
|
+
}),
|
|
1405
|
+
outputSchema: addFeatureOutputSchema,
|
|
1406
|
+
annotations: {
|
|
1407
|
+
title: "Add feature",
|
|
1408
|
+
readOnlyHint: false,
|
|
1409
|
+
destructiveHint: false,
|
|
1410
|
+
idempotentHint: false,
|
|
1411
|
+
openWorldHint: false
|
|
1412
|
+
}
|
|
1413
|
+
}, async (input) => {
|
|
954
1414
|
try {
|
|
955
1415
|
const safePath = sanitizePath(input.projectDir);
|
|
956
1416
|
const { add } = await import("./index.mjs");
|
|
@@ -966,16 +1426,20 @@ async function startMcpServer() {
|
|
|
966
1426
|
const existingConfig = await readBtsConfig(safePath);
|
|
967
1427
|
const graphPreview = existingConfig ? getMcpGraphPreview(existingConfig) : void 0;
|
|
968
1428
|
const installCmd = getInstallCommand(existingConfig?.ecosystem ?? "typescript", safePath.split("/").pop() ?? "project", input.packageManager, existingConfig?.javaBuildTool, existingConfig?.javaWebFramework);
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1429
|
+
const payload = {
|
|
1430
|
+
success: true,
|
|
1431
|
+
addedAddons: result.addedAddons,
|
|
1432
|
+
projectDir: result.projectDir,
|
|
1433
|
+
...graphPreview,
|
|
1434
|
+
message: `Added ${result.addedAddons.join(", ")} to project. Tell the user to run: ${installCmd}`
|
|
1435
|
+
};
|
|
1436
|
+
return {
|
|
1437
|
+
content: [{
|
|
1438
|
+
type: "text",
|
|
1439
|
+
text: JSON.stringify(payload, null, 2)
|
|
1440
|
+
}],
|
|
1441
|
+
structuredContent: payload
|
|
1442
|
+
};
|
|
979
1443
|
}
|
|
980
1444
|
return {
|
|
981
1445
|
content: [{
|