diff --git a/apps/analytics/package.json b/apps/analytics/package.json index b2564b96a..ff46b8e78 100644 --- a/apps/analytics/package.json +++ b/apps/analytics/package.json @@ -7,7 +7,7 @@ "deploy": "convex deploy" }, "dependencies": { - "convex": "^1.38.0" + "convex": "^1.39.1" }, "devDependencies": { "typescript": "^5.9.3" diff --git a/apps/cli/package.json b/apps/cli/package.json index d57bdf0bd..8b8ba276f 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -130,8 +130,8 @@ "@better-fullstack/template-generator": "workspace:*", "@better-fullstack/types": "workspace:*", "@clack/core": "^0.5.0", - "@clack/prompts": "^1.3.0", - "@orpc/server": "^1.14.2", + "@clack/prompts": "^1.4.0", + "@orpc/server": "^1.14.3", "consola": "^3.4.2", "env-paths": "^4.0.0", "execa": "^9.6.1", @@ -146,13 +146,13 @@ "trpc-cli": "^0.12.1", "ts-morph": "^27.0.2", "yaml": "^2.9.0", - "zod": "4.3.6" + "zod": "4.4.3" }, "devDependencies": { - "@types/bun": "^1.3.13", + "@types/bun": "^1.3.14", "@types/fs-extra": "^11.0.4", - "@types/node": "^25.6.2", - "publint": "^0.3.20", + "@types/node": "^25.8.0", + "publint": "^0.3.21", "tsdown": "^0.18.2", "typescript": "^5.9.3" } diff --git a/apps/cli/src/mcp.ts b/apps/cli/src/mcp.ts index 3f278242e..4128d97ed 100644 --- a/apps/cli/src/mcp.ts +++ b/apps/cli/src/mcp.ts @@ -321,6 +321,11 @@ function getInstallCommand( } } + +function mcpInputSchema>(schema: T): Record { + return schema; +} + function filterCompatibilityResult(result: { adjustedStack: CompatibilityInput | null; notes: Record; changes: { category: string; message: string }[] }, ecosystem: string) { const { adjustedStack, changes } = result; if (!adjustedStack) return { adjustedStack: null, changes }; @@ -671,10 +676,17 @@ export async function startMcpServer() { { instructions: INSTRUCTIONS, capabilities: { logging: {} } }, ); - server.tool( + const registerTool = server.tool.bind(server) as unknown as ( + name: string, + description: string, + inputSchema: Record, + cb: (input: any) => unknown, + ) => void; + + registerTool( "bfs_get_guidance", "Returns workflow rules, field semantics, ambiguity rules, and critical constraints. Call this FIRST before using other tools.", - {}, + mcpInputSchema({}), async () => { const guidance = getGuidance(); return { @@ -683,14 +695,14 @@ export async function startMcpServer() { }, ); - server.tool( + registerTool( "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.", - { + mcpInputSchema({ category: z.string().optional().describe("Category name (e.g., 'database', 'orm', 'frontend'). Omit for all categories."), ecosystem: EcosystemSchema.optional().describe("Filter categories to this ecosystem (e.g., 'rust' returns only Rust + shared categories)."), - }, - async ({ category, ecosystem }) => { + }), + async ({ category, ecosystem }: { category?: string; ecosystem?: ProjectConfig["ecosystem"] }) => { const result = getSchemaOptions(category, ecosystem); return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }], @@ -698,10 +710,10 @@ export async function startMcpServer() { }, ); - server.tool( + registerTool( "bfs_check_compatibility", "Validates a stack combination and returns auto-adjusted selections with warnings. Call BEFORE creating a project to avoid invalid combinations.", - { + mcpInputSchema({ ecosystem: EcosystemSchema.describe("Language ecosystem"), frontend: z.array(z.string()).optional().describe("Web frontend frameworks (TypeScript only)"), backend: z.string().optional().describe("Backend framework"), @@ -776,12 +788,12 @@ export async function startMcpServer() { .array(JavaTestingLibrariesSchema) .optional() .describe("Java testing libraries"), - }, - async (input) => { + }), + async (input: Record) => { try { const compatInput = buildCompatibilityInput(input); const result = analyzeStackCompatibility(compatInput); - const filtered = filterCompatibilityResult(result, input.ecosystem); + const filtered = filterCompatibilityResult(result, input.ecosystem as string); return { content: [{ type: "text", text: JSON.stringify(filtered, null, 2) }], }; @@ -870,11 +882,11 @@ export async function startMcpServer() { .describe("Java testing libraries"), }; - server.tool( + registerTool( "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) => { + mcpInputSchema(planCreateSchema), + async (input: Record) => { try { const { generateVirtualProject, EMBEDDED_TEMPLATES } = await import("@better-fullstack/template-generator"); const config = buildProjectConfig(input); @@ -899,11 +911,11 @@ export async function startMcpServer() { }, ); - server.tool( + registerTool( "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.", - { ...planCreateSchema, projectName: z.string().describe("Project name (kebab-case). Will be the directory name.") }, - async (input) => { + mcpInputSchema({ ...planCreateSchema, projectName: z.string().describe("Project name (kebab-case). Will be the directory name.") }), + async (input: Record & { projectName: string }) => { try { const { generateVirtualProject, EMBEDDED_TEMPLATES } = await import("@better-fullstack/template-generator"); const { writeTreeToFilesystem } = await import("@better-fullstack/template-generator/fs-writer"); @@ -938,7 +950,7 @@ export async function startMcpServer() { const installCmd = getInstallCommand( ecosystem, projectName, - input.packageManager, + input.packageManager as string | undefined, input.javaBuildTool as string | undefined, input.javaWebFramework as string | undefined, ); @@ -963,16 +975,16 @@ export async function startMcpServer() { }, ); - server.tool( + registerTool( "bfs_plan_addition", "Validates what would be added to an existing project. Reads the project config (bts.jsonc) and checks which addons are new.", - { + mcpInputSchema({ projectDir: z.string().describe("Absolute path to the existing project directory"), addons: z.array(AddonsSchema).optional().describe("Addons to add"), webDeploy: WebDeploySchema.optional().describe("Web deployment option"), serverDeploy: ServerDeploySchema.optional().describe("Server deployment option"), - }, - async ({ projectDir, addons, webDeploy, serverDeploy }) => { + }), + async ({ projectDir, addons, webDeploy, serverDeploy }: { projectDir: string; addons?: ProjectConfig["addons"]; webDeploy?: ProjectConfig["webDeploy"]; serverDeploy?: ProjectConfig["serverDeploy"] }) => { try { const safePath = sanitizePath(projectDir); const config = await readBtsConfig(safePath); @@ -1028,28 +1040,28 @@ export async function startMcpServer() { }, ); - server.tool( + registerTool( "bfs_add_feature", "Adds addons/features to an existing Better-Fullstack project. Dependencies are NOT installed. Call bfs_plan_addition first to validate.", - { + mcpInputSchema({ projectDir: z.string().describe("Absolute path to the existing project directory"), addons: z.array(AddonsSchema).optional().describe("Addons to add"), webDeploy: WebDeploySchema.optional().describe("Web deployment option"), serverDeploy: ServerDeploySchema.optional().describe("Server deployment option"), packageManager: PackageManagerSchema.optional().describe("Package manager to use"), - }, - async (input) => { + }), + async (input: Record & { projectDir: string }) => { try { const safePath = sanitizePath(input.projectDir); const { add } = await import("./index.js"); const addInput: AddInput = { - addons: input.addons, - webDeploy: input.webDeploy, - serverDeploy: input.serverDeploy, + addons: input.addons as ProjectConfig["addons"] | undefined, + webDeploy: input.webDeploy as ProjectConfig["webDeploy"] | undefined, + serverDeploy: input.serverDeploy as ProjectConfig["serverDeploy"] | undefined, projectDir: safePath, install: false, - packageManager: input.packageManager, + packageManager: input.packageManager as ProjectConfig["packageManager"] | undefined, }; const result = await add(addInput); @@ -1060,7 +1072,7 @@ export async function startMcpServer() { const installCmd = getInstallCommand( ecosystem, dirName, - input.packageManager, + input.packageManager as string | undefined, existingConfig?.javaBuildTool, existingConfig?.javaWebFramework, ); diff --git a/apps/web/package.json b/apps/web/package.json index 4a406f8d8..da2d97beb 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -31,18 +31,18 @@ "@orama/orama": "^3.1.18", "@shikijs/rehype": "^4.0.2", "@shikijs/transformers": "^3.20.0", - "@tanstack/react-router": "^1.169.2", - "@tanstack/react-start": "^1.167.65", - "@tanstack/zod-adapter": "^1.166.9", + "@tanstack/react-router": "^1.170.4", + "@tanstack/react-start": "^1.168.6", + "@tanstack/zod-adapter": "^1.167.0", "@vercel/analytics": "^1.6.1", "@vercel/speed-insights": "^1.3.1", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", - "convex": "^1.38.0", - "convex-helpers": "^0.1.116", + "convex": "^1.39.1", + "convex-helpers": "^0.1.118", "create-better-fullstack": "workspace:*", "culori": "^4.0.2", - "date-fns": "^4.1.0", + "date-fns": "^4.2.0", "geist": "^1.7.0", "gray-matter": "^4.0.3", "lucide-react": "^0.562.0", @@ -50,7 +50,7 @@ "mdast-util-toc": "^7.1.0", "motion": "^12.38.0", "papaparse": "^5.5.3", - "posthog-js": "^1.372.10", + "posthog-js": "^1.374.0", "qrcode": "^1.5.4", "radix-ui": "^1.4.3", "react": "^19.2.6", @@ -65,17 +65,17 @@ "sonner": "^2.0.7", "tailwind-merge": "^3.6.0", "unist-util-visit": "^5.1.0", - "zod": "4.3.6" + "zod": "4.4.3" }, "devDependencies": { "@axe-core/playwright": "^4.11.3", - "@playwright/test": "1.59.1", + "@playwright/test": "1.60.0", "@tailwindcss/postcss": "^4.3.0", "@tailwindcss/vite": "^4.3.0", "@types/culori": "^4.0.1", "@types/mdast": "^4.0.4", "@types/mdx": "^2.0.13", - "@types/node": "25.6.2", + "@types/node": "25.8.0", "@types/papaparse": "^5.5.2", "@types/qrcode": "^1.5.6", "@types/react": "19.2.14", @@ -83,7 +83,7 @@ "@types/unist": "^3.0.3", "@vitejs/plugin-react": "^5.1.2", "nitro": "^3.0.0", - "playwright-core": "1.59.1", + "playwright-core": "1.60.0", "postcss": "^8.5.14", "tailwindcss": "^4.3.0", "tw-animate-css": "^1.4.0", diff --git a/apps/web/src/components/docs/search-dialog.tsx b/apps/web/src/components/docs/search-dialog.tsx index dfe320bd5..51e189133 100644 --- a/apps/web/src/components/docs/search-dialog.tsx +++ b/apps/web/src/components/docs/search-dialog.tsx @@ -129,8 +129,8 @@ export function DocsSearchDialog({ className="absolute inset-0 bg-background/80 backdrop-blur-sm" onClick={() => onOpenChange(false)} /> - {searchSections.length} sections indexed - + ) : null} diff --git a/apps/web/src/components/stack-builder/preset-dropdown.tsx b/apps/web/src/components/stack-builder/preset-dropdown.tsx index 3f24d8446..73b71838d 100644 --- a/apps/web/src/components/stack-builder/preset-dropdown.tsx +++ b/apps/web/src/components/stack-builder/preset-dropdown.tsx @@ -20,6 +20,7 @@ export function PresetDropdown({ onApplyPreset }: PresetDropdownProps) { render={