@better-auth/mcp 1.4.13

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE.md ADDED
@@ -0,0 +1,17 @@
1
+ The MIT License (MIT)
2
+ Copyright (c) 2024 - present, Bereket Engida
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software
5
+ and associated documentation files (the "Software"), to deal in the Software without restriction,
6
+ including without limitation the rights to use, copy, modify, merge, publish, distribute,
7
+ sublicense, and/or sell copies of the Software, and to permit persons to whom the Software
8
+ is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or
11
+ substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
14
+ BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
15
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
16
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
17
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ export { };
package/dist/index.mjs ADDED
@@ -0,0 +1,1029 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
+ import * as z from "zod";
5
+
6
+ //#region src/lib/parser.ts
7
+ function parseExistingSetup(existingSetup) {
8
+ const detected = { features: [] };
9
+ if (existingSetup.authConfig) {
10
+ const config = existingSetup.authConfig;
11
+ detected.database = detectDatabase(config);
12
+ detected.orm = detectORM(config);
13
+ detected.features = detectFeatures(config, existingSetup.authClientConfig);
14
+ }
15
+ return detected;
16
+ }
17
+ function detectDatabase(config) {
18
+ if (/provider:\s*["']postgresql["']/.test(config) || /provider:\s*["']pg["']/.test(config)) return "postgres";
19
+ if (/provider:\s*["']mysql["']/.test(config)) return "mysql";
20
+ if (/provider:\s*["']sqlite["']/.test(config)) return "sqlite";
21
+ if (/provider:\s*["']mongodb["']/.test(config)) return "mongodb";
22
+ }
23
+ function detectORM(config) {
24
+ if (/prismaAdapter\(/.test(config)) return "prisma";
25
+ if (/drizzleAdapter\(/.test(config)) return "drizzle";
26
+ if (/database:\s*\{[\s\S]*provider:/.test(config)) return "none";
27
+ }
28
+ function detectFeatures(serverConfig, clientConfig) {
29
+ const features = [];
30
+ if (/emailAndPassword:\s*\{[\s\S]*enabled:\s*true/.test(serverConfig)) features.push("email-password");
31
+ for (const provider of [
32
+ "google",
33
+ "github",
34
+ "apple",
35
+ "discord",
36
+ "twitter",
37
+ "facebook",
38
+ "microsoft",
39
+ "linkedin"
40
+ ]) if ((/* @__PURE__ */ new RegExp(`${provider}:\\s*\\{`)).test(serverConfig)) features.push(provider);
41
+ for (const [pattern, feature] of [
42
+ [/twoFactor\(/, "2fa"],
43
+ [/organization\(/, "organization"],
44
+ [/admin\(/, "admin"],
45
+ [/username\(/, "username"],
46
+ [/multiSession\(/, "multi-session"],
47
+ [/apiKey\(/, "api-key"],
48
+ [/bearer\(/, "bearer"],
49
+ [/jwt\(/, "jwt"],
50
+ [/magicLink\(/, "magic-link"],
51
+ [/phoneNumber\(/, "phone-number"],
52
+ [/passkey\(/, "passkey"],
53
+ [/anonymous\(/, "anonymous"],
54
+ [/captcha\(/, "captcha"]
55
+ ]) if (pattern.test(serverConfig)) features.push(feature);
56
+ return features;
57
+ }
58
+ function computeFeatureDiff(existing, requested) {
59
+ return {
60
+ toAdd: requested.filter((f) => !existing.includes(f)),
61
+ existing: requested.filter((f) => existing.includes(f))
62
+ };
63
+ }
64
+
65
+ //#endregion
66
+ //#region src/lib/templates/database.ts
67
+ const DATABASE_CONFIGS = {
68
+ postgres: {
69
+ provider: "postgresql",
70
+ envVarName: "DATABASE_URL",
71
+ connectionStringExample: "postgresql://user:password@localhost:5432/mydb"
72
+ },
73
+ mysql: {
74
+ provider: "mysql",
75
+ envVarName: "DATABASE_URL",
76
+ connectionStringExample: "mysql://user:password@localhost:3306/mydb"
77
+ },
78
+ sqlite: {
79
+ provider: "sqlite",
80
+ envVarName: "DATABASE_URL",
81
+ connectionStringExample: "file:./dev.db"
82
+ },
83
+ mongodb: {
84
+ provider: "mongodb",
85
+ envVarName: "DATABASE_URL",
86
+ connectionStringExample: "mongodb://localhost:27017/mydb"
87
+ }
88
+ };
89
+ const ORM_CONFIGS = {
90
+ prisma: {
91
+ adapterImport: `import { prismaAdapter } from "better-auth/adapters/prisma";
92
+ import { PrismaClient } from "@prisma/client";`,
93
+ adapterSetup: (dbProvider) => `prismaAdapter(prisma, {
94
+ provider: "${dbProvider}",
95
+ })`,
96
+ schemaCommand: "npx @better-auth/cli generate --output prisma/schema.prisma",
97
+ pushCommand: "npx prisma db push"
98
+ },
99
+ drizzle: {
100
+ adapterImport: `import { drizzleAdapter } from "better-auth/adapters/drizzle";
101
+ import { db } from "./db";`,
102
+ adapterSetup: (dbProvider) => `drizzleAdapter(db, {
103
+ provider: "${getShortProvider(dbProvider)}",
104
+ })`,
105
+ schemaCommand: "npx @better-auth/cli generate --output src/lib/schema.ts",
106
+ pushCommand: "npx drizzle-kit push"
107
+ },
108
+ none: {
109
+ adapterImport: "",
110
+ adapterSetup: (dbProvider) => `{
111
+ provider: "${getShortProvider(dbProvider)}",
112
+ url: process.env.DATABASE_URL,
113
+ }`,
114
+ pushCommand: "npx @better-auth/cli migrate"
115
+ }
116
+ };
117
+ function getShortProvider(provider) {
118
+ switch (provider) {
119
+ case "postgresql": return "pg";
120
+ case "mysql": return "mysql";
121
+ case "sqlite": return "sqlite";
122
+ case "mongodb": return "mongodb";
123
+ default: return provider;
124
+ }
125
+ }
126
+ function generateDatabaseConfig(database, orm) {
127
+ 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.");
128
+ const dbConfig = DATABASE_CONFIGS[database];
129
+ const ormConfig = ORM_CONFIGS[orm];
130
+ let imports = "";
131
+ let prismaInstance = "";
132
+ if (orm === "prisma") {
133
+ imports = ormConfig.adapterImport;
134
+ prismaInstance = "\nconst prisma = new PrismaClient();\n";
135
+ } else if (orm === "drizzle") imports = ormConfig.adapterImport;
136
+ const config = ormConfig.adapterSetup(dbConfig.provider);
137
+ return {
138
+ imports,
139
+ config,
140
+ prismaInstance
141
+ };
142
+ }
143
+ function getDatabaseEnvVar(database) {
144
+ const config = DATABASE_CONFIGS[database];
145
+ return {
146
+ name: config.envVarName,
147
+ description: `${database.charAt(0).toUpperCase() + database.slice(1)} connection string`,
148
+ required: true,
149
+ example: config.connectionStringExample
150
+ };
151
+ }
152
+ function getDatabaseCommands(orm) {
153
+ const ormConfig = ORM_CONFIGS[orm];
154
+ const commands = [];
155
+ if (ormConfig.schemaCommand) commands.push({
156
+ command: ormConfig.schemaCommand,
157
+ description: `Generate ${orm === "prisma" ? "Prisma" : "Drizzle"} schema for auth tables`
158
+ });
159
+ commands.push({
160
+ command: ormConfig.pushCommand,
161
+ description: orm === "none" ? "Run database migrations for auth tables" : `Push ${orm === "prisma" ? "Prisma" : "Drizzle"} schema to database`,
162
+ when: "After setting DATABASE_URL"
163
+ });
164
+ return commands;
165
+ }
166
+
167
+ //#endregion
168
+ //#region src/lib/templates/features.ts
169
+ function generateSocialProviderConfig(provider) {
170
+ const upperName = provider.toUpperCase();
171
+ return `${provider}: {
172
+ clientId: process.env.${upperName}_CLIENT_ID!,
173
+ clientSecret: process.env.${upperName}_CLIENT_SECRET!,
174
+ }`;
175
+ }
176
+ function getSocialProviderEnvVars(provider) {
177
+ const upperName = provider.toUpperCase();
178
+ const providerName = provider.charAt(0).toUpperCase() + provider.slice(1);
179
+ return [{
180
+ name: `${upperName}_CLIENT_ID`,
181
+ description: `${providerName} OAuth client ID`,
182
+ required: true,
183
+ howToGet: `Create OAuth app at ${providerName} developer console`
184
+ }, {
185
+ name: `${upperName}_CLIENT_SECRET`,
186
+ description: `${providerName} OAuth client secret`,
187
+ required: true,
188
+ howToGet: `Create OAuth app at ${providerName} developer console`
189
+ }];
190
+ }
191
+ const PLUGIN_CONFIGS = {
192
+ "2fa": {
193
+ serverImport: "import { twoFactor } from \"better-auth/plugins\";",
194
+ clientImport: "import { twoFactorClient } from \"better-auth/client/plugins\";",
195
+ serverPlugin: () => `twoFactor()`,
196
+ clientPlugin: () => `twoFactorClient()`
197
+ },
198
+ organization: {
199
+ serverImport: "import { organization } from \"better-auth/plugins\";",
200
+ clientImport: "import { organizationClient } from \"better-auth/client/plugins\";",
201
+ serverPlugin: () => `organization()`,
202
+ clientPlugin: () => `organizationClient()`
203
+ },
204
+ admin: {
205
+ serverImport: "import { admin } from \"better-auth/plugins\";",
206
+ clientImport: "import { adminClient } from \"better-auth/client/plugins\";",
207
+ serverPlugin: () => `admin()`,
208
+ clientPlugin: () => `adminClient()`
209
+ },
210
+ username: {
211
+ serverImport: "import { username } from \"better-auth/plugins\";",
212
+ clientImport: "import { usernameClient } from \"better-auth/client/plugins\";",
213
+ serverPlugin: () => `username()`,
214
+ clientPlugin: () => `usernameClient()`
215
+ },
216
+ "multi-session": {
217
+ serverImport: "import { multiSession } from \"better-auth/plugins\";",
218
+ clientImport: "import { multiSessionClient } from \"better-auth/client/plugins\";",
219
+ serverPlugin: () => `multiSession()`,
220
+ clientPlugin: () => `multiSessionClient()`
221
+ },
222
+ "api-key": {
223
+ serverImport: "import { apiKey } from \"better-auth/plugins\";",
224
+ clientImport: "import { apiKeyClient } from \"better-auth/client/plugins\";",
225
+ serverPlugin: () => `apiKey()`,
226
+ clientPlugin: () => `apiKeyClient()`
227
+ },
228
+ bearer: {
229
+ serverImport: "import { bearer } from \"better-auth/plugins\";",
230
+ serverPlugin: () => `bearer()`
231
+ },
232
+ jwt: {
233
+ serverImport: "import { jwt } from \"better-auth/plugins\";",
234
+ serverPlugin: () => `jwt()`
235
+ },
236
+ "magic-link": {
237
+ serverImport: "import { magicLink } from \"better-auth/plugins\";",
238
+ clientImport: "import { magicLinkClient } from \"better-auth/client/plugins\";",
239
+ serverPlugin: () => `magicLink({
240
+ sendMagicLink: async ({ email, url }) => {
241
+ // TODO: Send magic link email
242
+ console.log("Send magic link to", email, url);
243
+ },
244
+ })`,
245
+ clientPlugin: () => `magicLinkClient()`
246
+ },
247
+ "phone-number": {
248
+ serverImport: "import { phoneNumber } from \"better-auth/plugins\";",
249
+ clientImport: "import { phoneNumberClient } from \"better-auth/client/plugins\";",
250
+ serverPlugin: () => `phoneNumber({
251
+ sendOTP: async ({ phoneNumber, code }) => {
252
+ // TODO: Send OTP via SMS
253
+ console.log("Send OTP", code, "to", phoneNumber);
254
+ },
255
+ })`,
256
+ clientPlugin: () => `phoneNumberClient()`
257
+ },
258
+ passkey: {
259
+ serverImport: "import { passkey } from \"better-auth/plugins\";",
260
+ clientImport: "import { passkeyClient } from \"better-auth/client/plugins\";",
261
+ serverPlugin: () => `passkey()`,
262
+ clientPlugin: () => `passkeyClient()`
263
+ },
264
+ anonymous: {
265
+ serverImport: "import { anonymous } from \"better-auth/plugins\";",
266
+ clientImport: "import { anonymousClient } from \"better-auth/client/plugins\";",
267
+ serverPlugin: () => `anonymous()`,
268
+ clientPlugin: () => `anonymousClient()`
269
+ },
270
+ captcha: {
271
+ serverImport: "import { captcha } from \"better-auth/plugins\";",
272
+ serverPlugin: () => `captcha({
273
+ provider: "cloudflare-turnstile", // or "recaptcha" or "hcaptcha"
274
+ secretKey: process.env.CAPTCHA_SECRET_KEY!,
275
+ })`,
276
+ envVars: [{
277
+ name: "CAPTCHA_SECRET_KEY",
278
+ description: "Captcha provider secret key",
279
+ required: true
280
+ }]
281
+ }
282
+ };
283
+ function generatePluginImports(plugins) {
284
+ const serverImports = [];
285
+ const clientImports = [];
286
+ for (const plugin of plugins) {
287
+ const config = PLUGIN_CONFIGS[plugin];
288
+ if (config) {
289
+ serverImports.push(config.serverImport);
290
+ if (config.clientImport) clientImports.push(config.clientImport);
291
+ }
292
+ }
293
+ return {
294
+ serverImports,
295
+ clientImports
296
+ };
297
+ }
298
+ function generatePluginSetup(plugins) {
299
+ const serverPlugins = [];
300
+ const clientPlugins = [];
301
+ for (const plugin of plugins) {
302
+ const config = PLUGIN_CONFIGS[plugin];
303
+ if (config) {
304
+ serverPlugins.push(config.serverPlugin());
305
+ if (config.clientPlugin) clientPlugins.push(config.clientPlugin());
306
+ }
307
+ }
308
+ return {
309
+ serverPlugins,
310
+ clientPlugins
311
+ };
312
+ }
313
+ function getPluginEnvVars(plugins) {
314
+ const envVars = [];
315
+ for (const plugin of plugins) {
316
+ const config = PLUGIN_CONFIGS[plugin];
317
+ if (config?.envVars) envVars.push(...config.envVars);
318
+ }
319
+ return envVars;
320
+ }
321
+ function categorizeFeatures(features) {
322
+ const socialProviders = [];
323
+ const plugins = [];
324
+ let hasEmailPassword = false;
325
+ for (const feature of features) if (feature === "email-password") hasEmailPassword = true;
326
+ else if (feature in PLUGIN_CONFIGS) plugins.push(feature);
327
+ else socialProviders.push(feature);
328
+ return {
329
+ socialProviders,
330
+ plugins,
331
+ hasEmailPassword
332
+ };
333
+ }
334
+
335
+ //#endregion
336
+ //#region src/lib/templates/frameworks.ts
337
+ const FRAMEWORK_CONFIGS = {
338
+ "next-app-router": {
339
+ name: "Next.js (App Router)",
340
+ defaultSrcDir: false,
341
+ defaultAuthPath: "lib/auth",
342
+ defaultApiPath: "app/api/auth/[...all]",
343
+ clientImport: "import { createAuthClient } from \"better-auth/react\";",
344
+ handlerImport: "import { toNextJsHandler } from \"better-auth/next-js\";",
345
+ handlerFunction: "toNextJsHandler",
346
+ apiRouteTemplate: (authPath) => `import { auth } from "@/${authPath}";
347
+ import { toNextJsHandler } from "better-auth/next-js";
348
+
349
+ export const { GET, POST } = toNextJsHandler(auth);`,
350
+ defaultPort: 3e3
351
+ },
352
+ "next-pages-router": {
353
+ name: "Next.js (Pages Router)",
354
+ defaultSrcDir: false,
355
+ defaultAuthPath: "lib/auth",
356
+ defaultApiPath: "pages/api/auth/[...all]",
357
+ clientImport: "import { createAuthClient } from \"better-auth/react\";",
358
+ handlerImport: "import { toNodeHandler } from \"better-auth/node\";",
359
+ handlerFunction: "toNodeHandler",
360
+ apiRouteTemplate: (authPath) => `import { toNodeHandler } from "better-auth/node";
361
+ import { auth } from "@/${authPath}";
362
+
363
+ export const config = { api: { bodyParser: false } };
364
+ export default toNodeHandler(auth);`,
365
+ defaultPort: 3e3
366
+ },
367
+ sveltekit: {
368
+ name: "SvelteKit",
369
+ defaultSrcDir: true,
370
+ defaultAuthPath: "lib/auth",
371
+ defaultApiPath: "routes/api/auth/[...all]",
372
+ clientImport: "import { createAuthClient } from \"better-auth/svelte\";",
373
+ handlerImport: "import { svelteKitHandler } from \"better-auth/svelte-kit\";",
374
+ handlerFunction: "svelteKitHandler",
375
+ apiRouteTemplate: () => "",
376
+ hooksTemplate: (authPath) => `import { auth } from "$${authPath}";
377
+ import { svelteKitHandler } from "better-auth/svelte-kit";
378
+ import type { Handle } from "@sveltejs/kit";
379
+
380
+ export const handle: Handle = async ({ event, resolve }) => {
381
+ return svelteKitHandler({ event, resolve, auth });
382
+ };`,
383
+ defaultPort: 5173
384
+ },
385
+ astro: {
386
+ name: "Astro",
387
+ defaultSrcDir: true,
388
+ defaultAuthPath: "lib/auth",
389
+ defaultApiPath: "pages/api/auth/[...all]",
390
+ clientImport: "import { createAuthClient } from \"better-auth/client\";",
391
+ handlerImport: "",
392
+ handlerFunction: "",
393
+ apiRouteTemplate: (authPath) => `import type { APIRoute } from "astro";
394
+ import { auth } from "@/${authPath}";
395
+
396
+ export const GET: APIRoute = async (ctx) => {
397
+ return auth.handler(ctx.request);
398
+ };
399
+
400
+ export const POST: APIRoute = async (ctx) => {
401
+ return auth.handler(ctx.request);
402
+ };`,
403
+ defaultPort: 4321
404
+ },
405
+ remix: {
406
+ name: "Remix",
407
+ defaultSrcDir: true,
408
+ defaultAuthPath: "lib/auth",
409
+ defaultApiPath: "routes/api.auth.$",
410
+ clientImport: "import { createAuthClient } from \"better-auth/react\";",
411
+ handlerImport: "",
412
+ handlerFunction: "",
413
+ apiRouteTemplate: (authPath) => `import { auth } from "~/${authPath}";
414
+ import type { ActionFunctionArgs, LoaderFunctionArgs } from "@remix-run/node";
415
+
416
+ export async function loader({ request }: LoaderFunctionArgs) {
417
+ return auth.handler(request);
418
+ }
419
+
420
+ export async function action({ request }: ActionFunctionArgs) {
421
+ return auth.handler(request);
422
+ }`,
423
+ defaultPort: 3e3
424
+ },
425
+ nuxt: {
426
+ name: "Nuxt 3",
427
+ defaultSrcDir: false,
428
+ defaultAuthPath: "lib/auth",
429
+ defaultApiPath: "server/api/auth/[...all]",
430
+ clientImport: "import { createAuthClient } from \"better-auth/vue\";",
431
+ handlerImport: "",
432
+ handlerFunction: "",
433
+ apiRouteTemplate: (authPath) => `import { auth } from "~/${authPath}";
434
+
435
+ export default defineEventHandler((event) => {
436
+ return auth.handler(toWebRequest(event));
437
+ });`,
438
+ defaultPort: 3e3
439
+ },
440
+ "solid-start": {
441
+ name: "SolidStart",
442
+ defaultSrcDir: true,
443
+ defaultAuthPath: "lib/auth",
444
+ defaultApiPath: "routes/api/auth/[...all]",
445
+ clientImport: "import { createAuthClient } from \"better-auth/solid\";",
446
+ handlerImport: "",
447
+ handlerFunction: "",
448
+ apiRouteTemplate: (authPath) => `import { auth } from "~/${authPath}";
449
+ import type { APIEvent } from "@solidjs/start/server";
450
+
451
+ export async function GET(event: APIEvent) {
452
+ return auth.handler(event.request);
453
+ }
454
+
455
+ export async function POST(event: APIEvent) {
456
+ return auth.handler(event.request);
457
+ }`,
458
+ defaultPort: 3e3
459
+ },
460
+ hono: {
461
+ name: "Hono",
462
+ defaultSrcDir: true,
463
+ defaultAuthPath: "lib/auth",
464
+ defaultApiPath: "",
465
+ clientImport: "import { createAuthClient } from \"better-auth/client\";",
466
+ handlerImport: "",
467
+ handlerFunction: "",
468
+ apiRouteTemplate: (authPath) => `import { Hono } from "hono";
469
+ import { auth } from "./${authPath}";
470
+
471
+ const app = new Hono();
472
+
473
+ app.on(["POST", "GET"], "/api/auth/**", (c) => {
474
+ return auth.handler(c.req.raw);
475
+ });
476
+
477
+ export default app;`,
478
+ defaultPort: 3e3
479
+ },
480
+ express: {
481
+ name: "Express.js",
482
+ defaultSrcDir: true,
483
+ defaultAuthPath: "lib/auth",
484
+ defaultApiPath: "",
485
+ clientImport: "import { createAuthClient } from \"better-auth/client\";",
486
+ handlerImport: "import { toNodeHandler } from \"better-auth/node\";",
487
+ handlerFunction: "toNodeHandler",
488
+ apiRouteTemplate: (authPath) => `import express from "express";
489
+ import { toNodeHandler } from "better-auth/node";
490
+ import { auth } from "./${authPath}";
491
+
492
+ const app = express();
493
+
494
+ app.all("/api/auth/*", toNodeHandler(auth));
495
+
496
+ app.listen(3000, () => {
497
+ console.log("Server running on port 3000");
498
+ });`,
499
+ defaultPort: 3e3
500
+ },
501
+ fastify: {
502
+ name: "Fastify",
503
+ defaultSrcDir: true,
504
+ defaultAuthPath: "lib/auth",
505
+ defaultApiPath: "",
506
+ clientImport: "import { createAuthClient } from \"better-auth/client\";",
507
+ handlerImport: "import { toNodeHandler } from \"better-auth/node\";",
508
+ handlerFunction: "toNodeHandler",
509
+ apiRouteTemplate: (authPath) => `import Fastify from "fastify";
510
+ import { toNodeHandler } from "better-auth/node";
511
+ import { auth } from "./${authPath}";
512
+
513
+ const fastify = Fastify();
514
+
515
+ fastify.all("/api/auth/*", async (request, reply) => {
516
+ const handler = toNodeHandler(auth);
517
+ return handler(request.raw, reply.raw);
518
+ });
519
+
520
+ fastify.listen({ port: 3000 }, (err) => {
521
+ if (err) throw err;
522
+ console.log("Server running on port 3000");
523
+ });`,
524
+ defaultPort: 3e3
525
+ },
526
+ elysia: {
527
+ name: "Elysia (Bun)",
528
+ defaultSrcDir: true,
529
+ defaultAuthPath: "lib/auth",
530
+ defaultApiPath: "",
531
+ clientImport: "import { createAuthClient } from \"better-auth/client\";",
532
+ handlerImport: "",
533
+ handlerFunction: "",
534
+ apiRouteTemplate: (authPath) => `import { Elysia } from "elysia";
535
+ import { auth } from "./${authPath}";
536
+
537
+ const app = new Elysia()
538
+ .all("/api/auth/*", ({ request }) => auth.handler(request))
539
+ .listen(3000);
540
+
541
+ console.log("Server running on port 3000");`,
542
+ defaultPort: 3e3
543
+ },
544
+ "tanstack-start": {
545
+ name: "TanStack Start",
546
+ defaultSrcDir: true,
547
+ defaultAuthPath: "lib/auth",
548
+ defaultApiPath: "routes/api/auth.$",
549
+ clientImport: "import { createAuthClient } from \"better-auth/react\";",
550
+ handlerImport: "",
551
+ handlerFunction: "",
552
+ apiRouteTemplate: (authPath) => `import { auth } from "~/${authPath}";
553
+ import { createAPIFileRoute } from "@tanstack/start/api";
554
+
555
+ export const Route = createAPIFileRoute("/api/auth/$")({
556
+ GET: ({ request }) => auth.handler(request),
557
+ POST: ({ request }) => auth.handler(request),
558
+ });`,
559
+ defaultPort: 3e3
560
+ },
561
+ expo: {
562
+ name: "Expo (React Native)",
563
+ defaultSrcDir: true,
564
+ defaultAuthPath: "lib/auth",
565
+ defaultApiPath: "",
566
+ clientImport: "import { createAuthClient } from \"@better-auth/expo\";",
567
+ handlerImport: "",
568
+ handlerFunction: "",
569
+ apiRouteTemplate: () => "",
570
+ defaultPort: 8081
571
+ }
572
+ };
573
+ function getDefaultAuthPath(framework, srcDir) {
574
+ const basePath = FRAMEWORK_CONFIGS[framework].defaultAuthPath;
575
+ return srcDir ? `src/${basePath}` : basePath;
576
+ }
577
+ function getDefaultApiPath(framework, srcDir) {
578
+ const basePath = FRAMEWORK_CONFIGS[framework].defaultApiPath;
579
+ if (!basePath) return "";
580
+ return srcDir ? `src/${basePath}` : basePath;
581
+ }
582
+
583
+ //#endregion
584
+ //#region src/lib/generator.ts
585
+ function generateSetup(input) {
586
+ const framework = input.framework;
587
+ const database = input.database;
588
+ const orm = input.orm || "none";
589
+ const features = input.features || ["email-password"];
590
+ const typescript = input.typescript ?? true;
591
+ const srcDir = input.srcDir ?? FRAMEWORK_CONFIGS[framework].defaultSrcDir;
592
+ const authPath = input.authPath || getDefaultAuthPath(framework, srcDir);
593
+ const apiPath = input.apiPath || getDefaultApiPath(framework, srcDir);
594
+ let mode = "create";
595
+ let featuresToGenerate = features;
596
+ let detected;
597
+ if (input.existingSetup?.authConfig) {
598
+ mode = "update";
599
+ detected = parseExistingSetup(input.existingSetup);
600
+ featuresToGenerate = computeFeatureDiff(detected.features, features).toAdd;
601
+ if (featuresToGenerate.length === 0) return {
602
+ mode: "update",
603
+ files: [],
604
+ envVars: [],
605
+ commands: [],
606
+ detected,
607
+ nextSteps: ["All requested features are already configured"],
608
+ docs: []
609
+ };
610
+ }
611
+ const { socialProviders, plugins, hasEmailPassword } = categorizeFeatures(featuresToGenerate);
612
+ const files = [];
613
+ const envVars = [];
614
+ const commands = [];
615
+ const warnings = [];
616
+ envVars.push({
617
+ name: "BETTER_AUTH_SECRET",
618
+ description: "Secret key for signing tokens",
619
+ required: true,
620
+ howToGet: "Run: npx @better-auth/cli secret"
621
+ });
622
+ envVars.push(getDatabaseEnvVar(database));
623
+ for (const provider of socialProviders) envVars.push(...getSocialProviderEnvVars(provider));
624
+ envVars.push(...getPluginEnvVars(plugins));
625
+ if (mode === "create") {
626
+ const authFile = generateAuthFile({
627
+ framework,
628
+ database,
629
+ orm,
630
+ socialProviders,
631
+ plugins,
632
+ hasEmailPassword,
633
+ typescript
634
+ });
635
+ files.push({
636
+ path: `${authPath}.${typescript ? "ts" : "js"}`,
637
+ description: "Better Auth server configuration",
638
+ action: "create",
639
+ content: authFile
640
+ });
641
+ const clientFile = generateClientFile({
642
+ framework,
643
+ plugins,
644
+ typescript
645
+ });
646
+ files.push({
647
+ path: `${authPath}-client.${typescript ? "ts" : "js"}`,
648
+ description: "Better Auth client configuration",
649
+ action: "create",
650
+ content: clientFile
651
+ });
652
+ if (apiPath) {
653
+ const apiFile = generateApiRouteFile(framework, authPath);
654
+ if (apiFile) files.push({
655
+ path: `${apiPath}/route.${typescript ? "ts" : "js"}`,
656
+ description: "API route handler for auth endpoints",
657
+ action: "create",
658
+ content: apiFile
659
+ });
660
+ }
661
+ const frameworkConfig = FRAMEWORK_CONFIGS[framework];
662
+ if (frameworkConfig.hooksTemplate) files.push({
663
+ path: srcDir ? "src/hooks.server.ts" : "hooks.server.ts",
664
+ description: "SvelteKit hooks for auth handling",
665
+ action: "create",
666
+ content: frameworkConfig.hooksTemplate(authPath)
667
+ });
668
+ } else if (socialProviders.length > 0 || plugins.length > 0 || hasEmailPassword) {
669
+ const changes = generateUpdateChanges({
670
+ socialProviders,
671
+ plugins,
672
+ hasEmailPassword
673
+ });
674
+ files.push({
675
+ path: `${authPath}.${typescript ? "ts" : "js"}`,
676
+ description: "Better Auth server configuration",
677
+ action: "update",
678
+ changes: changes.serverChanges
679
+ });
680
+ if (plugins.length > 0) files.push({
681
+ path: `${authPath}-client.${typescript ? "ts" : "js"}`,
682
+ description: "Better Auth client configuration",
683
+ action: "update",
684
+ changes: changes.clientChanges
685
+ });
686
+ }
687
+ commands.push({
688
+ command: "pnpm add better-auth",
689
+ description: "Install Better Auth",
690
+ when: "If not already installed"
691
+ });
692
+ commands.push(...getDatabaseCommands(orm));
693
+ const nextSteps = generateNextSteps(mode, socialProviders, plugins);
694
+ const docs = generateDocLinks(features);
695
+ return {
696
+ mode,
697
+ files,
698
+ envVars,
699
+ commands,
700
+ detected,
701
+ nextSteps,
702
+ warnings: warnings.length > 0 ? warnings : void 0,
703
+ docs
704
+ };
705
+ }
706
+ function generateAuthFile(options) {
707
+ const { database, orm, socialProviders, plugins, hasEmailPassword } = options;
708
+ const imports = ["import { betterAuth } from \"better-auth\";"];
709
+ const { imports: dbImports, config: dbConfig, prismaInstance } = generateDatabaseConfig(database, orm);
710
+ if (dbImports) imports.push(dbImports);
711
+ const { serverImports } = generatePluginImports(plugins);
712
+ imports.push(...serverImports);
713
+ const socialProvidersConfig = socialProviders.map((p) => ` ${generateSocialProviderConfig(p)}`).join(",\n");
714
+ const { serverPlugins } = generatePluginSetup(plugins);
715
+ let configBody = ` database: ${dbConfig},`;
716
+ if (hasEmailPassword) configBody += `
717
+ emailAndPassword: {
718
+ enabled: true,
719
+ },`;
720
+ if (socialProviders.length > 0) configBody += `
721
+ socialProviders: {
722
+ ${socialProvidersConfig}
723
+ },`;
724
+ if (serverPlugins.length > 0) configBody += `
725
+ plugins: [
726
+ ${serverPlugins.join(",\n ")}
727
+ ],`;
728
+ return `${imports.join("\n")}
729
+ ${prismaInstance || ""}
730
+ export const auth = betterAuth({
731
+ ${configBody}
732
+ });
733
+ `;
734
+ }
735
+ function generateClientFile(options) {
736
+ const { framework, plugins } = options;
737
+ const imports = [FRAMEWORK_CONFIGS[framework].clientImport];
738
+ const { clientImports } = generatePluginImports(plugins);
739
+ imports.push(...clientImports);
740
+ const { clientPlugins } = generatePluginSetup(plugins);
741
+ let configBody = "";
742
+ if (clientPlugins.length > 0) configBody = `{
743
+ plugins: [
744
+ ${clientPlugins.join(",\n ")}
745
+ ],
746
+ }`;
747
+ else configBody = "{}";
748
+ return `${imports.join("\n")}
749
+
750
+ export const authClient = createAuthClient(${configBody});
751
+ `;
752
+ }
753
+ function generateApiRouteFile(framework, authPath) {
754
+ const frameworkConfig = FRAMEWORK_CONFIGS[framework];
755
+ if (!frameworkConfig.apiRouteTemplate) return null;
756
+ return frameworkConfig.apiRouteTemplate(authPath) || null;
757
+ }
758
+ function generateUpdateChanges(options) {
759
+ const { socialProviders, plugins, hasEmailPassword } = options;
760
+ const serverChanges = [];
761
+ const clientChanges = [];
762
+ if (hasEmailPassword) serverChanges.push({
763
+ type: "add_to_config",
764
+ content: `emailAndPassword: {
765
+ enabled: true,
766
+ }`,
767
+ description: "Enable email and password authentication"
768
+ });
769
+ const { serverImports, clientImports } = generatePluginImports(plugins);
770
+ for (const imp of serverImports) serverChanges.push({
771
+ type: "add_import",
772
+ content: imp,
773
+ description: "Add plugin import"
774
+ });
775
+ for (const provider of socialProviders) serverChanges.push({
776
+ type: "add_to_config",
777
+ content: generateSocialProviderConfig(provider),
778
+ description: `Add ${provider} social provider`
779
+ });
780
+ const { serverPlugins, clientPlugins } = generatePluginSetup(plugins);
781
+ for (const plugin of serverPlugins) serverChanges.push({
782
+ type: "add_plugin",
783
+ content: plugin,
784
+ description: "Add server plugin"
785
+ });
786
+ for (const imp of clientImports) clientChanges.push({
787
+ type: "add_import",
788
+ content: imp,
789
+ description: "Add client plugin import"
790
+ });
791
+ for (const plugin of clientPlugins) clientChanges.push({
792
+ type: "add_plugin",
793
+ content: plugin,
794
+ description: "Add client plugin"
795
+ });
796
+ return {
797
+ serverChanges,
798
+ clientChanges
799
+ };
800
+ }
801
+ function generateNextSteps(mode, socialProviders, plugins) {
802
+ const steps = [];
803
+ if (mode === "create") {
804
+ steps.push("Set environment variables in .env file");
805
+ steps.push("Run database migrations");
806
+ }
807
+ if (socialProviders.length > 0) {
808
+ steps.push(`Configure OAuth apps for: ${socialProviders.join(", ")}`);
809
+ steps.push("Add callback URLs to OAuth provider dashboards");
810
+ }
811
+ if (plugins.includes("magic-link")) steps.push("Configure email provider for magic links");
812
+ if (plugins.includes("phone-number")) steps.push("Configure SMS provider for OTP");
813
+ if (plugins.includes("captcha")) steps.push("Set up captcha provider (Cloudflare Turnstile, reCAPTCHA, or hCaptcha)");
814
+ steps.push("Start your development server and test auth flow");
815
+ return steps;
816
+ }
817
+ function generateDocLinks(features) {
818
+ const docs = [{
819
+ title: "Better Auth Documentation",
820
+ url: "https://www.better-auth.com/docs"
821
+ }, {
822
+ title: "Getting Started",
823
+ url: "https://www.better-auth.com/docs/getting-started"
824
+ }];
825
+ if (features.some((f) => [
826
+ "google",
827
+ "github",
828
+ "apple",
829
+ "discord"
830
+ ].includes(f))) docs.push({
831
+ title: "Social Sign-On",
832
+ url: "https://www.better-auth.com/docs/authentication/social-sign-on"
833
+ });
834
+ if (features.includes("2fa")) docs.push({
835
+ title: "Two-Factor Authentication",
836
+ url: "https://www.better-auth.com/docs/plugins/two-factor"
837
+ });
838
+ if (features.includes("organization")) docs.push({
839
+ title: "Organizations",
840
+ url: "https://www.better-auth.com/docs/plugins/organization"
841
+ });
842
+ if (features.includes("passkey")) docs.push({
843
+ title: "Passkeys",
844
+ url: "https://www.better-auth.com/docs/plugins/passkey"
845
+ });
846
+ return docs;
847
+ }
848
+ function isSetupError(result) {
849
+ return "error" in result;
850
+ }
851
+
852
+ //#endregion
853
+ //#region src/lib/schemas.ts
854
+ const FrameworkEnum = z.enum([
855
+ "next-app-router",
856
+ "next-pages-router",
857
+ "sveltekit",
858
+ "astro",
859
+ "remix",
860
+ "nuxt",
861
+ "solid-start",
862
+ "hono",
863
+ "express",
864
+ "fastify",
865
+ "elysia",
866
+ "tanstack-start",
867
+ "expo"
868
+ ]);
869
+ const DatabaseEnum = z.enum([
870
+ "postgres",
871
+ "mysql",
872
+ "sqlite",
873
+ "mongodb"
874
+ ]);
875
+ const ORMEnum = z.enum([
876
+ "prisma",
877
+ "drizzle",
878
+ "none"
879
+ ]);
880
+ const FeatureEnum = z.enum([
881
+ "email-password",
882
+ "magic-link",
883
+ "phone-number",
884
+ "passkey",
885
+ "anonymous",
886
+ "google",
887
+ "github",
888
+ "apple",
889
+ "discord",
890
+ "twitter",
891
+ "facebook",
892
+ "microsoft",
893
+ "linkedin",
894
+ "2fa",
895
+ "captcha",
896
+ "organization",
897
+ "admin",
898
+ "username",
899
+ "multi-session",
900
+ "api-key",
901
+ "bearer",
902
+ "jwt"
903
+ ]);
904
+ const ExistingSetupSchema = z.object({
905
+ authConfig: z.string().optional().describe("Contents of existing auth.ts file"),
906
+ authClientConfig: z.string().optional().describe("Contents of existing auth-client.ts file"),
907
+ envVars: z.array(z.string()).optional().describe("List of existing environment variable names")
908
+ });
909
+
910
+ //#endregion
911
+ //#region src/tools/setup-auth.ts
912
+ function registerSetupAuth(server$1) {
913
+ server$1.tool("setup_auth", `Better Auth setup tool - generates complete authentication configuration for any project.
914
+
915
+ IMPORTANT: This tool is for the Better Auth framework (npm: better-auth).
916
+ NOT for NextAuth, Auth.js, Clerk, Auth0, Supabase Auth, or Firebase Auth.
917
+
918
+ ALWAYS USE THIS TOOL when user mentions ANY of these:
919
+
920
+ Core triggers (any spelling/variation):
921
+ - "better auth", "betterauth", "Better Auth", "better-auth"
922
+ - "Set up Better Auth", "configure Better Auth", "integrate Better Auth", "use Better Auth"
923
+ - "set up auth", "setup auth", "configure auth", "add auth", "implement auth", "create auth"
924
+ - "add authentication", "add login", "add signup", "create login", "build auth", "build authentication"
925
+ - "I need auth", "I need authentication", "help me with auth", "help me add login"
926
+ - "add user login", "user authentication", "make login work", "authentication for my app"
927
+
928
+ Framework-specific triggers:
929
+ - "auth for Next.js", "Next.js authentication", "add auth to my Next app", "Next.js app router auth"
930
+ - "auth for SvelteKit", "SvelteKit authentication", "Svelte auth"
931
+ - "auth for Remix", "auth for Nuxt", "auth for Astro", "auth for Solid", "authentication for Remix/Nuxt/Astro"
932
+ - "Express auth", "Hono auth", "Fastify auth", "Elysia auth", "TanStack Start auth"
933
+ - "React Native auth", "Expo auth", "mobile auth"
934
+
935
+ Database triggers:
936
+ - "auth with PostgreSQL", "auth with MySQL", "auth with SQLite", "auth with MongoDB"
937
+ - "Set up auth with PostgreSQL/MySQL/SQLite", "configure database for auth"
938
+ - "auth with Prisma", "auth with Drizzle", "configure auth with Prisma/Drizzle"
939
+
940
+ Social/OAuth triggers:
941
+ - "add Google login", "add GitHub login", "add Apple login", "add Discord login"
942
+ - "add Twitter login", "add Facebook login", "add Microsoft login", "add LinkedIn login"
943
+ - "Add Google/GitHub/Apple/Discord/Twitter/Facebook/Microsoft/LinkedIn login"
944
+ - "add social login", "add OAuth", "add OAuth providers", "add SSO", "social authentication"
945
+
946
+ Security feature triggers:
947
+ - "Set up 2FA", "add 2FA", "add two-factor", "add two-factor authentication"
948
+ - "enable MFA", "add TOTP", "authenticator app"
949
+ - "add passkeys", "passwordless", "passwordless authentication", "add magic links", "email login", "email verification"
950
+
951
+ Organization/team triggers:
952
+ - "add organizations", "Add organization support", "enable multi-tenancy", "multi-tenancy"
953
+ - "team management", "workspaces"
954
+
955
+ API/token triggers:
956
+ - "add API keys", "Add API key authentication", "bearer tokens", "JWT auth", "JWT authentication"
957
+
958
+ Admin/user triggers:
959
+ - "add admin panel", "admin auth", "admin authentication"
960
+ - "Add user management", "user management", "handle sessions", "session management"
961
+ - "Add username login", "username login", "phone auth", "phone number auth", "anonymous auth"
962
+
963
+ OUTPUT: Returns all files, environment variables, and terminal commands needed.
964
+ One tool call = complete auth setup ready to copy-paste.`, {
965
+ 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'"),
966
+ 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'"),
967
+ orm: ORMEnum.optional().default("none").describe("ORM being used - affects adapter imports. Options: 'prisma', 'drizzle', 'none'. Detect from package.json or user's message."),
968
+ 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']"),
969
+ typescript: z.boolean().optional().default(true).describe("Generate TypeScript (.ts) or JavaScript (.js). Default: true (TypeScript)"),
970
+ 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/."),
971
+ authPath: z.string().optional().describe("Where to create auth.ts file. Auto-detected based on framework. Override only if user specifies custom path."),
972
+ apiPath: z.string().optional().describe("API route path for auth handler. Auto-detected based on framework. Override only if user specifies custom path."),
973
+ 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.")
974
+ }, async (input) => {
975
+ try {
976
+ const result = generateSetup({
977
+ framework: input.framework,
978
+ database: input.database,
979
+ orm: input.orm || "none",
980
+ features: input.features || ["email-password"],
981
+ typescript: input.typescript ?? true,
982
+ srcDir: input.srcDir ?? false,
983
+ authPath: input.authPath,
984
+ apiPath: input.apiPath,
985
+ existingSetup: input.existingSetup
986
+ });
987
+ if (isSetupError(result)) return {
988
+ content: [{
989
+ type: "text",
990
+ text: JSON.stringify(result, null, 2)
991
+ }],
992
+ isError: true
993
+ };
994
+ return { content: [{
995
+ type: "text",
996
+ text: JSON.stringify(result, null, 2)
997
+ }] };
998
+ } catch (error) {
999
+ const message = error instanceof Error ? error.message : "Unknown error";
1000
+ return {
1001
+ content: [{
1002
+ type: "text",
1003
+ text: JSON.stringify({ error: {
1004
+ code: "SETUP_ERROR",
1005
+ message
1006
+ } }, null, 2)
1007
+ }],
1008
+ isError: true
1009
+ };
1010
+ }
1011
+ });
1012
+ }
1013
+
1014
+ //#endregion
1015
+ //#region src/index.ts
1016
+ const server = new McpServer({
1017
+ name: "better-auth",
1018
+ description: "Better Auth MCP server for AI-powered auth setup and diagnostics",
1019
+ version: "0.0.1"
1020
+ });
1021
+ registerSetupAuth(server);
1022
+ async function main() {
1023
+ const transport = new StdioServerTransport();
1024
+ await server.connect(transport);
1025
+ }
1026
+ main().catch(console.error);
1027
+
1028
+ //#endregion
1029
+ export { };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@better-auth/mcp",
3
+ "version": "1.4.13",
4
+ "type": "module",
5
+ "description": "Better Auth MCP server for AI-powered auth setup and diagnostics",
6
+ "module": "dist/index.mjs",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/better-auth/better-auth.git",
10
+ "directory": "packages/mcp"
11
+ },
12
+ "homepage": "https://www.better-auth.com/docs/concepts/cli#mcp",
13
+ "main": "./dist/index.mjs",
14
+ "publishConfig": {
15
+ "access": "public",
16
+ "executableFiles": [
17
+ "./dist/index.mjs"
18
+ ]
19
+ },
20
+ "license": "MIT",
21
+ "keywords": [
22
+ "auth",
23
+ "mcp",
24
+ "ai",
25
+ "model-context-protocol",
26
+ "better-auth"
27
+ ],
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.mts",
31
+ "default": "./dist/index.mjs"
32
+ }
33
+ },
34
+ "bin": {
35
+ "better-auth-mcp": "./dist/index.mjs"
36
+ },
37
+ "devDependencies": {
38
+ "tsdown": "^0.17.2",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^1.8.0",
43
+ "zod": "^3.24.2"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "scripts": {
49
+ "build": "tsdown",
50
+ "dev": "tsdown --watch",
51
+ "typecheck": "tsc --project tsconfig.json"
52
+ }
53
+ }