From 041dc17b2aefbf6046912913a4f3715fc1ecc949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Paola=20Estefan=C3=ADa=20de=20Campos?= <84341268+Paola3stefania@users.noreply.github.com> Date: Fri, 23 Jan 2026 15:04:12 -0300 Subject: [PATCH] fix(mcp): remove local mpc (#7574) --- packages/cli/src/commands/mcp.ts | 240 ++------- packages/mcp/package.json | 56 -- packages/mcp/src/index.ts | 20 - packages/mcp/src/lib/generator.ts | 467 ----------------- packages/mcp/src/lib/parser.ts | 111 ---- packages/mcp/src/lib/schemas.ts | 61 --- packages/mcp/src/lib/templates/database.ts | 140 ----- packages/mcp/src/lib/templates/features.ts | 224 -------- packages/mcp/src/lib/templates/frameworks.ts | 274 ---------- packages/mcp/src/lib/types.ts | 167 ------ packages/mcp/src/tools/index.ts | 1 - packages/mcp/src/tools/setup-auth.ts | 184 ------- packages/mcp/tests/lib/generator.test.ts | 495 ------------------ packages/mcp/tests/lib/parser.test.ts | 329 ------------ .../mcp/tests/lib/templates/database.test.ts | 153 ------ .../mcp/tests/lib/templates/features.test.ts | 150 ------ .../tests/lib/templates/frameworks.test.ts | 76 --- packages/mcp/tsconfig.json | 6 - packages/mcp/tsdown.config.ts | 16 - 19 files changed, 57 insertions(+), 3113 deletions(-) delete mode 100644 packages/mcp/package.json delete mode 100644 packages/mcp/src/index.ts delete mode 100644 packages/mcp/src/lib/generator.ts delete mode 100644 packages/mcp/src/lib/parser.ts delete mode 100644 packages/mcp/src/lib/schemas.ts delete mode 100644 packages/mcp/src/lib/templates/database.ts delete mode 100644 packages/mcp/src/lib/templates/features.ts delete mode 100644 packages/mcp/src/lib/templates/frameworks.ts delete mode 100644 packages/mcp/src/lib/types.ts delete mode 100644 packages/mcp/src/tools/index.ts delete mode 100644 packages/mcp/src/tools/setup-auth.ts delete mode 100644 packages/mcp/tests/lib/generator.test.ts delete mode 100644 packages/mcp/tests/lib/parser.test.ts delete mode 100644 packages/mcp/tests/lib/templates/database.test.ts delete mode 100644 packages/mcp/tests/lib/templates/features.test.ts delete mode 100644 packages/mcp/tests/lib/templates/frameworks.test.ts delete mode 100644 packages/mcp/tsconfig.json delete mode 100644 packages/mcp/tsdown.config.ts diff --git a/packages/cli/src/commands/mcp.ts b/packages/cli/src/commands/mcp.ts index 4849145d19..1011b853d1 100644 --- a/packages/cli/src/commands/mcp.ts +++ b/packages/cli/src/commands/mcp.ts @@ -11,62 +11,25 @@ interface MCPOptions { claudeCode?: boolean; openCode?: boolean; manual?: boolean; - localOnly?: boolean; - remoteOnly?: boolean; } const REMOTE_MCP_URL = "https://mcp.inkeep.com/better-auth/mcp"; -const LOCAL_MCP_COMMAND = "npx @better-auth/mcp"; async function mcpAction(options: MCPOptions) { - const installLocal = !options.remoteOnly; - const installRemote = !options.localOnly; - if (options.cursor) { - await handleCursorAction(installLocal, installRemote); + await handleCursorAction(); } else if (options.claudeCode) { - handleClaudeCodeAction(installLocal, installRemote); + handleClaudeCodeAction(); } else if (options.openCode) { - handleOpenCodeAction(installLocal, installRemote); + handleOpenCodeAction(); } else if (options.manual) { - handleManualAction(installLocal, installRemote); + handleManualAction(); } else { showAllOptions(); } } -async function _installMcpServers( - client: - | "cursor" - | "claude-code" - | "open-code" - | "opencode" - | "manual" - | string, - installLocal: boolean = true, - installRemote: boolean = true, -) { - switch (client) { - case "cursor": - await handleCursorAction(installLocal, installRemote); - break; - case "claude-code": - handleClaudeCodeAction(installLocal, installRemote); - break; - case "open-code": - case "opencode": - handleOpenCodeAction(installLocal, installRemote); - break; - case "manual": - handleManualAction(installLocal, installRemote); - break; - } -} - -async function handleCursorAction( - installLocal: boolean, - installRemote: boolean, -) { +async function handleCursorAction() { console.log(chalk.bold.blue("šŸš€ Adding Better Auth MCP to Cursor...")); const platform = os.platform(); @@ -86,62 +49,30 @@ async function handleCursorAction( throw new Error(`Unsupported platform: ${platform}`); } - const installed: string[] = []; + const remoteConfig = { url: REMOTE_MCP_URL }; + const encodedRemote = base64.encode( + new TextEncoder().encode(JSON.stringify(remoteConfig)), + ); + const remoteDeeplink = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("better-auth-docs")}&config=${encodedRemote}`; - if (installRemote) { - const remoteConfig = { url: REMOTE_MCP_URL }; - const encodedRemote = base64.encode( - new TextEncoder().encode(JSON.stringify(remoteConfig)), + try { + const cmd = + platform === "win32" + ? `start "" "${remoteDeeplink}"` + : `${openCommand} "${remoteDeeplink}"`; + execSync(cmd, { stdio: "inherit" }); + console.log(chalk.green("\nāœ“ Better Auth MCP server installed!")); + } catch { + console.log( + chalk.yellow( + "\n⚠ Could not automatically open Cursor for MCP installation.", + ), ); - const remoteDeeplink = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("better-auth-docs")}&config=${encodedRemote}`; - - try { - const cmd = - platform === "win32" - ? `start "" "${remoteDeeplink}"` - : `${openCommand} "${remoteDeeplink}"`; - execSync(cmd, { stdio: "inherit" }); - installed.push("better-auth-docs (remote - documentation & search)"); - } catch { - console.log( - chalk.yellow("\n⚠ Could not automatically open Cursor for remote MCP."), - ); - } - } - - if (installLocal) { - await new Promise((resolve) => setTimeout(resolve, 1000)); - - const localConfig = { command: LOCAL_MCP_COMMAND }; - const encodedLocal = base64.encode( - new TextEncoder().encode(JSON.stringify(localConfig)), - ); - const localDeeplink = `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent("better-auth")}&config=${encodedLocal}`; - - try { - const cmd = - platform === "win32" - ? `start "" "${localDeeplink}"` - : `${openCommand} "${localDeeplink}"`; - execSync(cmd, { stdio: "inherit" }); - installed.push("better-auth (local - setup & diagnostics)"); - } catch { - console.log( - chalk.yellow("\n⚠ Could not automatically open Cursor for local MCP."), - ); - } - } - - if (installed.length > 0) { - console.log(chalk.green("\nāœ“ Cursor MCP servers installed:")); - for (const name of installed) { - console.log(chalk.green(` • ${name}`)); - } } console.log(chalk.bold.white("\n✨ Next Steps:")); console.log( - chalk.gray("• The MCP servers will be added to your Cursor configuration"), + chalk.gray("• The MCP server will be added to your Cursor configuration"), ); console.log( chalk.gray("• You can now use Better Auth features directly in Cursor"), @@ -153,42 +84,27 @@ async function handleCursorAction( ); } -function handleClaudeCodeAction(installLocal: boolean, installRemote: boolean) { +function handleClaudeCodeAction() { console.log(chalk.bold.blue("šŸ¤– Adding Better Auth MCP to Claude Code...")); - const commands: string[] = []; + const command = `claude mcp add --transport http better-auth-docs ${REMOTE_MCP_URL}`; - if (installRemote) { - commands.push( - `claude mcp add --transport http better-auth-docs ${REMOTE_MCP_URL}`, - ); - } - if (installLocal) { - commands.push(`claude mcp add better-auth -- ${LOCAL_MCP_COMMAND}`); - } - - let anySucceeded = false; - for (const command of commands) { - try { - execSync(command, { stdio: "inherit" }); - anySucceeded = true; - } catch { - console.log( - chalk.yellow( - "\n⚠ Could not automatically add to Claude Code. Please run this command manually:", - ), - ); - console.log(chalk.cyan(command)); - } - } - - if (anySucceeded) { + try { + execSync(command, { stdio: "inherit" }); console.log(chalk.green("\nāœ“ Claude Code MCP configured!")); + } catch { + console.log( + chalk.yellow( + "\n⚠ Could not automatically add to Claude Code. Please run this command manually:", + ), + ); + console.log(chalk.cyan(command)); } + console.log(chalk.bold.white("\n✨ Next Steps:")); console.log( chalk.gray( - "• The MCP servers will be added to your Claude Code configuration", + "• The MCP server will be added to your Claude Code configuration", ), ); console.log( @@ -198,30 +114,18 @@ function handleClaudeCodeAction(installLocal: boolean, installRemote: boolean) { ); } -function handleOpenCodeAction(installLocal: boolean, installRemote: boolean) { +function handleOpenCodeAction() { console.log(chalk.bold.blue("šŸ”§ Adding Better Auth MCP to Open Code...")); - const mcpConfig: Record = {}; - - if (installRemote) { - mcpConfig["better-auth-docs"] = { - type: "remote", - url: REMOTE_MCP_URL, - enabled: true, - }; - } - - if (installLocal) { - mcpConfig["better-auth"] = { - type: "local", - command: [LOCAL_MCP_COMMAND], - enabled: true, - }; - } - const openCodeConfig = { $schema: "https://opencode.ai/config.json", - mcp: mcpConfig, + mcp: { + "better-auth-docs": { + type: "remote", + url: REMOTE_MCP_URL, + enabled: true, + }, + }, }; const configPath = path.join(process.cwd(), "opencode.json"); @@ -249,7 +153,7 @@ function handleOpenCodeAction(installLocal: boolean, installRemote: boolean) { console.log( chalk.green(`\nāœ“ Open Code configuration written to ${configPath}`), ); - console.log(chalk.green("āœ“ Better Auth MCP servers added successfully!")); + console.log(chalk.green("āœ“ Better Auth MCP server added successfully!")); } catch { console.log( chalk.yellow( @@ -260,28 +164,20 @@ function handleOpenCodeAction(installLocal: boolean, installRemote: boolean) { } console.log(chalk.bold.white("\n✨ Next Steps:")); - console.log(chalk.gray("• Restart Open Code to load the new MCP servers")); + console.log(chalk.gray("• Restart Open Code to load the new MCP server")); console.log( chalk.gray("• You can now use Better Auth features directly in Open Code"), ); } -function handleManualAction(installLocal: boolean, installRemote: boolean) { +function handleManualAction() { console.log(chalk.bold.blue("šŸ“ Better Auth MCP Configuration...")); - const manualConfig: Record = {}; - - if (installRemote) { - manualConfig["better-auth-docs"] = { + const manualConfig = { + "better-auth-docs": { url: REMOTE_MCP_URL, - }; - } - - if (installLocal) { - manualConfig["better-auth"] = { - command: LOCAL_MCP_COMMAND, - }; - } + }, + }; const configPath = path.join(process.cwd(), "mcp.json"); @@ -299,7 +195,7 @@ function handleManualAction(installLocal: boolean, installRemote: boolean) { fs.writeFileSync(configPath, JSON.stringify(mergedConfig, null, 2)); console.log(chalk.green(`\nāœ“ MCP configuration written to ${configPath}`)); - console.log(chalk.green("āœ“ Better Auth MCP servers added successfully!")); + console.log(chalk.green("āœ“ Better Auth MCP server added successfully!")); } catch { console.log( chalk.yellow( @@ -310,7 +206,7 @@ function handleManualAction(installLocal: boolean, installRemote: boolean) { } console.log(chalk.bold.white("\n✨ Next Steps:")); - console.log(chalk.gray("• Restart your MCP client to load the new servers")); + console.log(chalk.gray("• Restart your MCP client to load the new server")); console.log( chalk.gray( "• You can now use Better Auth features directly in your MCP client", @@ -319,7 +215,7 @@ function handleManualAction(installLocal: boolean, installRemote: boolean) { } function showAllOptions() { - console.log(chalk.bold.blue("šŸ”Œ Better Auth MCP Servers")); + console.log(chalk.bold.blue("šŸ”Œ Better Auth MCP Server")); console.log(chalk.gray("Choose your MCP client to get started:")); console.log(); @@ -334,41 +230,19 @@ function showAllOptions() { ); console.log(); - console.log(chalk.bold.white("Server Selection:")); - console.log( - chalk.cyan(" --local-only ") + - chalk.gray("Install only local MCP (setup & diagnostics)"), - ); - console.log( - chalk.cyan(" --remote-only ") + - chalk.gray("Install only remote MCP (documentation & search)"), - ); - console.log(chalk.gray(" (default: install both servers)")); - console.log(); - - console.log(chalk.bold.white("Servers:")); - console.log( - chalk.gray(" • ") + - chalk.white("better-auth") + - chalk.gray(" (local) - Setup auth, diagnose issues, validate config"), - ); + console.log(chalk.bold.white("Server:")); console.log( chalk.gray(" • ") + chalk.white("better-auth-docs") + - chalk.gray(" (remote) - Search documentation, code examples"), + chalk.gray(" - Search documentation, code examples, setup assistance"), ); console.log(); } export const mcp = new Command("mcp") - .description("Add Better Auth MCP servers to MCP Clients") + .description("Add Better Auth MCP server to MCP Clients") .option("--cursor", "Automatically open Cursor with the MCP configuration") .option("--claude-code", "Show Claude Code MCP configuration command") .option("--open-code", "Show Open Code MCP configuration") .option("--manual", "Show manual MCP configuration for mcp.json") - .option("--local-only", "Install only local MCP server (setup & diagnostics)") - .option( - "--remote-only", - "Install only remote MCP server (documentation & search)", - ) .action(mcpAction); diff --git a/packages/mcp/package.json b/packages/mcp/package.json deleted file mode 100644 index 7a019924cc..0000000000 --- a/packages/mcp/package.json +++ /dev/null @@ -1,56 +0,0 @@ -{ - "name": "@better-auth/mcp", - "version": "1.5.0-beta.9", - "type": "module", - "description": "Better Auth MCP server for AI-powered auth setup and diagnostics", - "module": "dist/index.mjs", - "repository": { - "type": "git", - "url": "git+https://github.com/better-auth/better-auth.git", - "directory": "packages/mcp" - }, - "homepage": "https://www.better-auth.com/docs/concepts/cli#mcp", - "main": "./dist/index.mjs", - "scripts": { - "build": "tsdown", - "dev": "tsdown --watch", - "typecheck": "tsc --project tsconfig.json" - }, - "publishConfig": { - "access": "public", - "executableFiles": [ - "./dist/index.mjs" - ] - }, - "license": "MIT", - "keywords": [ - "auth", - "mcp", - "ai", - "model-context-protocol", - "better-auth" - ], - "exports": { - ".": { - "types": "./dist/index.d.mts", - "default": "./dist/index.mjs" - } - }, - "bin": { - "better-auth-mcp": "./dist/index.mjs" - }, - "engines": { - "node": ">=20.0.0" - }, - "devDependencies": { - "tsdown": "catalog:", - "typescript": "catalog:" - }, - "dependencies": { - "@modelcontextprotocol/sdk": "^1.8.0", - "zod": "^4.3.5" - }, - "files": [ - "dist" - ] -} diff --git a/packages/mcp/src/index.ts b/packages/mcp/src/index.ts deleted file mode 100644 index 9cab843aeb..0000000000 --- a/packages/mcp/src/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env node -import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { registerSetupAuth } from "./tools/index.js"; - -const server = new McpServer({ - name: "better-auth", - description: - "Better Auth MCP server for AI-powered auth setup and diagnostics", - version: process.env.BETTER_AUTH_VERSION!, -}); - -registerSetupAuth(server); - -async function main() { - const transport = new StdioServerTransport(); - await server.connect(transport); -} - -main().catch(console.error); diff --git a/packages/mcp/src/lib/generator.ts b/packages/mcp/src/lib/generator.ts deleted file mode 100644 index 33b805e849..0000000000 --- a/packages/mcp/src/lib/generator.ts +++ /dev/null @@ -1,467 +0,0 @@ -import { computeFeatureDiff, parseExistingSetup } from "./parser.js"; -import { - generateDatabaseConfig, - getDatabaseCommands, - getDatabaseEnvVar, -} from "./templates/database.js"; -import { - categorizeFeatures, - generatePluginImports, - generatePluginSetup, - generateSocialProviderConfig, - getPluginEnvVars, - getSocialProviderEnvVars, -} from "./templates/features.js"; -import { - FRAMEWORK_CONFIGS, - getDefaultApiPath, - getDefaultAuthPath, -} from "./templates/frameworks.js"; -import type { - Command, - Database, - DocLink, - EnvVar, - Feature, - Framework, - ORM, - OutputFile, - SetupAuthError, - SetupAuthInput, - SetupAuthOutput, -} from "./types.js"; - -export function generateSetup( - input: SetupAuthInput, -): SetupAuthOutput | SetupAuthError { - const framework = input.framework; - const database = input.database; - const orm = input.orm || "none"; - const features = input.features || ["email-password"]; - const typescript = input.typescript ?? true; - const srcDir = input.srcDir ?? FRAMEWORK_CONFIGS[framework].defaultSrcDir; - - const authPath = input.authPath || getDefaultAuthPath(framework, srcDir); - const apiPath = input.apiPath || getDefaultApiPath(framework, srcDir); - - let mode: "create" | "update" = "create"; - let featuresToGenerate = features; - let detected: ReturnType | undefined; - - if (input.existingSetup?.authConfig) { - mode = "update"; - detected = parseExistingSetup(input.existingSetup); - const diff = computeFeatureDiff(detected.features as Feature[], features); - featuresToGenerate = diff.toAdd; - - if (featuresToGenerate.length === 0) { - return { - mode: "update", - files: [], - envVars: [], - commands: [], - detected, - nextSteps: ["All requested features are already configured"], - docs: [], - }; - } - } - - const { socialProviders, plugins, hasEmailPassword } = - categorizeFeatures(featuresToGenerate); - - const files: OutputFile[] = []; - const envVars: EnvVar[] = []; - const commands: Command[] = []; - const warnings: string[] = []; - - envVars.push({ - name: "BETTER_AUTH_SECRET", - description: "Secret key for signing tokens", - required: true, - howToGet: "Run: npx @better-auth/cli secret", - }); - - envVars.push(getDatabaseEnvVar(database)); - - for (const provider of socialProviders) { - envVars.push(...getSocialProviderEnvVars(provider)); - } - - envVars.push(...getPluginEnvVars(plugins)); - - if (mode === "create") { - const authFile = generateAuthFile({ - framework, - database, - orm, - socialProviders, - plugins, - hasEmailPassword, - typescript, - }); - - files.push({ - path: `${authPath}.${typescript ? "ts" : "js"}`, - description: "Better Auth server configuration", - action: "create", - content: authFile, - }); - - const clientFile = generateClientFile({ - framework, - plugins, - typescript, - }); - - files.push({ - path: `${authPath}-client.${typescript ? "ts" : "js"}`, - description: "Better Auth client configuration", - action: "create", - content: clientFile, - }); - - if (apiPath) { - const apiFile = generateApiRouteFile(framework, authPath); - if (apiFile) { - files.push({ - path: `${apiPath}/route.${typescript ? "ts" : "js"}`, - description: "API route handler for auth endpoints", - action: "create", - content: apiFile, - }); - } - } - - const frameworkConfig = FRAMEWORK_CONFIGS[framework]; - if (frameworkConfig.hooksTemplate) { - files.push({ - path: srcDir ? "src/hooks.server.ts" : "hooks.server.ts", - description: "SvelteKit hooks for auth handling", - action: "create", - content: frameworkConfig.hooksTemplate(authPath), - }); - } - } else { - if (socialProviders.length > 0 || plugins.length > 0 || hasEmailPassword) { - const changes = generateUpdateChanges({ - socialProviders, - plugins, - hasEmailPassword, - }); - - files.push({ - path: `${authPath}.${typescript ? "ts" : "js"}`, - description: "Better Auth server configuration", - action: "update", - changes: changes.serverChanges, - }); - - if (plugins.length > 0) { - files.push({ - path: `${authPath}-client.${typescript ? "ts" : "js"}`, - description: "Better Auth client configuration", - action: "update", - changes: changes.clientChanges, - }); - } - } - } - - commands.push({ - command: "pnpm add better-auth", - description: "Install Better Auth", - when: "If not already installed", - }); - - commands.push(...getDatabaseCommands(orm)); - - const nextSteps = generateNextSteps(mode, socialProviders, plugins); - const docs = generateDocLinks(features); - - return { - mode, - files, - envVars, - commands, - detected, - nextSteps, - warnings: warnings.length > 0 ? warnings : undefined, - docs, - }; -} - -function generateAuthFile(options: { - framework: Framework; - database: Database; - orm: ORM; - socialProviders: string[]; - plugins: string[]; - hasEmailPassword: boolean; - typescript: boolean; -}): string { - const { database, orm, socialProviders, plugins, hasEmailPassword } = options; - - const imports: string[] = ['import { betterAuth } from "better-auth";']; - - const { - imports: dbImports, - config: dbConfig, - prismaInstance, - } = generateDatabaseConfig(database, orm); - - if (dbImports) { - imports.push(dbImports); - } - - const { serverImports } = generatePluginImports(plugins); - imports.push(...serverImports); - - const socialProvidersConfig = socialProviders - .map((p) => ` ${generateSocialProviderConfig(p)}`) - .join(",\n"); - - const { serverPlugins } = generatePluginSetup(plugins); - - let configBody = ` database: ${dbConfig},`; - - if (hasEmailPassword) { - configBody += ` - emailAndPassword: { - enabled: true, - },`; - } - - if (socialProviders.length > 0) { - configBody += ` - socialProviders: { -${socialProvidersConfig} - },`; - } - - if (serverPlugins.length > 0) { - configBody += ` - plugins: [ - ${serverPlugins.join(",\n ")} - ],`; - } - - return `${imports.join("\n")} -${prismaInstance || ""} -export const auth = betterAuth({ -${configBody} -}); -`; -} - -function generateClientFile(options: { - framework: Framework; - plugins: string[]; - typescript: boolean; -}): string { - const { framework, plugins } = options; - - const frameworkConfig = FRAMEWORK_CONFIGS[framework]; - const imports: string[] = [frameworkConfig.clientImport]; - - const { clientImports } = generatePluginImports(plugins); - imports.push(...clientImports); - - const { clientPlugins } = generatePluginSetup(plugins); - - let configBody = ""; - - if (clientPlugins.length > 0) { - configBody = `{ - plugins: [ - ${clientPlugins.join(",\n ")} - ], -}`; - } else { - configBody = "{}"; - } - - return `${imports.join("\n")} - -export const authClient = createAuthClient(${configBody}); -`; -} - -function generateApiRouteFile( - framework: Framework, - authPath: string, -): string | null { - const frameworkConfig = FRAMEWORK_CONFIGS[framework]; - - if (!frameworkConfig.apiRouteTemplate) { - return null; - } - - const template = frameworkConfig.apiRouteTemplate(authPath); - return template || null; -} - -function generateUpdateChanges(options: { - socialProviders: string[]; - plugins: string[]; - hasEmailPassword: boolean; -}): { - serverChanges: { type: string; content: string; description: string }[]; - clientChanges: { type: string; content: string; description: string }[]; -} { - const { socialProviders, plugins, hasEmailPassword } = options; - - const serverChanges: { - type: string; - content: string; - description: string; - }[] = []; - const clientChanges: { - type: string; - content: string; - description: string; - }[] = []; - - if (hasEmailPassword) { - serverChanges.push({ - type: "add_to_config", - content: `emailAndPassword: { - enabled: true, - }`, - description: "Enable email and password authentication", - }); - } - - const { serverImports, clientImports } = generatePluginImports(plugins); - - for (const imp of serverImports) { - serverChanges.push({ - type: "add_import", - content: imp, - description: "Add plugin import", - }); - } - - for (const provider of socialProviders) { - serverChanges.push({ - type: "add_to_config", - content: generateSocialProviderConfig(provider), - description: `Add ${provider} social provider`, - }); - } - - const { serverPlugins, clientPlugins } = generatePluginSetup(plugins); - - for (const plugin of serverPlugins) { - serverChanges.push({ - type: "add_plugin", - content: plugin, - description: "Add server plugin", - }); - } - - for (const imp of clientImports) { - clientChanges.push({ - type: "add_import", - content: imp, - description: "Add client plugin import", - }); - } - - for (const plugin of clientPlugins) { - clientChanges.push({ - type: "add_plugin", - content: plugin, - description: "Add client plugin", - }); - } - - return { serverChanges, clientChanges }; -} - -function generateNextSteps( - mode: "create" | "update", - socialProviders: string[], - plugins: string[], -): string[] { - const steps: string[] = []; - - if (mode === "create") { - steps.push("Set environment variables in .env file"); - steps.push("Run database migrations"); - } - - if (socialProviders.length > 0) { - steps.push(`Configure OAuth apps for: ${socialProviders.join(", ")}`); - steps.push("Add callback URLs to OAuth provider dashboards"); - } - - if (plugins.includes("magic-link")) { - steps.push("Configure email provider for magic links"); - } - - if (plugins.includes("phone-number")) { - steps.push("Configure SMS provider for OTP"); - } - - if (plugins.includes("captcha")) { - steps.push( - "Set up captcha provider (Cloudflare Turnstile, reCAPTCHA, or hCaptcha)", - ); - } - - steps.push("Start your development server and test auth flow"); - - return steps; -} - -function generateDocLinks(features: Feature[]): DocLink[] { - const docs: DocLink[] = [ - { - title: "Better Auth Documentation", - url: "https://www.better-auth.com/docs", - }, - { - title: "Getting Started", - url: "https://www.better-auth.com/docs/getting-started", - }, - ]; - - if ( - features.some((f) => ["google", "github", "apple", "discord"].includes(f)) - ) { - docs.push({ - title: "Social Sign-On", - url: "https://www.better-auth.com/docs/authentication/social-sign-on", - }); - } - - if (features.includes("2fa")) { - docs.push({ - title: "Two-Factor Authentication", - url: "https://www.better-auth.com/docs/plugins/two-factor", - }); - } - - if (features.includes("organization")) { - docs.push({ - title: "Organizations", - url: "https://www.better-auth.com/docs/plugins/organization", - }); - } - - if (features.includes("passkey")) { - docs.push({ - title: "Passkeys", - url: "https://www.better-auth.com/docs/plugins/passkey", - }); - } - - return docs; -} - -export function isSetupError( - result: SetupAuthOutput | SetupAuthError, -): result is SetupAuthError { - return "error" in result; -} diff --git a/packages/mcp/src/lib/parser.ts b/packages/mcp/src/lib/parser.ts deleted file mode 100644 index 7f28cb3bee..0000000000 --- a/packages/mcp/src/lib/parser.ts +++ /dev/null @@ -1,111 +0,0 @@ -import type { DetectedConfig, ExistingSetup, Feature } from "./types.js"; - -export function parseExistingSetup( - existingSetup: ExistingSetup, -): DetectedConfig { - const detected: DetectedConfig = { - features: [], - }; - - if (existingSetup.authConfig) { - const config = existingSetup.authConfig; - detected.database = detectDatabase(config); - detected.orm = detectORM(config); - detected.features = detectFeatures(config, existingSetup.authClientConfig); - } - - return detected; -} - -function detectDatabase(config: string): string | undefined { - if ( - /provider:\s*["']postgresql["']/.test(config) || - /provider:\s*["']pg["']/.test(config) - ) { - return "postgres"; - } - if (/provider:\s*["']mysql["']/.test(config)) { - return "mysql"; - } - if (/provider:\s*["']sqlite["']/.test(config)) { - return "sqlite"; - } - if (/provider:\s*["']mongodb["']/.test(config)) { - return "mongodb"; - } - return undefined; -} - -function detectORM(config: string): string | undefined { - if (/prismaAdapter\(/.test(config)) { - return "prisma"; - } - if (/drizzleAdapter\(/.test(config)) { - return "drizzle"; - } - if (/database:\s*\{[\s\S]*provider:/.test(config)) { - return "none"; - } - return undefined; -} - -function detectFeatures( - serverConfig: string, - clientConfig?: string, -): Feature[] { - const features: Feature[] = []; - - if (/emailAndPassword:\s*\{[\s\S]*enabled:\s*true/.test(serverConfig)) { - features.push("email-password"); - } - - const socialProviders: Feature[] = [ - "google", - "github", - "apple", - "discord", - "twitter", - "facebook", - "microsoft", - "linkedin", - ]; - - for (const provider of socialProviders) { - if (new RegExp(`${provider}:\\s*\\{`).test(serverConfig)) { - features.push(provider); - } - } - - const pluginPatterns: [RegExp, Feature][] = [ - [/twoFactor\(/, "2fa"], - [/organization\(/, "organization"], - [/admin\(/, "admin"], - [/username\(/, "username"], - [/multiSession\(/, "multi-session"], - [/apiKey\(/, "api-key"], - [/bearer\(/, "bearer"], - [/jwt\(/, "jwt"], - [/magicLink\(/, "magic-link"], - [/phoneNumber\(/, "phone-number"], - [/passkey\(/, "passkey"], - [/anonymous\(/, "anonymous"], - [/captcha\(/, "captcha"], - ]; - - for (const [pattern, feature] of pluginPatterns) { - if (pattern.test(serverConfig)) { - features.push(feature); - } - } - - return features; -} - -export function computeFeatureDiff( - existing: Feature[], - requested: Feature[], -): { toAdd: Feature[]; existing: Feature[] } { - const toAdd = requested.filter((f) => !existing.includes(f)); - const existingFeatures = requested.filter((f) => existing.includes(f)); - return { toAdd, existing: existingFeatures }; -} diff --git a/packages/mcp/src/lib/schemas.ts b/packages/mcp/src/lib/schemas.ts deleted file mode 100644 index 7a246b4cfe..0000000000 --- a/packages/mcp/src/lib/schemas.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as z from "zod"; - -export const FrameworkEnum = z.enum([ - "next-app-router", - "next-pages-router", - "sveltekit", - "astro", - "remix", - "nuxt", - "solid-start", - "hono", - "express", - "fastify", - "elysia", - "tanstack-start", - "expo", -]); - -export const DatabaseEnum = z.enum(["postgres", "mysql", "sqlite", "mongodb"]); - -export const ORMEnum = z.enum(["prisma", "drizzle", "none"]); - -export const FeatureEnum = z.enum([ - "email-password", - "magic-link", - "phone-number", - "passkey", - "anonymous", - "google", - "github", - "apple", - "discord", - "twitter", - "facebook", - "microsoft", - "linkedin", - "2fa", - "captcha", - "organization", - "admin", - "username", - "multi-session", - "api-key", - "bearer", - "jwt", -]); - -export const ExistingSetupSchema = z.object({ - authConfig: z - .string() - .optional() - .describe("Contents of existing auth.ts file"), - authClientConfig: z - .string() - .optional() - .describe("Contents of existing auth-client.ts file"), - envVars: z - .array(z.string()) - .optional() - .describe("List of existing environment variable names"), -}); diff --git a/packages/mcp/src/lib/templates/database.ts b/packages/mcp/src/lib/templates/database.ts deleted file mode 100644 index cb5f7d3db4..0000000000 --- a/packages/mcp/src/lib/templates/database.ts +++ /dev/null @@ -1,140 +0,0 @@ -import type { - Database, - DatabaseConfig, - EnvVar, - ORM, - ORMConfig, -} from "../types.js"; - -// cspell:ignore mydb -const DATABASE_CONFIGS: Record = { - postgres: { - provider: "postgresql", - envVarName: "DATABASE_URL", - connectionStringExample: "postgresql://user:password@localhost:5432/mydb", - }, - mysql: { - provider: "mysql", - envVarName: "DATABASE_URL", - connectionStringExample: "mysql://user:password@localhost:3306/mydb", - }, - sqlite: { - provider: "sqlite", - envVarName: "DATABASE_URL", - connectionStringExample: "file:./dev.db", - }, - mongodb: { - provider: "mongodb", - envVarName: "DATABASE_URL", - connectionStringExample: "mongodb://localhost:27017/mydb", - }, -}; - -const ORM_CONFIGS: Record = { - prisma: { - adapterImport: `import { prismaAdapter } from "better-auth/adapters/prisma"; -import { PrismaClient } from "@prisma/client";`, - adapterSetup: (dbProvider: string) => `prismaAdapter(prisma, { - provider: "${dbProvider}", - })`, - schemaCommand: - "npx @better-auth/cli generate --output prisma/schema.prisma", - pushCommand: "npx prisma db push", - }, - drizzle: { - adapterImport: `import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { db } from "./db";`, - adapterSetup: (dbProvider: string) => `drizzleAdapter(db, { - provider: "${getShortProvider(dbProvider)}", - })`, - schemaCommand: "npx @better-auth/cli generate --output src/lib/schema.ts", - pushCommand: "npx drizzle-kit push", - }, - none: { - adapterImport: "", - adapterSetup: (dbProvider: string) => `{ - provider: "${getShortProvider(dbProvider)}", - url: process.env.DATABASE_URL, - }`, - pushCommand: "npx @better-auth/cli migrate", - }, -}; - -function getShortProvider(provider: string): string { - switch (provider) { - case "postgresql": - return "pg"; - case "mysql": - return "mysql"; - case "sqlite": - return "sqlite"; - case "mongodb": - return "mongodb"; - default: - return provider; - } -} - -export function generateDatabaseConfig( - database: Database, - orm: ORM, -): { imports: string; config: string; prismaInstance?: string } { - if (orm === "drizzle" && database === "mongodb") { - throw new Error( - "Drizzle ORM does not support MongoDB. Please select Prisma or use the built-in MongoDB adapter instead.", - ); - } - - const dbConfig = DATABASE_CONFIGS[database]; - const ormConfig = ORM_CONFIGS[orm]; - - let imports = ""; - let prismaInstance = ""; - - if (orm === "prisma") { - imports = ormConfig.adapterImport; - prismaInstance = "\nconst prisma = new PrismaClient();\n"; - } else if (orm === "drizzle") { - imports = ormConfig.adapterImport; - } - - const config = ormConfig.adapterSetup(dbConfig.provider); - - return { imports, config, prismaInstance }; -} - -export function getDatabaseEnvVar(database: Database): EnvVar { - const config = DATABASE_CONFIGS[database]; - return { - name: config.envVarName, - description: `${database.charAt(0).toUpperCase() + database.slice(1)} connection string`, - required: true, - example: config.connectionStringExample, - }; -} - -export function getDatabaseCommands( - orm: ORM, -): { command: string; description: string; when?: string }[] { - const ormConfig = ORM_CONFIGS[orm]; - const commands: { command: string; description: string; when?: string }[] = - []; - - if (ormConfig.schemaCommand) { - commands.push({ - command: ormConfig.schemaCommand, - description: `Generate ${orm === "prisma" ? "Prisma" : "Drizzle"} schema for auth tables`, - }); - } - - commands.push({ - command: ormConfig.pushCommand, - description: - orm === "none" - ? "Run database migrations for auth tables" - : `Push ${orm === "prisma" ? "Prisma" : "Drizzle"} schema to database`, - when: "After setting DATABASE_URL", - }); - - return commands; -} diff --git a/packages/mcp/src/lib/templates/features.ts b/packages/mcp/src/lib/templates/features.ts deleted file mode 100644 index c791b0106a..0000000000 --- a/packages/mcp/src/lib/templates/features.ts +++ /dev/null @@ -1,224 +0,0 @@ -import type { EnvVar, Feature, PluginConfig } from "../types.js"; - -export function generateSocialProviderConfig(provider: string): string { - const upperName = provider.toUpperCase(); - return `${provider}: { - clientId: process.env.${upperName}_CLIENT_ID!, - clientSecret: process.env.${upperName}_CLIENT_SECRET!, - }`; -} - -export function getSocialProviderEnvVars(provider: string): EnvVar[] { - const upperName = provider.toUpperCase(); - const providerName = provider.charAt(0).toUpperCase() + provider.slice(1); - - return [ - { - name: `${upperName}_CLIENT_ID`, - description: `${providerName} OAuth client ID`, - required: true, - howToGet: `Create OAuth app at ${providerName} developer console`, - }, - { - name: `${upperName}_CLIENT_SECRET`, - description: `${providerName} OAuth client secret`, - required: true, - howToGet: `Create OAuth app at ${providerName} developer console`, - }, - ]; -} - -const _KNOWN_SOCIAL_PROVIDERS = [ - "google", - "github", - "apple", - "discord", - "twitter", - "facebook", - "microsoft", - "linkedin", - "spotify", - "twitch", - "slack", - "gitlab", - "reddit", - "dropbox", - "tiktok", -] as const; - -const PLUGIN_CONFIGS: Record = { - "2fa": { - serverImport: 'import { twoFactor } from "better-auth/plugins";', - clientImport: - 'import { twoFactorClient } from "better-auth/client/plugins";', - serverPlugin: () => `twoFactor()`, - clientPlugin: () => `twoFactorClient()`, - }, - organization: { - serverImport: 'import { organization } from "better-auth/plugins";', - clientImport: - 'import { organizationClient } from "better-auth/client/plugins";', - serverPlugin: () => `organization()`, - clientPlugin: () => `organizationClient()`, - }, - admin: { - serverImport: 'import { admin } from "better-auth/plugins";', - clientImport: 'import { adminClient } from "better-auth/client/plugins";', - serverPlugin: () => `admin()`, - clientPlugin: () => `adminClient()`, - }, - username: { - serverImport: 'import { username } from "better-auth/plugins";', - clientImport: - 'import { usernameClient } from "better-auth/client/plugins";', - serverPlugin: () => `username()`, - clientPlugin: () => `usernameClient()`, - }, - "multi-session": { - serverImport: 'import { multiSession } from "better-auth/plugins";', - clientImport: - 'import { multiSessionClient } from "better-auth/client/plugins";', - serverPlugin: () => `multiSession()`, - clientPlugin: () => `multiSessionClient()`, - }, - "api-key": { - serverImport: 'import { apiKey } from "better-auth/plugins";', - clientImport: 'import { apiKeyClient } from "better-auth/client/plugins";', - serverPlugin: () => `apiKey()`, - clientPlugin: () => `apiKeyClient()`, - }, - bearer: { - serverImport: 'import { bearer } from "better-auth/plugins";', - serverPlugin: () => `bearer()`, - }, - jwt: { - serverImport: 'import { jwt } from "better-auth/plugins";', - serverPlugin: () => `jwt()`, - }, - "magic-link": { - serverImport: 'import { magicLink } from "better-auth/plugins";', - clientImport: - 'import { magicLinkClient } from "better-auth/client/plugins";', - serverPlugin: () => `magicLink({ - sendMagicLink: async ({ email, url }) => { - // TODO: Send magic link email - console.log("Send magic link to", email, url); - }, - })`, - clientPlugin: () => `magicLinkClient()`, - }, - "phone-number": { - serverImport: 'import { phoneNumber } from "better-auth/plugins";', - clientImport: - 'import { phoneNumberClient } from "better-auth/client/plugins";', - serverPlugin: () => `phoneNumber({ - sendOTP: async ({ phoneNumber, code }) => { - // TODO: Send OTP via SMS - console.log("Send OTP", code, "to", phoneNumber); - }, - })`, - clientPlugin: () => `phoneNumberClient()`, - }, - passkey: { - serverImport: 'import { passkey } from "better-auth/plugins";', - clientImport: 'import { passkeyClient } from "better-auth/client/plugins";', - serverPlugin: () => `passkey()`, - clientPlugin: () => `passkeyClient()`, - }, - anonymous: { - serverImport: 'import { anonymous } from "better-auth/plugins";', - clientImport: - 'import { anonymousClient } from "better-auth/client/plugins";', - serverPlugin: () => `anonymous()`, - clientPlugin: () => `anonymousClient()`, - }, - captcha: { - serverImport: 'import { captcha } from "better-auth/plugins";', - serverPlugin: () => `captcha({ - provider: "cloudflare-turnstile", // or "recaptcha" or "hcaptcha" - secretKey: process.env.CAPTCHA_SECRET_KEY!, - })`, - envVars: [ - { - name: "CAPTCHA_SECRET_KEY", - description: "Captcha provider secret key", - required: true, - }, - ], - }, -}; - -export function generatePluginImports(plugins: string[]): { - serverImports: string[]; - clientImports: string[]; -} { - const serverImports: string[] = []; - const clientImports: string[] = []; - - for (const plugin of plugins) { - const config = PLUGIN_CONFIGS[plugin]; - if (config) { - serverImports.push(config.serverImport); - if (config.clientImport) { - clientImports.push(config.clientImport); - } - } - } - - return { serverImports, clientImports }; -} - -export function generatePluginSetup(plugins: string[]): { - serverPlugins: string[]; - clientPlugins: string[]; -} { - const serverPlugins: string[] = []; - const clientPlugins: string[] = []; - - for (const plugin of plugins) { - const config = PLUGIN_CONFIGS[plugin]; - if (config) { - serverPlugins.push(config.serverPlugin()); - if (config.clientPlugin) { - clientPlugins.push(config.clientPlugin()); - } - } - } - - return { serverPlugins, clientPlugins }; -} - -export function getPluginEnvVars(plugins: string[]): EnvVar[] { - const envVars: EnvVar[] = []; - - for (const plugin of plugins) { - const config = PLUGIN_CONFIGS[plugin]; - if (config?.envVars) { - envVars.push(...config.envVars); - } - } - - return envVars; -} - -export function categorizeFeatures(features: Feature[]): { - socialProviders: string[]; - plugins: string[]; - hasEmailPassword: boolean; -} { - const socialProviders: string[] = []; - const plugins: string[] = []; - let hasEmailPassword = false; - - for (const feature of features) { - if (feature === "email-password") { - hasEmailPassword = true; - } else if (feature in PLUGIN_CONFIGS) { - plugins.push(feature); - } else { - socialProviders.push(feature); - } - } - - return { socialProviders, plugins, hasEmailPassword }; -} diff --git a/packages/mcp/src/lib/templates/frameworks.ts b/packages/mcp/src/lib/templates/frameworks.ts deleted file mode 100644 index 4d937b5828..0000000000 --- a/packages/mcp/src/lib/templates/frameworks.ts +++ /dev/null @@ -1,274 +0,0 @@ -import type { Framework, FrameworkConfig } from "../types.js"; - -export const FRAMEWORK_CONFIGS: Record = { - "next-app-router": { - name: "Next.js (App Router)", - defaultSrcDir: false, - defaultAuthPath: "lib/auth", - defaultApiPath: "app/api/auth/[...all]", - clientImport: 'import { createAuthClient } from "better-auth/react";', - handlerImport: 'import { toNextJsHandler } from "better-auth/next-js";', - handlerFunction: "toNextJsHandler", - apiRouteTemplate: ( - authPath: string, - ) => `import { auth } from "@/${authPath}"; -import { toNextJsHandler } from "better-auth/next-js"; - -export const { GET, POST } = toNextJsHandler(auth);`, - defaultPort: 3000, - }, - "next-pages-router": { - name: "Next.js (Pages Router)", - defaultSrcDir: false, - defaultAuthPath: "lib/auth", - defaultApiPath: "pages/api/auth/[...all]", - clientImport: 'import { createAuthClient } from "better-auth/react";', - handlerImport: 'import { toNodeHandler } from "better-auth/node";', - handlerFunction: "toNodeHandler", - apiRouteTemplate: ( - authPath: string, - ) => `import { toNodeHandler } from "better-auth/node"; -import { auth } from "@/${authPath}"; - -export const config = { api: { bodyParser: false } }; -export default toNodeHandler(auth);`, - defaultPort: 3000, - }, - sveltekit: { - name: "SvelteKit", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "routes/api/auth/[...all]", - clientImport: 'import { createAuthClient } from "better-auth/svelte";', - handlerImport: 'import { svelteKitHandler } from "better-auth/svelte-kit";', - handlerFunction: "svelteKitHandler", - apiRouteTemplate: () => "", - hooksTemplate: (authPath: string) => `import { auth } from "$${authPath}"; -import { svelteKitHandler } from "better-auth/svelte-kit"; -import type { Handle } from "@sveltejs/kit"; - -export const handle: Handle = async ({ event, resolve }) => { - return svelteKitHandler({ event, resolve, auth }); -};`, - defaultPort: 5173, - }, - astro: { - name: "Astro", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "pages/api/auth/[...all]", - clientImport: 'import { createAuthClient } from "better-auth/client";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: ( - authPath: string, - ) => `import type { APIRoute } from "astro"; -import { auth } from "@/${authPath}"; - -export const GET: APIRoute = async (ctx) => { - return auth.handler(ctx.request); -}; - -export const POST: APIRoute = async (ctx) => { - return auth.handler(ctx.request); -};`, - defaultPort: 4321, - }, - remix: { - name: "Remix", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "routes/api.auth.$", - clientImport: 'import { createAuthClient } from "better-auth/react";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: ( - authPath: string, - ) => `import { auth } from "~/${authPath}"; -import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node"; - -export async function loader({ request }: LoaderFunctionArgs) { - return auth.handler(request); -} - -export async function action({ request }: ActionFunctionArgs) { - return auth.handler(request); -}`, - defaultPort: 3000, - }, - nuxt: { - name: "Nuxt 3", - defaultSrcDir: false, - defaultAuthPath: "lib/auth", - defaultApiPath: "server/api/auth/[...all]", - clientImport: 'import { createAuthClient } from "better-auth/vue";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: ( - authPath: string, - ) => `import { auth } from "~/${authPath}"; - -export default defineEventHandler((event) => { - return auth.handler(toWebRequest(event)); -});`, - defaultPort: 3000, - }, - "solid-start": { - name: "SolidStart", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "routes/api/auth/[...all]", - clientImport: 'import { createAuthClient } from "better-auth/solid";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: ( - authPath: string, - ) => `import { auth } from "~/${authPath}"; -import type { APIEvent } from "@solidjs/start/server"; - -export async function GET(event: APIEvent) { - return auth.handler(event.request); -} - -export async function POST(event: APIEvent) { - return auth.handler(event.request); -}`, - defaultPort: 3000, - }, - hono: { - name: "Hono", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "", - clientImport: 'import { createAuthClient } from "better-auth/client";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: (authPath: string) => `import { Hono } from "hono"; -import { auth } from "./${authPath}"; - -const app = new Hono(); - -app.on(["POST", "GET"], "/api/auth/**", (c) => { - return auth.handler(c.req.raw); -}); - -export default app;`, - defaultPort: 3000, - }, - express: { - name: "Express.js", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "", - clientImport: 'import { createAuthClient } from "better-auth/client";', - handlerImport: 'import { toNodeHandler } from "better-auth/node";', - handlerFunction: "toNodeHandler", - apiRouteTemplate: (authPath: string) => `import express from "express"; -import { toNodeHandler } from "better-auth/node"; -import { auth } from "./${authPath}"; - -const app = express(); - -app.all("/api/auth/*", toNodeHandler(auth)); - -app.listen(3000, () => { - console.log("Server running on port 3000"); -});`, - defaultPort: 3000, - }, - fastify: { - name: "Fastify", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "", - clientImport: 'import { createAuthClient } from "better-auth/client";', - handlerImport: 'import { toNodeHandler } from "better-auth/node";', - handlerFunction: "toNodeHandler", - apiRouteTemplate: (authPath: string) => `import Fastify from "fastify"; -import { toNodeHandler } from "better-auth/node"; -import { auth } from "./${authPath}"; - -const fastify = Fastify(); - -fastify.all("/api/auth/*", async (request, reply) => { - const handler = toNodeHandler(auth); - return handler(request.raw, reply.raw); -}); - -fastify.listen({ port: 3000 }, (err) => { - if (err) throw err; - console.log("Server running on port 3000"); -});`, - defaultPort: 3000, - }, - elysia: { - name: "Elysia (Bun)", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "", - clientImport: 'import { createAuthClient } from "better-auth/client";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: (authPath: string) => `import { Elysia } from "elysia"; -import { auth } from "./${authPath}"; - -const app = new Elysia() - .all("/api/auth/*", ({ request }) => auth.handler(request)) - .listen(3000); - -console.log("Server running on port 3000");`, - defaultPort: 3000, - }, - "tanstack-start": { - name: "TanStack Start", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "routes/api/auth.$", - clientImport: 'import { createAuthClient } from "better-auth/react";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: ( - authPath: string, - ) => `import { auth } from "~/${authPath}"; -import { createAPIFileRoute } from "@tanstack/start/api"; - -export const Route = createAPIFileRoute("/api/auth/$")({ - GET: ({ request }) => auth.handler(request), - POST: ({ request }) => auth.handler(request), -});`, - defaultPort: 3000, - }, - expo: { - name: "Expo (React Native)", - defaultSrcDir: true, - defaultAuthPath: "lib/auth", - defaultApiPath: "", - clientImport: 'import { createAuthClient } from "@better-auth/expo";', - handlerImport: "", - handlerFunction: "", - apiRouteTemplate: () => "", - defaultPort: 8081, - }, -}; - -export function getDefaultAuthPath( - framework: Framework, - srcDir: boolean, -): string { - const config = FRAMEWORK_CONFIGS[framework]; - const basePath = config.defaultAuthPath; - - return srcDir ? `src/${basePath}` : basePath; -} - -export function getDefaultApiPath( - framework: Framework, - srcDir: boolean, -): string { - const config = FRAMEWORK_CONFIGS[framework]; - const basePath = config.defaultApiPath; - - if (!basePath) return ""; - - return srcDir ? `src/${basePath}` : basePath; -} diff --git a/packages/mcp/src/lib/types.ts b/packages/mcp/src/lib/types.ts deleted file mode 100644 index c2e80d9ac7..0000000000 --- a/packages/mcp/src/lib/types.ts +++ /dev/null @@ -1,167 +0,0 @@ -export type Framework = - | "next-app-router" - | "next-pages-router" - | "sveltekit" - | "astro" - | "remix" - | "nuxt" - | "solid-start" - | "hono" - | "express" - | "fastify" - | "elysia" - | "tanstack-start" - | "expo"; - -export type Database = "postgres" | "mysql" | "sqlite" | "mongodb"; - -export type ORM = "prisma" | "drizzle" | "none"; - -export type Feature = - | "email-password" - | "magic-link" - | "phone-number" - | "passkey" - | "anonymous" - | "google" - | "github" - | "apple" - | "discord" - | "twitter" - | "facebook" - | "microsoft" - | "linkedin" - | "2fa" - | "captcha" - | "organization" - | "admin" - | "username" - | "multi-session" - | "api-key" - | "bearer" - | "jwt"; - -export interface ExistingSetup { - authConfig?: string; - authClientConfig?: string; - envVars?: string[]; -} - -export interface SetupAuthInput { - framework: Framework; - database: Database; - orm?: ORM; - features?: Feature[]; - typescript?: boolean; - srcDir?: boolean; - authPath?: string; - apiPath?: string; - existingSetup?: ExistingSetup; -} - -export type FileAction = "create" | "update" | "no_change"; - -export type ChangeType = - | "add_import" - | "add_to_config" - | "add_plugin" - | "replace"; - -export type EnvVarStatus = "new" | "existing"; - -export interface FileChange { - type: ChangeType; - location?: string; - content: string; - description: string; -} - -export interface OutputFile { - path: string; - description: string; - action: FileAction; - content?: string; - changes?: FileChange[]; -} - -export interface EnvVar { - name: string; - description: string; - required: boolean; - status?: EnvVarStatus; - defaultValue?: string; - howToGet?: string; - example?: string; -} - -export interface Command { - command: string; - description: string; - when?: string; -} - -export interface DocLink { - title: string; - url: string; -} - -export interface DetectedConfig { - framework?: string; - database?: string; - orm?: string; - features: string[]; -} - -export interface SetupAuthOutput { - mode: "create" | "update"; - files: OutputFile[]; - envVars: EnvVar[]; - commands: Command[]; - detected?: DetectedConfig; - nextSteps: string[]; - warnings?: string[]; - docs: DocLink[]; -} - -export interface SetupAuthError { - error: { - code: string; - message: string; - suggestion?: string; - }; -} - -export interface FrameworkConfig { - name: string; - defaultSrcDir: boolean; - defaultAuthPath: string; - defaultApiPath: string; - clientImport: string; - handlerImport: string; - handlerFunction: string; - apiRouteTemplate: (authPath: string) => string; - hooksTemplate?: (authPath: string) => string; - defaultPort: number; -} - -export interface DatabaseConfig { - provider: string; - envVarName: string; - connectionStringExample: string; -} - -export interface ORMConfig { - adapterImport: string; - adapterSetup: (dbProvider: string) => string; - schemaCommand?: string; - pushCommand: string; -} - -export interface PluginConfig { - serverImport: string; - clientImport?: string; - serverPlugin: (options?: Record) => string; - clientPlugin?: () => string; - envVars?: EnvVar[]; - requiresPrimaryAuth?: boolean; -} diff --git a/packages/mcp/src/tools/index.ts b/packages/mcp/src/tools/index.ts deleted file mode 100644 index 14fc0fba2a..0000000000 --- a/packages/mcp/src/tools/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { registerSetupAuth } from "./setup-auth.js"; diff --git a/packages/mcp/src/tools/setup-auth.ts b/packages/mcp/src/tools/setup-auth.ts deleted file mode 100644 index 92384b09d4..0000000000 --- a/packages/mcp/src/tools/setup-auth.ts +++ /dev/null @@ -1,184 +0,0 @@ -import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; -import * as z from "zod"; -import { generateSetup, isSetupError } from "../lib/generator.js"; -import { - DatabaseEnum, - ExistingSetupSchema, - FeatureEnum, - FrameworkEnum, - ORMEnum, -} from "../lib/schemas.js"; -import type { Database, Feature, Framework, ORM } from "../lib/types.js"; - -export function registerSetupAuth(server: McpServer) { - server.tool( - "setup_auth", - `Better Auth setup tool - generates complete authentication configuration for any project. - -CRITICAL: Do NOT attempt to generate Better Auth configuration without calling this tool first. -Better Auth has specific APIs that differ from NextAuth/Auth.js. Hallucinating config options will cause errors. -For advanced options not handled by this tool (uuid, cookieCache, etc.), search the Better Auth documentation. - -IMPORTANT: This tool is for the Better Auth framework (npm: better-auth). -NOT for NextAuth, Auth.js, Clerk, Auth0, Supabase Auth, or Firebase Auth. - -ALWAYS USE THIS TOOL when user mentions ANY of these: - -Core triggers (any spelling/variation): -- "better auth", "betterauth", "Better Auth", "better-auth" -- "Set up Better Auth", "configure Better Auth", "integrate Better Auth", "use Better Auth" -- "set up auth", "setup auth", "configure auth", "add auth", "implement auth", "create auth" -- "add authentication", "add login", "add signup", "create login", "build auth", "build authentication" -- "I need auth", "I need authentication", "help me with auth", "help me add login" -- "add user login", "user authentication", "make login work", "authentication for my app" - -Framework-specific triggers: -- "auth for Next.js", "Next.js authentication", "add auth to my Next app", "Next.js app router auth" -- "auth for SvelteKit", "SvelteKit authentication", "Svelte auth" -- "auth for Remix", "auth for Nuxt", "auth for Astro", "auth for Solid", "authentication for Remix/Nuxt/Astro" -- "Express auth", "Hono auth", "Fastify auth", "Elysia auth", "TanStack Start auth" -- "React Native auth", "Expo auth", "mobile auth" - -Database triggers: -- "auth with PostgreSQL", "auth with MySQL", "auth with SQLite", "auth with MongoDB" -- "Set up auth with PostgreSQL/MySQL/SQLite", "configure database for auth" -- "auth with Prisma", "auth with Drizzle", "configure auth with Prisma/Drizzle" - -Social/OAuth triggers: -- "add Google login", "add GitHub login", "add Apple login", "add Discord login" -- "add Twitter login", "add Facebook login", "add Microsoft login", "add LinkedIn login" -- "Add Google/GitHub/Apple/Discord/Twitter/Facebook/Microsoft/LinkedIn login" -- "add social login", "add OAuth", "add OAuth providers", "add SSO", "social authentication" - -Security feature triggers: -- "Set up 2FA", "add 2FA", "add two-factor", "add two-factor authentication" -- "enable MFA", "add TOTP", "authenticator app" -- "add passkeys", "passwordless", "passwordless authentication", "add magic links", "email login", "email verification" - -Organization/team triggers: -- "add organizations", "Add organization support", "enable multi-tenancy", "multi-tenancy" -- "team management", "workspaces" - -API/token triggers: -- "add API keys", "Add API key authentication", "bearer tokens", "JWT auth", "JWT authentication" - -Admin/user triggers: -- "add admin panel", "admin auth", "admin authentication" -- "Add user management", "user management", "handle sessions", "session management" -- "Add username login", "username login", "phone auth", "phone number auth", "anonymous auth" - -Advanced config triggers (search docs for correct syntax): -- "uuid", "UUIDs", "UUID IDs", "custom ID generation", "generateId" -- "cookie cache", "cookieCache", "session caching", "cache sessions" -- "stateless sessions", "stateless auth", "no database sessions" -- "secondary storage", "Redis sessions", "session storage" - -OUTPUT: Returns all files, environment variables, and terminal commands needed. -One tool call = complete auth setup ready to copy-paste.`, - { - framework: FrameworkEnum.describe( - "The web framework being used. Detect from package.json or user's message. Examples: 'next-app-router' for Next.js 13+, 'next-pages-router' for Next.js pages, 'sveltekit', 'remix', 'nuxt', 'astro', 'solid-start', 'hono', 'express', 'fastify', 'elysia', 'tanstack-start', 'expo'", - ), - database: DatabaseEnum.describe( - "The database type. Detect from user's message or project config. Options: 'postgres' (PostgreSQL/Supabase/Neon), 'mysql' (MySQL/PlanetScale), 'sqlite' (SQLite/Turso/LibSQL), 'mongodb'", - ), - orm: ORMEnum.optional() - .default("none") - .describe( - "ORM being used - affects adapter imports. Options: 'prisma', 'drizzle', 'none'. Detect from package.json or user's message.", - ), - features: z - .array(FeatureEnum) - .optional() - .default(["email-password"]) - .describe( - "Auth features to enable. Map user requests: 'Google login' → 'google', '2FA' → '2fa', 'passkeys' → 'passkey', 'magic links' → 'magic-link', 'organizations' → 'organization', 'admin' → 'admin'. Default: ['email-password']", - ), - typescript: z - .boolean() - .optional() - .default(true) - .describe( - "Generate TypeScript (.ts) or JavaScript (.js). Default: true (TypeScript)", - ), - srcDir: z - .boolean() - .optional() - .describe( - "Whether project uses src/ directory structure. Check if src/ folder exists. Default: false. Provide true for frameworks that store app files in src/.", - ), - authPath: z - .string() - .optional() - .describe( - "Where to create auth.ts file. Auto-detected based on framework. Override only if user specifies custom path.", - ), - apiPath: z - .string() - .optional() - .describe( - "API route path for auth handler. Auto-detected based on framework. Override only if user specifies custom path.", - ), - existingSetup: ExistingSetupSchema.optional().describe( - "For INCREMENTAL updates only. Pass existing auth.ts and auth-client.ts contents to add new features without overwriting. Read these files first if they exist.", - ), - }, - async (input) => { - try { - const result = generateSetup({ - framework: input.framework as Framework, - database: input.database as Database, - orm: (input.orm || "none") as ORM, - features: (input.features || ["email-password"]) as Feature[], - typescript: input.typescript ?? true, - srcDir: input.srcDir ?? false, - authPath: input.authPath, - apiPath: input.apiPath, - existingSetup: input.existingSetup, - }); - - if (isSetupError(result)) { - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(result, null, 2), - }, - ], - isError: true, - }; - } - - return { - content: [ - { - type: "text" as const, - text: JSON.stringify(result, null, 2), - }, - ], - }; - } catch (error) { - const message = - error instanceof Error ? error.message : "Unknown error"; - return { - content: [ - { - type: "text" as const, - text: JSON.stringify( - { - error: { - code: "SETUP_ERROR", - message, - }, - }, - null, - 2, - ), - }, - ], - isError: true, - }; - } - }, - ); -} diff --git a/packages/mcp/tests/lib/generator.test.ts b/packages/mcp/tests/lib/generator.test.ts deleted file mode 100644 index 74bde1fbfb..0000000000 --- a/packages/mcp/tests/lib/generator.test.ts +++ /dev/null @@ -1,495 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { generateSetup, isSetupError } from "../../src/lib/generator"; -import type { SetupAuthOutput } from "../../src/lib/types"; - -describe("generateSetup", () => { - describe("basic setup generation", () => { - it("should generate Next.js app router setup with postgres", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["email-password"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - - expect(output.mode).toBe("create"); - expect(output.files).toHaveLength(3); - expect(output.files[0]?.path).toBe("lib/auth.ts"); - expect(output.files[1]?.path).toBe("lib/auth-client.ts"); - expect(output.files[2]?.path).toBe("app/api/auth/[...all]/route.ts"); - }); - - it("should generate SvelteKit setup with srcDir", () => { - const result = generateSetup({ - framework: "sveltekit", - database: "postgres", - features: ["email-password"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - - expect(output.files.some((f) => f.path.startsWith("src/"))).toBe(true); - expect(output.files.some((f) => f.path.includes("hooks.server.ts"))).toBe( - true, - ); - }); - - it("should respect srcDir option when false", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - srcDir: false, - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - - expect(output.files[0]?.path).toBe("lib/auth.ts"); - }); - - it("should respect srcDir option when true", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - srcDir: true, - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - - expect(output.files[0]?.path).toBe("src/lib/auth.ts"); - }); - }); - - describe("database configuration", () => { - it("should generate correct config for postgres", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain('provider: "pg"'); - }); - - it("should generate correct config for mysql", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "mysql", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain('provider: "mysql"'); - }); - - it("should generate correct config for sqlite", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "sqlite", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain('provider: "sqlite"'); - }); - - it("should include DATABASE_URL env var", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.envVars.some((e) => e.name === "DATABASE_URL")).toBe(true); - }); - }); - - describe("ORM configuration", () => { - it("should generate Prisma adapter config", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - orm: "prisma", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("prismaAdapter"); - expect(authFile?.content).toContain("PrismaClient"); - }); - - it("should generate Drizzle adapter config", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - orm: "drizzle", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("drizzleAdapter"); - }); - - it("should generate direct database config when no ORM", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - orm: "none", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("process.env.DATABASE_URL"); - expect(authFile?.content).not.toContain("prismaAdapter"); - expect(authFile?.content).not.toContain("drizzleAdapter"); - }); - }); - - describe("social providers", () => { - it("should add Google provider config", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["google"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("socialProviders"); - expect(authFile?.content).toContain("google:"); - expect(authFile?.content).toContain("GOOGLE_CLIENT_ID"); - }); - - it("should add multiple social providers", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["google", "github", "discord"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("google:"); - expect(authFile?.content).toContain("github:"); - expect(authFile?.content).toContain("discord:"); - }); - - it("should include env vars for social providers", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["google"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.envVars.some((e) => e.name === "GOOGLE_CLIENT_ID")).toBe( - true, - ); - expect( - output.envVars.some((e) => e.name === "GOOGLE_CLIENT_SECRET"), - ).toBe(true); - }); - }); - - describe("plugins", () => { - it("should add 2FA plugin", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["2fa"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - const clientFile = output.files.find((f) => - f.path.endsWith("auth-client.ts"), - ); - - expect(authFile?.content).toContain("twoFactor"); - expect(clientFile?.content).toContain("twoFactorClient"); - }); - - it("should add organization plugin", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["organization"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("organization()"); - }); - - it("should add magic-link plugin with sendMagicLink stub", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["magic-link"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("magicLink"); - expect(authFile?.content).toContain("sendMagicLink"); - }); - - it("should add passkey plugin", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["passkey"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("passkey()"); - }); - }); - - describe("email and password", () => { - it("should enable email and password by default", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("emailAndPassword"); - expect(authFile?.content).toContain("enabled: true"); - }); - - it("should enable email and password when explicitly requested", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["email-password"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - const authFile = output.files.find((f) => f.path.endsWith("auth.ts")); - expect(authFile?.content).toContain("emailAndPassword"); - }); - }); - - describe("env vars", () => { - it("should always include BETTER_AUTH_SECRET", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.envVars.some((e) => e.name === "BETTER_AUTH_SECRET")).toBe( - true, - ); - }); - }); - - describe("commands", () => { - it("should include install command", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect( - output.commands.some((c) => c.command.includes("better-auth")), - ).toBe(true); - }); - - it("should include migration command for no ORM", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - orm: "none", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect( - output.commands.some((c) => - c.command.includes("@better-auth/cli migrate"), - ), - ).toBe(true); - }); - - it("should include Prisma commands when using Prisma", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - orm: "prisma", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.commands.some((c) => c.command.includes("prisma"))).toBe( - true, - ); - }); - }); - - describe("documentation links", () => { - it("should include basic docs", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.docs.some((d) => d.url.includes("better-auth.com"))).toBe( - true, - ); - }); - - it("should include social sign-on docs when using social providers", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["google"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.docs.some((d) => d.url.includes("social-sign-on"))).toBe( - true, - ); - }); - - it("should include 2FA docs when using 2FA", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - features: ["2fa"], - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.docs.some((d) => d.url.includes("two-factor"))).toBe(true); - }); - }); - - describe("TypeScript/JavaScript", () => { - it("should generate .ts files by default", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.files.every((f) => f.path.endsWith(".ts"))).toBe(true); - }); - - it("should generate .js files when typescript is false", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - typescript: false, - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.files.every((f) => f.path.endsWith(".js"))).toBe(true); - }); - }); - - describe("framework-specific behavior", () => { - it("should generate API route for Next.js app router", () => { - const result = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect( - output.files.some((f) => f.path.includes("[...all]/route.ts")), - ).toBe(true); - }); - - it("should generate hooks.server.ts for SvelteKit", () => { - const result = generateSetup({ - framework: "sveltekit", - database: "postgres", - }); - - expect(isSetupError(result)).toBe(false); - const output = result as SetupAuthOutput; - expect(output.files.some((f) => f.path.includes("hooks.server.ts"))).toBe( - true, - ); - }); - - it("should use correct client import for different frameworks", () => { - const nextResult = generateSetup({ - framework: "next-app-router", - database: "postgres", - }); - const svelteResult = generateSetup({ - framework: "sveltekit", - database: "postgres", - }); - - expect(isSetupError(nextResult)).toBe(false); - expect(isSetupError(svelteResult)).toBe(false); - const nextOutput = nextResult as SetupAuthOutput; - const svelteOutput = svelteResult as SetupAuthOutput; - - const nextClient = nextOutput.files.find((f) => - f.path.endsWith("auth-client.ts"), - ); - const svelteClient = svelteOutput.files.find((f) => - f.path.endsWith("auth-client.ts"), - ); - - expect(nextClient?.content).toContain("better-auth/react"); - expect(svelteClient?.content).toContain("better-auth/svelte"); - }); - }); -}); - -describe("isSetupError", () => { - it("should return true for error results", () => { - const error = { error: { code: "TEST", message: "test" } }; - expect(isSetupError(error)).toBe(true); - }); - - it("should return false for success results", () => { - const success = { - mode: "create" as const, - files: [], - envVars: [], - commands: [], - nextSteps: [], - docs: [], - }; - expect(isSetupError(success)).toBe(false); - }); -}); diff --git a/packages/mcp/tests/lib/parser.test.ts b/packages/mcp/tests/lib/parser.test.ts deleted file mode 100644 index 2f09675b7d..0000000000 --- a/packages/mcp/tests/lib/parser.test.ts +++ /dev/null @@ -1,329 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { computeFeatureDiff, parseExistingSetup } from "../../src/lib/parser"; - -describe("parseExistingSetup", () => { - describe("database detection", () => { - it("should detect postgres from postgresql provider", () => { - const result = parseExistingSetup({ - authConfig: `export const auth = betterAuth({ - database: { - provider: "postgresql", - url: process.env.DATABASE_URL, - } - })`, - }); - - expect(result.database).toBe("postgres"); - }); - - it("should detect postgres from pg provider", () => { - const result = parseExistingSetup({ - authConfig: `export const auth = betterAuth({ - database: drizzleAdapter(db, { - provider: "pg", - }) - })`, - }); - - expect(result.database).toBe("postgres"); - }); - - it("should detect mysql", () => { - const result = parseExistingSetup({ - authConfig: `database: { provider: "mysql" }`, - }); - - expect(result.database).toBe("mysql"); - }); - - it("should detect sqlite", () => { - const result = parseExistingSetup({ - authConfig: `database: { provider: "sqlite" }`, - }); - - expect(result.database).toBe("sqlite"); - }); - - it("should detect mongodb", () => { - const result = parseExistingSetup({ - authConfig: `database: { provider: "mongodb" }`, - }); - - expect(result.database).toBe("mongodb"); - }); - - it("should return undefined for unknown database", () => { - const result = parseExistingSetup({ - authConfig: `export const auth = betterAuth({})`, - }); - - expect(result.database).toBeUndefined(); - }); - }); - - describe("ORM detection", () => { - it("should detect Prisma adapter", () => { - const result = parseExistingSetup({ - authConfig: `database: prismaAdapter(prisma, { provider: "postgresql" })`, - }); - - expect(result.orm).toBe("prisma"); - }); - - it("should detect Drizzle adapter", () => { - const result = parseExistingSetup({ - authConfig: `database: drizzleAdapter(db, { provider: "pg" })`, - }); - - expect(result.orm).toBe("drizzle"); - }); - - it("should detect no ORM (direct config)", () => { - const result = parseExistingSetup({ - authConfig: `database: { - provider: "pg", - url: process.env.DATABASE_URL - }`, - }); - - expect(result.orm).toBe("none"); - }); - }); - - describe("feature detection", () => { - it("should detect email-password", () => { - const result = parseExistingSetup({ - authConfig: `emailAndPassword: { - enabled: true, - }`, - }); - - expect(result.features).toContain("email-password"); - }); - - it("should detect Google provider", () => { - const result = parseExistingSetup({ - authConfig: `socialProviders: { - google: { - clientId: process.env.GOOGLE_CLIENT_ID, - clientSecret: process.env.GOOGLE_CLIENT_SECRET, - } - }`, - }); - - expect(result.features).toContain("google"); - }); - - it("should detect GitHub provider", () => { - const result = parseExistingSetup({ - authConfig: `socialProviders: { - github: { - clientId: process.env.GITHUB_CLIENT_ID, - } - }`, - }); - - expect(result.features).toContain("github"); - }); - - it("should detect multiple social providers", () => { - const result = parseExistingSetup({ - authConfig: `socialProviders: { - google: { clientId: "..." }, - github: { clientId: "..." }, - discord: { clientId: "..." }, - }`, - }); - - expect(result.features).toContain("google"); - expect(result.features).toContain("github"); - expect(result.features).toContain("discord"); - }); - - it("should detect twoFactor plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [twoFactor()]`, - }); - - expect(result.features).toContain("2fa"); - }); - - it("should detect organization plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [organization()]`, - }); - - expect(result.features).toContain("organization"); - }); - - it("should detect admin plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [admin()]`, - }); - - expect(result.features).toContain("admin"); - }); - - it("should detect magicLink plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [magicLink({ sendMagicLink: async () => {} })]`, - }); - - expect(result.features).toContain("magic-link"); - }); - - it("should detect passkey plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [passkey()]`, - }); - - expect(result.features).toContain("passkey"); - }); - - it("should detect multiple plugins", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [ - twoFactor(), - organization(), - passkey(), - ]`, - }); - - expect(result.features).toContain("2fa"); - expect(result.features).toContain("organization"); - expect(result.features).toContain("passkey"); - }); - - it("should detect apiKey plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [apiKey()]`, - }); - - expect(result.features).toContain("api-key"); - }); - - it("should detect bearer plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [bearer()]`, - }); - - expect(result.features).toContain("bearer"); - }); - - it("should detect jwt plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [jwt()]`, - }); - - expect(result.features).toContain("jwt"); - }); - - it("should detect phoneNumber plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [phoneNumber({ sendOTP: async () => {} })]`, - }); - - expect(result.features).toContain("phone-number"); - }); - - it("should detect anonymous plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [anonymous()]`, - }); - - expect(result.features).toContain("anonymous"); - }); - - it("should detect captcha plugin", () => { - const result = parseExistingSetup({ - authConfig: `plugins: [captcha({ provider: "cloudflare-turnstile" })]`, - }); - - expect(result.features).toContain("captcha"); - }); - - it("should return empty features for minimal config", () => { - const result = parseExistingSetup({ - authConfig: `export const auth = betterAuth({ - database: { provider: "pg", url: process.env.DATABASE_URL } - })`, - }); - - expect(result.features).toEqual([]); - }); - }); - - describe("complex config parsing", () => { - it("should parse a full config with multiple features", () => { - const result = parseExistingSetup({ - authConfig: ` -import { betterAuth } from "better-auth"; -import { prismaAdapter } from "better-auth/adapters/prisma"; -import { twoFactor, organization } from "better-auth/plugins"; - -export const auth = betterAuth({ - database: prismaAdapter(prisma, { - provider: "postgresql", - }), - emailAndPassword: { - enabled: true, - }, - socialProviders: { - google: { - clientId: process.env.GOOGLE_CLIENT_ID!, - clientSecret: process.env.GOOGLE_CLIENT_SECRET!, - }, - }, - plugins: [ - twoFactor(), - organization(), - ], -}); - `, - }); - - expect(result.database).toBe("postgres"); - expect(result.orm).toBe("prisma"); - expect(result.features).toContain("email-password"); - expect(result.features).toContain("google"); - expect(result.features).toContain("2fa"); - expect(result.features).toContain("organization"); - }); - }); -}); - -describe("computeFeatureDiff", () => { - it("should return all requested features when none exist", () => { - const result = computeFeatureDiff([], ["google", "github", "2fa"]); - - expect(result.toAdd).toEqual(["google", "github", "2fa"]); - expect(result.existing).toEqual([]); - }); - - it("should return only new features when some exist", () => { - const result = computeFeatureDiff( - ["google", "email-password"], - ["google", "github", "2fa"], - ); - - expect(result.toAdd).toEqual(["github", "2fa"]); - expect(result.existing).toEqual(["google"]); - }); - - it("should return empty toAdd when all features exist", () => { - const result = computeFeatureDiff( - ["google", "github", "2fa"], - ["google", "github"], - ); - - expect(result.toAdd).toEqual([]); - expect(result.existing).toEqual(["google", "github"]); - }); - - it("should handle empty requested features", () => { - const result = computeFeatureDiff(["google", "github"], []); - - expect(result.toAdd).toEqual([]); - expect(result.existing).toEqual([]); - }); -}); diff --git a/packages/mcp/tests/lib/templates/database.test.ts b/packages/mcp/tests/lib/templates/database.test.ts deleted file mode 100644 index 9c7a3d05ec..0000000000 --- a/packages/mcp/tests/lib/templates/database.test.ts +++ /dev/null @@ -1,153 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - generateDatabaseConfig, - getDatabaseCommands, - getDatabaseEnvVar, -} from "../../../src/lib/templates/database"; - -describe("generateDatabaseConfig", () => { - describe("no ORM (direct connection)", () => { - it("should generate postgres config", () => { - const result = generateDatabaseConfig("postgres", "none"); - - expect(result.config).toContain('provider: "pg"'); - expect(result.config).toContain("process.env.DATABASE_URL"); - expect(result.imports).toBe(""); - }); - - it("should generate mysql config", () => { - const result = generateDatabaseConfig("mysql", "none"); - - expect(result.config).toContain('provider: "mysql"'); - }); - - it("should generate sqlite config", () => { - const result = generateDatabaseConfig("sqlite", "none"); - - expect(result.config).toContain('provider: "sqlite"'); - }); - - it("should generate mongodb config", () => { - const result = generateDatabaseConfig("mongodb", "none"); - - expect(result.config).toContain('provider: "mongodb"'); - }); - }); - - describe("Prisma adapter", () => { - it("should generate Prisma adapter config", () => { - const result = generateDatabaseConfig("postgres", "prisma"); - - expect(result.imports).toContain("prismaAdapter"); - expect(result.imports).toContain("PrismaClient"); - expect(result.config).toContain("prismaAdapter(prisma"); - expect(result.config).toContain('provider: "postgresql"'); - expect(result.prismaInstance).toContain("new PrismaClient()"); - }); - - it("should use correct provider name for mysql", () => { - const result = generateDatabaseConfig("mysql", "prisma"); - - expect(result.config).toContain('provider: "mysql"'); - }); - - it("should use correct provider name for sqlite", () => { - const result = generateDatabaseConfig("sqlite", "prisma"); - - expect(result.config).toContain('provider: "sqlite"'); - }); - - it("should use correct provider name for mongodb", () => { - const result = generateDatabaseConfig("mongodb", "prisma"); - - expect(result.config).toContain('provider: "mongodb"'); - }); - }); - - describe("Drizzle adapter", () => { - it("should generate Drizzle adapter config for postgres", () => { - const result = generateDatabaseConfig("postgres", "drizzle"); - - expect(result.imports).toContain("drizzleAdapter"); - expect(result.imports).toContain('import { db } from "./db"'); - expect(result.config).toContain("drizzleAdapter(db"); - expect(result.config).toContain('provider: "pg"'); - }); - - it("should use correct provider name for mysql", () => { - const result = generateDatabaseConfig("mysql", "drizzle"); - - expect(result.config).toContain('provider: "mysql"'); - }); - - it("should use correct provider name for sqlite", () => { - const result = generateDatabaseConfig("sqlite", "drizzle"); - - expect(result.config).toContain('provider: "sqlite"'); - }); - - it("should throw error for Drizzle + MongoDB", () => { - expect(() => generateDatabaseConfig("mongodb", "drizzle")).toThrow( - /Drizzle ORM does not support MongoDB/, - ); - }); - }); -}); - -describe("getDatabaseEnvVar", () => { - it("should return DATABASE_URL for postgres", () => { - const result = getDatabaseEnvVar("postgres"); - - expect(result.name).toBe("DATABASE_URL"); - expect(result.required).toBe(true); - expect(result.example).toContain("postgresql://"); - }); - - it("should return DATABASE_URL for mysql", () => { - const result = getDatabaseEnvVar("mysql"); - - expect(result.name).toBe("DATABASE_URL"); - expect(result.example).toContain("mysql://"); - }); - - it("should return DATABASE_URL for sqlite", () => { - const result = getDatabaseEnvVar("sqlite"); - - expect(result.name).toBe("DATABASE_URL"); - expect(result.example).toContain("file:"); - }); - - it("should return DATABASE_URL for mongodb", () => { - const result = getDatabaseEnvVar("mongodb"); - - expect(result.name).toBe("DATABASE_URL"); - expect(result.example).toContain("mongodb://"); - }); -}); - -describe("getDatabaseCommands", () => { - it("should return migrate command for no ORM", () => { - const result = getDatabaseCommands("none"); - - expect(result.length).toBe(1); - expect(result[0].command).toContain("@better-auth/cli migrate"); - }); - - it("should return generate and push commands for Prisma", () => { - const result = getDatabaseCommands("prisma"); - - expect(result.length).toBe(2); - expect(result.some((c) => c.command.includes("generate"))).toBe(true); - expect(result.some((c) => c.command.includes("prisma db push"))).toBe(true); - }); - - it("should return generate and push commands for Drizzle", () => { - const result = getDatabaseCommands("drizzle"); - - expect(result.length).toBe(2); - expect(result.some((c) => c.command.includes("generate"))).toBe(true); - expect(result.some((c) => c.command.includes("drizzle-kit push"))).toBe( - true, - ); - }); -}); diff --git a/packages/mcp/tests/lib/templates/features.test.ts b/packages/mcp/tests/lib/templates/features.test.ts deleted file mode 100644 index 55b885c24f..0000000000 --- a/packages/mcp/tests/lib/templates/features.test.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - categorizeFeatures, - generatePluginImports, - generatePluginSetup, - getPluginEnvVars, - getSocialProviderEnvVars, -} from "../../../src/lib/templates/features"; - -describe("categorizeFeatures", () => { - it("should categorize email-password", () => { - const result = categorizeFeatures(["email-password"]); - - expect(result.hasEmailPassword).toBe(true); - expect(result.socialProviders).toEqual([]); - expect(result.plugins).toEqual([]); - }); - - it("should categorize social providers", () => { - const result = categorizeFeatures(["google", "github", "discord"]); - - expect(result.socialProviders).toEqual(["google", "github", "discord"]); - expect(result.plugins).toEqual([]); - }); - - it("should categorize plugins", () => { - const result = categorizeFeatures(["2fa", "organization", "passkey"]); - - expect(result.plugins).toEqual(["2fa", "organization", "passkey"]); - expect(result.socialProviders).toEqual([]); - }); - - it("should categorize mixed features", () => { - const result = categorizeFeatures([ - "email-password", - "google", - "github", - "2fa", - "organization", - ]); - - expect(result.hasEmailPassword).toBe(true); - expect(result.socialProviders).toEqual(["google", "github"]); - expect(result.plugins).toEqual(["2fa", "organization"]); - }); - - it("should handle empty features", () => { - const result = categorizeFeatures([]); - - expect(result.hasEmailPassword).toBe(false); - expect(result.socialProviders).toEqual([]); - expect(result.plugins).toEqual([]); - }); -}); - -describe("getSocialProviderEnvVars", () => { - it("should return client ID and secret env vars", () => { - const result = getSocialProviderEnvVars("google"); - - expect(result).toHaveLength(2); - expect(result.some((e) => e.name === "GOOGLE_CLIENT_ID")).toBe(true); - expect(result.some((e) => e.name === "GOOGLE_CLIENT_SECRET")).toBe(true); - expect(result.every((e) => e.required === true)).toBe(true); - }); - - it("should uppercase provider name for env vars", () => { - const result = getSocialProviderEnvVars("github"); - - expect(result.some((e) => e.name === "GITHUB_CLIENT_ID")).toBe(true); - expect(result.some((e) => e.name === "GITHUB_CLIENT_SECRET")).toBe(true); - }); -}); - -describe("generatePluginImports", () => { - it("should generate server and client imports for plugins with both", () => { - const result = generatePluginImports(["2fa"]); - - expect(result.serverImports).toHaveLength(1); - expect(result.clientImports).toHaveLength(1); - }); - - it("should generate server-only imports for plugins without client", () => { - const result = generatePluginImports(["bearer"]); - - expect(result.serverImports).toHaveLength(1); - expect(result.clientImports).toEqual([]); - }); - - it("should generate imports for multiple plugins", () => { - const result = generatePluginImports(["2fa", "organization", "admin"]); - - expect(result.serverImports).toHaveLength(3); - expect(result.clientImports).toHaveLength(3); - }); - - it("should return empty arrays for unknown plugins", () => { - const result = generatePluginImports(["unknown-plugin"]); - - expect(result.serverImports).toEqual([]); - expect(result.clientImports).toEqual([]); - }); -}); - -describe("generatePluginSetup", () => { - it("should generate server and client plugin calls", () => { - const result = generatePluginSetup(["2fa"]); - - expect(result.serverPlugins).toHaveLength(1); - expect(result.clientPlugins).toHaveLength(1); - }); - - it("should generate plugins with configuration for magic-link", () => { - const result = generatePluginSetup(["magic-link"]); - - expect(result.serverPlugins[0]).toContain("sendMagicLink"); - }); - - it("should generate plugins with configuration for phone-number", () => { - const result = generatePluginSetup(["phone-number"]); - - expect(result.serverPlugins[0]).toContain("sendOTP"); - }); - - it("should generate plugins with configuration for captcha", () => { - const result = generatePluginSetup(["captcha"]); - - expect(result.serverPlugins[0]).toContain("provider"); - expect(result.serverPlugins[0]).toContain("secretKey"); - }); -}); - -describe("getPluginEnvVars", () => { - it("should return env vars for plugins that need them", () => { - const result = getPluginEnvVars(["captcha"]); - - expect(result.some((e) => e.name === "CAPTCHA_SECRET_KEY")).toBe(true); - }); - - it("should return empty for plugins without env vars", () => { - const result = getPluginEnvVars(["2fa", "organization"]); - - expect(result).toEqual([]); - }); - - it("should aggregate env vars from multiple plugins", () => { - const result = getPluginEnvVars(["captcha", "2fa"]); - - expect(result).toHaveLength(1); - }); -}); diff --git a/packages/mcp/tests/lib/templates/frameworks.test.ts b/packages/mcp/tests/lib/templates/frameworks.test.ts deleted file mode 100644 index a48e5c6341..0000000000 --- a/packages/mcp/tests/lib/templates/frameworks.test.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { describe, expect, it } from "vitest"; -import { - FRAMEWORK_CONFIGS, - getDefaultApiPath, - getDefaultAuthPath, -} from "../../../src/lib/templates/frameworks"; - -describe("FRAMEWORK_CONFIGS", () => { - it("should have config for all supported frameworks", () => { - const frameworks = [ - "next-app-router", - "next-pages-router", - "sveltekit", - "astro", - "remix", - "nuxt", - "solid-start", - "hono", - "express", - "fastify", - "elysia", - "tanstack-start", - "expo", - ]; - - for (const framework of frameworks) { - expect( - FRAMEWORK_CONFIGS[framework as keyof typeof FRAMEWORK_CONFIGS], - ).toBeDefined(); - } - }); -}); - -describe("getDefaultAuthPath", () => { - it("should return base path when srcDir is false", () => { - const result = getDefaultAuthPath("next-app-router", false); - expect(result).toBe("lib/auth"); - }); - - it("should return src-prefixed path when srcDir is true", () => { - const result = getDefaultAuthPath("next-app-router", true); - expect(result).toBe("src/lib/auth"); - }); - - it("should work for different frameworks", () => { - expect(getDefaultAuthPath("sveltekit", false)).toBe("lib/auth"); - expect(getDefaultAuthPath("sveltekit", true)).toBe("src/lib/auth"); - expect(getDefaultAuthPath("remix", true)).toBe("src/lib/auth"); - }); -}); - -describe("getDefaultApiPath", () => { - it("should return base path when srcDir is false", () => { - const result = getDefaultApiPath("next-app-router", false); - expect(result).toBe("app/api/auth/[...all]"); - }); - - it("should return src-prefixed path when srcDir is true", () => { - const result = getDefaultApiPath("next-app-router", true); - expect(result).toBe("src/app/api/auth/[...all]"); - }); - - it("should return empty string for frameworks without API path", () => { - expect(getDefaultApiPath("hono", false)).toBe(""); - expect(getDefaultApiPath("express", true)).toBe(""); - expect(getDefaultApiPath("expo", false)).toBe(""); - }); - - it("should handle framework-specific paths", () => { - expect(getDefaultApiPath("sveltekit", true)).toBe( - "src/routes/api/auth/[...all]", - ); - expect(getDefaultApiPath("nuxt", false)).toBe("server/api/auth/[...all]"); - expect(getDefaultApiPath("remix", true)).toBe("src/routes/api.auth.$"); - }); -}); diff --git a/packages/mcp/tsconfig.json b/packages/mcp/tsconfig.json deleted file mode 100644 index 3348242a72..0000000000 --- a/packages/mcp/tsconfig.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "extends": "../../tsconfig.base.json", - "compilerOptions": { - "lib": ["esnext"] - } -} diff --git a/packages/mcp/tsdown.config.ts b/packages/mcp/tsdown.config.ts deleted file mode 100644 index 3ef649ff00..0000000000 --- a/packages/mcp/tsdown.config.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { readFile } from "node:fs/promises"; -import { defineConfig } from "tsdown"; - -const packageJson = JSON.parse( - await readFile(new URL("./package.json", import.meta.url), "utf-8"), -); - -export default defineConfig({ - dts: { build: true }, - format: ["esm"], - entry: ["./src/index.ts"], - sourcemap: "inline", - env: { - BETTER_AUTH_VERSION: packageJson.version, - }, -});