@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 +17 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +1029 -0
- package/package.json +53 -0
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.
|
package/dist/index.d.mts
ADDED
|
@@ -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
|
+
}
|