@launchframe/mcp 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/content/auth/overview.md +64 -0
  2. package/dist/content/content/auth/overview.md +64 -0
  3. package/dist/content/content/credits/deduction.md +27 -0
  4. package/dist/content/content/credits/strategies.md +25 -0
  5. package/dist/content/content/crons/pattern.md +51 -0
  6. package/dist/content/content/entities/conventions.md +97 -0
  7. package/dist/content/content/env/conventions.md +123 -0
  8. package/dist/content/content/feature-gates/overview.md +74 -0
  9. package/dist/content/content/modules/structure.md +44 -0
  10. package/dist/content/content/queues/names.md +18 -0
  11. package/dist/content/content/variants/overview.md +67 -0
  12. package/dist/content/content/webhooks/architecture.md +53 -0
  13. package/dist/content/credits/deduction.md +27 -0
  14. package/dist/content/credits/strategies.md +25 -0
  15. package/dist/content/crons/pattern.md +51 -0
  16. package/dist/content/entities/conventions.md +97 -0
  17. package/dist/content/env/conventions.md +123 -0
  18. package/dist/content/feature-gates/overview.md +74 -0
  19. package/dist/content/modules/structure.md +44 -0
  20. package/dist/content/queues/names.md +18 -0
  21. package/dist/content/variants/overview.md +67 -0
  22. package/dist/content/webhooks/architecture.md +53 -0
  23. package/dist/index.d.ts +2 -0
  24. package/dist/index.js +5 -0
  25. package/dist/lib/content.d.ts +5 -0
  26. package/dist/lib/content.js +11 -0
  27. package/dist/server.d.ts +2 -0
  28. package/dist/server.js +27 -0
  29. package/dist/tools/auth.d.ts +2 -0
  30. package/dist/tools/auth.js +108 -0
  31. package/dist/tools/credits.d.ts +2 -0
  32. package/dist/tools/credits.js +43 -0
  33. package/dist/tools/crons.d.ts +2 -0
  34. package/dist/tools/crons.js +76 -0
  35. package/dist/tools/entities.d.ts +2 -0
  36. package/dist/tools/entities.js +94 -0
  37. package/dist/tools/env.d.ts +2 -0
  38. package/dist/tools/env.js +6 -0
  39. package/dist/tools/feature-gates.d.ts +2 -0
  40. package/dist/tools/feature-gates.js +59 -0
  41. package/dist/tools/modules.d.ts +2 -0
  42. package/dist/tools/modules.js +144 -0
  43. package/dist/tools/queues.d.ts +2 -0
  44. package/dist/tools/queues.js +81 -0
  45. package/dist/tools/variants.d.ts +2 -0
  46. package/dist/tools/variants.js +6 -0
  47. package/dist/tools/webhooks.d.ts +2 -0
  48. package/dist/tools/webhooks.js +121 -0
  49. package/package.json +23 -0
package/dist/server.js ADDED
@@ -0,0 +1,27 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { registerAuthTools } from './tools/auth.js';
3
+ import { registerFeatureGatesTools } from './tools/feature-gates.js';
4
+ import { registerCreditsTools } from './tools/credits.js';
5
+ import { registerQueueTools } from './tools/queues.js';
6
+ import { registerWebhookTools } from './tools/webhooks.js';
7
+ import { registerCronTools } from './tools/crons.js';
8
+ import { registerModuleTools } from './tools/modules.js';
9
+ import { registerEntityTools } from './tools/entities.js';
10
+ import { registerEnvTools } from './tools/env.js';
11
+ import { registerVariantTools } from './tools/variants.js';
12
+ // Phase 2: import { registerCliTools } from './tools/cli.js';
13
+ export function createServer() {
14
+ const server = new McpServer({ name: 'launchframe-mcp', version: '1.0.0' });
15
+ registerAuthTools(server);
16
+ registerFeatureGatesTools(server);
17
+ registerCreditsTools(server);
18
+ registerQueueTools(server);
19
+ registerWebhookTools(server);
20
+ registerCronTools(server);
21
+ registerModuleTools(server);
22
+ registerEntityTools(server);
23
+ registerEnvTools(server);
24
+ registerVariantTools(server);
25
+ // Phase 2: registerCliTools(server);
26
+ return server;
27
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerAuthTools(server: McpServer): void;
@@ -0,0 +1,108 @@
1
+ import { z } from 'zod';
2
+ import { loadContent } from '../lib/content.js';
3
+ export function registerAuthTools(server) {
4
+ server.tool('auth_get_overview', 'Get full auth system overview: guard hierarchy, session flow, Better Auth setup, roles, and decorator system.', {}, async () => ({
5
+ content: [{ type: 'text', text: loadContent('auth/overview.md') }],
6
+ }));
7
+ server.tool('auth_get_decorator_usage', 'Get the exact decorator and import for a specific auth need.', {
8
+ need: z.enum([
9
+ 'public',
10
+ 'optional',
11
+ 'customer_portal',
12
+ 'admin_only',
13
+ 'current_user',
14
+ 'full_session',
15
+ ]).describe('The auth need for the route'),
16
+ }, async ({ need }) => {
17
+ const map = {
18
+ public: `// No authentication required
19
+ import { AllowAnonymous } from '../auth/auth.decorator';
20
+
21
+ @AllowAnonymous()
22
+ @Get('route')
23
+ handler() { ... }`,
24
+ optional: `// Auth is checked but not required — user may be undefined
25
+ import { OptionalAuth, UserSession } from '../auth/auth.decorator';
26
+ import { User } from '../users/user.entity';
27
+
28
+ @OptionalAuth()
29
+ @Get('route')
30
+ handler(@UserSession() user?: User) { ... }`,
31
+ customer_portal: `// Accessible by regular_user role (B2B2C variant only)
32
+ // Without this decorator, regular_user gets 401
33
+ import { CustomerPortal, UserSession } from '../auth/auth.decorator';
34
+ import { User } from '../users/user.entity';
35
+
36
+ @CustomerPortal()
37
+ @Get('route')
38
+ handler(@UserSession() user: User) { ... }`,
39
+ admin_only: `// Admin-only access: superadmin role only.
40
+ // Admin routes are separated by the /admin/* prefix with AdminGuard (applied globally via RouterModule).
41
+ // For a superadmin check on a non-admin route, guard manually in the handler:
42
+ import { UserSession } from '../auth/auth.decorator';
43
+ import { User } from '../users/user.entity';
44
+ import { ForbiddenException } from '@nestjs/common';
45
+
46
+ @Get('route')
47
+ handler(@UserSession() user: User) {
48
+ if (user.role !== 'superadmin') throw new ForbiddenException();
49
+ ...
50
+ }`,
51
+ current_user: `// Inject the current authenticated user
52
+ import { UserSession } from '../auth/auth.decorator';
53
+ import { User } from '../users/user.entity';
54
+
55
+ @Get('route')
56
+ handler(@UserSession() user: User) {
57
+ // user.id, user.email, user.role, etc.
58
+ }`,
59
+ full_session: `// Inject the full Better Auth session (user + session metadata)
60
+ import { Session } from '../auth/auth.decorator';
61
+
62
+ @Get('route')
63
+ handler(@Session() session: { user: any; session: any }) {
64
+ const { user, session: sessionData } = session;
65
+ }`,
66
+ };
67
+ return {
68
+ content: [{ type: 'text', text: map[need] ?? `Unknown need: ${need}` }],
69
+ };
70
+ });
71
+ server.tool('auth_get_guard_usage', 'Get the guard class, import path, and decorator combo for a specific guard type.', {
72
+ guard: z.enum(['admin', 'business_user', 'credits']).describe('The guard to look up'),
73
+ }, async ({ guard }) => {
74
+ const map = {
75
+ admin: `// AdminGuard — applied globally to all /admin/* routes via RouterModule.
76
+ // Source: src/modules/admin/guards/admin.guard.ts
77
+ // You do NOT need to add @UseGuards(AdminGuard) manually on admin controllers.
78
+ // The admin router config registers it globally for the admin route prefix.
79
+ // Checking inside: verifies user.role === 'superadmin'`,
80
+ business_user: `// BetterAuthGuard — the global default guard (all routes).
81
+ // Source: src/modules/auth/better-auth.guard.ts
82
+ // Applied globally in app.module.ts as APP_GUARD.
83
+ // Allows: business_user, superadmin
84
+ // Blocks: unauthenticated, regular_user (unless @CustomerPortal())
85
+ // You never need to add this manually.`,
86
+ credits: `// CreditsGuard — deducts credits per request based on @DeductCredits(n).
87
+ // Source: src/modules/credits/guards/credits.guard.ts
88
+ // MUST be combined with @DeductCredits(n) decorator.
89
+ import { UseGuards } from '@nestjs/common';
90
+ import { CreditsGuard } from '../credits/guards/credits.guard';
91
+ import { DeductCredits } from '../credits/decorators/deduct-credits.decorator';
92
+
93
+ @DeductCredits(10)
94
+ @UseGuards(CreditsGuard)
95
+ @Post('ai-operation')
96
+ handler() { ... }
97
+
98
+ // Strategy behaviour (read from AdminSettings, cached 24h in Redis):
99
+ // - free: bypass (no deduction)
100
+ // - subscription: bypass (use feature gates instead)
101
+ // - credits: deduct from balance
102
+ // - hybrid: deduct from monthly allowance first, then Polar overage`,
103
+ };
104
+ return {
105
+ content: [{ type: 'text', text: map[guard] ?? `Unknown guard: ${guard}` }],
106
+ };
107
+ });
108
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCreditsTools(server: McpServer): void;
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ import { loadContent } from '../lib/content.js';
3
+ export function registerCreditsTools(server) {
4
+ server.tool('credits_get_deduction_pattern', 'Get the decorator + guard pattern for deducting credits on a route.', {}, async () => ({
5
+ content: [{ type: 'text', text: loadContent('credits/deduction.md') }],
6
+ }));
7
+ server.tool('credits_get_add_pattern', 'Get the code snippet for programmatically adding credits to a user with a specific transaction type.', {
8
+ transactionType: z
9
+ .enum(['INITIAL', 'PURCHASE', 'USAGE', 'REFUND', 'BONUS', 'EXPIRY', 'REDEMPTION'])
10
+ .describe('The CreditTransactionType to use'),
11
+ }, async ({ transactionType }) => {
12
+ const descriptions = {
13
+ INITIAL: 'Grant starting credits to a new user',
14
+ PURCHASE: 'Credit top-up purchased by the user',
15
+ USAGE: 'Manual usage deduction (prefer CreditsGuard for route-level deduction)',
16
+ REFUND: 'Refund credits after a failed or cancelled operation',
17
+ BONUS: 'Promotional or reward credits',
18
+ EXPIRY: 'Expire/remove unused credits',
19
+ REDEMPTION: 'Redeem credits (e.g. voucher, referral)',
20
+ };
21
+ const snippet = `// ${descriptions[transactionType]}
22
+ import { CreditsService } from '../credits/credits.service';
23
+ import { CreditTransactionType } from '../credits/entities/credit-transaction.entity';
24
+
25
+ // Inject in constructor:
26
+ constructor(private readonly creditsService: CreditsService) {}
27
+
28
+ // Call:
29
+ await this.creditsService.addCredits(
30
+ user, // User entity
31
+ 100, // amount (positive to add, negative to deduct)
32
+ CreditTransactionType.${transactionType},
33
+ 'Optional description', // optional
34
+ 'optional-ref-id', // optional — external reference (e.g. Polar order ID)
35
+ );`;
36
+ return {
37
+ content: [{ type: 'text', text: snippet }],
38
+ };
39
+ });
40
+ server.tool('credits_get_monetization_strategies', 'Get an overview of all monetization strategies (free, subscription, credits, hybrid) and when to use each.', {}, async () => ({
41
+ content: [{ type: 'text', text: loadContent('credits/strategies.md') }],
42
+ }));
43
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCronTools(server: McpServer): void;
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+ import { loadContent } from '../lib/content.js';
3
+ const CRON_EXPRESSIONS = [
4
+ 'EVERY_MINUTE',
5
+ 'EVERY_30_MINUTES',
6
+ 'EVERY_HOUR',
7
+ 'EVERY_DAY_AT_MIDNIGHT',
8
+ 'EVERY_WEEK',
9
+ ];
10
+ export function registerCronTools(server) {
11
+ server.tool('cron_get_pattern', 'Get the LaunchFrame cron job pattern: where jobs live, available CronExpression presets, and module registration rules.', {}, async () => ({
12
+ content: [{ type: 'text', text: loadContent('crons/pattern.md') }],
13
+ }));
14
+ server.tool('cron_scaffold_job', 'Scaffold a new cron method to add to CronService (src/jobs/cron.service.ts).', {
15
+ methodName: z
16
+ .string()
17
+ .describe('The name of the cron method, camelCase (e.g. "syncUserStats")'),
18
+ schedule: z
19
+ .enum(CRON_EXPRESSIONS)
20
+ .describe('The CronExpression preset to use'),
21
+ queueName: z
22
+ .string()
23
+ .optional()
24
+ .describe('Optional Bull queue name to enqueue work into (e.g. "api"). Omit for jobs that do lightweight direct work.'),
25
+ }, async ({ methodName, schedule, queueName }) => {
26
+ const queueInjection = queueName
27
+ ? `\n @InjectQueue('${queueName}') private readonly ${toCamelCase(queueName)}Queue: Queue,`
28
+ : '';
29
+ const queueImports = queueName
30
+ ? `\nimport { InjectQueue } from '@nestjs/bull';\nimport { Queue } from 'bull';`
31
+ : '';
32
+ const methodBody = queueName
33
+ ? ` this.logger.log('Starting ${methodName}...');
34
+ try {
35
+ // TODO: fetch items to process
36
+ // const items = await this.repo.find({ where: { ... } });
37
+ // for (const item of items) {
38
+ // await this.${toCamelCase(queueName)}Queue.add({ id: item.id }, {
39
+ // attempts: 3,
40
+ // backoff: { type: 'exponential', delay: 2000 },
41
+ // });
42
+ // }
43
+ } catch (error) {
44
+ this.logger.error('Error in ${methodName}:', error);
45
+ }`
46
+ : ` this.logger.log('Starting ${methodName}...');
47
+ try {
48
+ // TODO: implement job logic
49
+ } catch (error) {
50
+ this.logger.error('Error in ${methodName}:', error);
51
+ }`;
52
+ const snippet = `// Add this method to src/jobs/cron.service.ts
53
+ // ─── Additional imports needed ───
54
+ import { Cron, CronExpression } from '@nestjs/schedule';${queueImports}
55
+
56
+ // ─── Add to CronService constructor params ───
57
+ constructor(
58
+ // ... existing params ...${queueInjection}
59
+ ) {}
60
+
61
+ // ─── New cron method ───
62
+ @Cron(CronExpression.${schedule})
63
+ async ${methodName}() {
64
+ ${methodBody}
65
+ }`;
66
+ return {
67
+ content: [{ type: 'text', text: snippet }],
68
+ };
69
+ });
70
+ }
71
+ function toCamelCase(str) {
72
+ return str
73
+ .split('-')
74
+ .map((part, i) => (i === 0 ? part : part.charAt(0).toUpperCase() + part.slice(1)))
75
+ .join('');
76
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerEntityTools(server: McpServer): void;
@@ -0,0 +1,94 @@
1
+ import { z } from 'zod';
2
+ import { loadContent } from '../lib/content.js';
3
+ export function registerEntityTools(server) {
4
+ server.tool('entity_get_conventions', 'Get TypeORM entity conventions for LaunchFrame: required decorators, naming strategy, column types, relations, and multi-tenancy.', {}, async () => ({
5
+ content: [{ type: 'text', text: loadContent('entities/conventions.md') }],
6
+ }));
7
+ server.tool('entity_scaffold_typeorm', 'Generate a TypeORM entity file following LaunchFrame conventions.', {
8
+ entityName: z
9
+ .string()
10
+ .describe('Entity class name in PascalCase (e.g. "FeedbackEntry", "AiSummary")'),
11
+ tableName: z
12
+ .string()
13
+ .describe('Database table name in snake_case (e.g. "feedback_entries", "ai_summaries")'),
14
+ primaryKeyType: z
15
+ .enum(['int', 'uuid'])
16
+ .default('int')
17
+ .describe('Primary key type: "int" (auto-increment) or "uuid"'),
18
+ multiTenant: z
19
+ .boolean()
20
+ .default(false)
21
+ .describe('Add projectId column for multi-tenant variant'),
22
+ withEnum: z
23
+ .string()
24
+ .optional()
25
+ .describe('Optional: define a status enum. Provide enum name in PascalCase (e.g. "EntryStatus"). Values will be a placeholder — edit as needed.'),
26
+ }, async ({ entityName, tableName, primaryKeyType, multiTenant, withEnum, }) => {
27
+ const pkDeclarator = primaryKeyType === 'uuid'
28
+ ? `@PrimaryGeneratedColumn('uuid')\n id: string;`
29
+ : `@PrimaryGeneratedColumn()\n id: number;`;
30
+ const enumBlock = withEnum
31
+ ? `export enum ${withEnum} {
32
+ ACTIVE = 'active',
33
+ INACTIVE = 'inactive',
34
+ // TODO: add values
35
+ }
36
+
37
+ `
38
+ : '';
39
+ const enumImportEntry = withEnum ? `, Column` : `, Column`;
40
+ const multiTenantBlock = multiTenant
41
+ ? ` // MULTI_TENANT_FIELDS_START
42
+ @Column() projectId: number;
43
+ // MULTI_TENANT_FIELDS_END
44
+
45
+ `
46
+ : ` // MULTI_TENANT_FIELDS_START
47
+ // @Column() projectId: number;
48
+ // MULTI_TENANT_FIELDS_END
49
+
50
+ `;
51
+ const enumColumnBlock = withEnum
52
+ ? `
53
+ @Column({ type: 'enum', enum: ${withEnum} })
54
+ status: ${withEnum};
55
+ `
56
+ : '';
57
+ const imports = [
58
+ 'Entity',
59
+ 'PrimaryGeneratedColumn',
60
+ 'Column',
61
+ 'CreateDateColumn',
62
+ 'UpdateDateColumn',
63
+ ];
64
+ const entityFile = `import { ${imports.join(', ')} } from 'typeorm';
65
+
66
+ ${enumBlock}@Entity('${tableName}')
67
+ export class ${entityName} {
68
+ ${pkDeclarator}
69
+
70
+ ${multiTenantBlock} // TODO: add domain columns here
71
+ // @Column() name: string;
72
+ // @Column({ type: 'text', nullable: true }) description: string;
73
+ ${enumColumnBlock}
74
+ @CreateDateColumn()
75
+ createdAt: Date;
76
+
77
+ @UpdateDateColumn()
78
+ updatedAt: Date;
79
+ }
80
+ `;
81
+ const registerNote = `// === Register in your module ===
82
+ // In src/modules/<domain>/<domain>.module.ts:
83
+ // imports: [TypeOrmModule.forFeature([${entityName}])]
84
+ `;
85
+ return {
86
+ content: [
87
+ {
88
+ type: 'text',
89
+ text: `// === entities/${tableName}.entity.ts ===\n${entityFile}\n${registerNote}`,
90
+ },
91
+ ],
92
+ };
93
+ });
94
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerEnvTools(server: McpServer): void;
@@ -0,0 +1,6 @@
1
+ import { loadContent } from '../lib/content.js';
2
+ export function registerEnvTools(server) {
3
+ server.tool('env_get_conventions', 'Get environment variable conventions for LaunchFrame: single centralized .env location, variable naming rules, full key variable reference, and how to add new variables.', {}, async () => ({
4
+ content: [{ type: 'text', text: loadContent('env/conventions.md') }],
5
+ }));
6
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerFeatureGatesTools(server: McpServer): void;
@@ -0,0 +1,59 @@
1
+ import { z } from 'zod';
2
+ import { loadContent } from '../lib/content.js';
3
+ export function registerFeatureGatesTools(server) {
4
+ server.tool('feature_gates_get_overview', 'Get the feature gate system overview: how features are stored, how to query them, and the check pattern.', {}, async () => ({
5
+ content: [{ type: 'text', text: loadContent('feature-gates/overview.md') }],
6
+ }));
7
+ server.tool('feature_gates_get_check_pattern', 'Get a copy-paste TypeScript snippet for checking a feature gate by code and type.', {
8
+ featureCode: z.string().describe('The feature code to check (as defined in the database)'),
9
+ featureType: z.enum(['boolean', 'numeric']).describe('Whether to generate a boolean or numeric (with limit) check'),
10
+ }, async ({ featureCode, featureType }) => {
11
+ const label = featureCode.replace(/_/g, ' ');
12
+ const camel = featureCode.split('_').map(w => w[0].toUpperCase() + w.slice(1)).join('');
13
+ const checkSnippet = featureType === 'boolean'
14
+ ? `const features = await this.userSubscriptionService.getCurrentFeatures(userId);
15
+
16
+ const hasAccess = features['${featureCode}'] === true;
17
+ if (!hasAccess) {
18
+ throw new ForbiddenException('Your plan does not include ${label}');
19
+ }`
20
+ : `const features = await this.userSubscriptionService.getCurrentFeatures(userId);
21
+
22
+ const limit = features['${featureCode}'] as number ?? 0;
23
+ const isUnlimited = limit === -1;
24
+ const currentCount = await this.get${camel}Count(userId);
25
+ if (!isUnlimited && currentCount >= limit) {
26
+ throw new ForbiddenException(\`Plan limit reached for ${label} (\${limit})\`);
27
+ }`;
28
+ return {
29
+ content: [{
30
+ type: 'text',
31
+ text: `# Feature Gate Check: \`${featureCode}\` (${featureType})
32
+
33
+ ## Imports
34
+
35
+ \`\`\`typescript
36
+ import { Injectable, ForbiddenException } from '@nestjs/common';
37
+ import { UserSubscriptionService } from '../subscriptions/services/user-subscription.service';
38
+ \`\`\`
39
+
40
+ ## Constructor Injection
41
+
42
+ \`\`\`typescript
43
+ constructor(
44
+ private readonly userSubscriptionService: UserSubscriptionService,
45
+ ) {}
46
+ \`\`\`
47
+
48
+ ## Check
49
+
50
+ \`\`\`typescript
51
+ ${checkSnippet}
52
+ \`\`\`
53
+
54
+ > Feature code \`${featureCode}\` must exist in the \`subscription_plan_features\` table and have values configured per plan.
55
+ `,
56
+ }],
57
+ };
58
+ });
59
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerModuleTools(server: McpServer): void;
@@ -0,0 +1,144 @@
1
+ import { z } from 'zod';
2
+ import { loadContent } from '../lib/content.js';
3
+ export function registerModuleTools(server) {
4
+ server.tool('module_get_structure', 'Get the NestJS module folder structure, conventions, and rules used in LaunchFrame.', {}, async () => ({
5
+ content: [{ type: 'text', text: loadContent('modules/structure.md') }],
6
+ }));
7
+ server.tool('module_scaffold_nestjs', 'Generate a NestJS module scaffold (module + service + optional controller + optional entity) following LaunchFrame conventions.', {
8
+ moduleName: z
9
+ .string()
10
+ .describe('Domain name in kebab-case (e.g. "projects", "ai-summaries")'),
11
+ withController: z
12
+ .boolean()
13
+ .default(true)
14
+ .describe('Include a controller with basic CRUD routes'),
15
+ withEntity: z
16
+ .boolean()
17
+ .default(true)
18
+ .describe('Include a TypeORM entity and register it via TypeOrmModule.forFeature'),
19
+ }, async ({ moduleName, withController, withEntity, }) => {
20
+ const pascal = toPascalCase(moduleName);
21
+ const camel = toCamelCase(moduleName);
22
+ const snake = toSnakeCase(moduleName);
23
+ const entityImport = withEntity
24
+ ? `import { TypeOrmModule } from '@nestjs/typeorm';\nimport { ${pascal} } from './entities/${moduleName}.entity';`
25
+ : '';
26
+ const entityFeature = withEntity
27
+ ? `\n TypeOrmModule.forFeature([${pascal}]),`
28
+ : '';
29
+ const controllerImport = withController
30
+ ? `import { ${pascal}Controller } from './${moduleName}.controller';`
31
+ : '';
32
+ const controllerDecl = withController ? `\n controllers: [${pascal}Controller],` : '';
33
+ const moduleFile = `import { Module } from '@nestjs/common';
34
+ ${entityImport}
35
+ import { ${pascal}Service } from './${moduleName}.service';
36
+ ${controllerImport}
37
+
38
+ @Module({
39
+ imports: [${entityFeature}
40
+ // MULTI_TENANT_MODULE_IMPORTS_START
41
+ // MULTI_TENANT_MODULE_IMPORTS_END
42
+ ],
43
+ providers: [
44
+ ${pascal}Service,
45
+ // MULTI_TENANT_PROVIDERS_START
46
+ // MULTI_TENANT_PROVIDERS_END
47
+ ],${controllerDecl}
48
+ exports: [${pascal}Service],
49
+ })
50
+ export class ${pascal}Module {}`;
51
+ const serviceFile = `import { Injectable } from '@nestjs/common';
52
+ ${withEntity ? `import { InjectRepository } from '@nestjs/typeorm';\nimport { Repository } from 'typeorm';\nimport { ${pascal} } from './entities/${moduleName}.entity';` : ''}
53
+
54
+ @Injectable()
55
+ export class ${pascal}Service {
56
+ ${withEntity ? ` constructor(
57
+ @InjectRepository(${pascal})
58
+ private readonly ${camel}Repository: Repository<${pascal}>,
59
+ ) {}` : ''}
60
+ // TODO: implement service methods
61
+ }`;
62
+ const controllerFile = withController
63
+ ? `import { Controller, Get, Post, Body, Param, Delete, Put } from '@nestjs/common';
64
+ import { ${pascal}Service } from './${moduleName}.service';
65
+ import { CurrentUser } from '@/modules/auth/auth.decorator';
66
+ import { User } from '@/modules/users/user.entity';
67
+
68
+ @Controller('${moduleName}')
69
+ export class ${pascal}Controller {
70
+ constructor(private readonly ${camel}Service: ${pascal}Service) {}
71
+
72
+ @Get()
73
+ findAll(@CurrentUser() user: User) {
74
+ // TODO: implement
75
+ }
76
+
77
+ @Get(':id')
78
+ findOne(@Param('id') id: string, @CurrentUser() user: User) {
79
+ // TODO: implement
80
+ }
81
+
82
+ @Post()
83
+ create(@Body() body: Record<string, unknown>, @CurrentUser() user: User) {
84
+ // TODO: implement
85
+ }
86
+
87
+ @Put(':id')
88
+ update(@Param('id') id: string, @Body() body: Record<string, unknown>, @CurrentUser() user: User) {
89
+ // TODO: implement
90
+ }
91
+
92
+ @Delete(':id')
93
+ remove(@Param('id') id: string, @CurrentUser() user: User) {
94
+ // TODO: implement
95
+ }
96
+ }`
97
+ : null;
98
+ const entityFile = withEntity
99
+ ? `import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
100
+
101
+ @Entity('${snake}')
102
+ export class ${pascal} {
103
+ @PrimaryGeneratedColumn()
104
+ id: number;
105
+
106
+ // MULTI_TENANT_FIELDS_START
107
+ // @Column() projectId: number;
108
+ // MULTI_TENANT_FIELDS_END
109
+
110
+ // TODO: add domain columns here
111
+
112
+ @CreateDateColumn()
113
+ createdAt: Date;
114
+
115
+ @UpdateDateColumn()
116
+ updatedAt: Date;
117
+ }`
118
+ : null;
119
+ const sections = [
120
+ `// === ${moduleName}.module.ts ===\n${moduleFile}`,
121
+ `\n// === ${moduleName}.service.ts ===\n${serviceFile}`,
122
+ ];
123
+ if (controllerFile)
124
+ sections.push(`\n// === ${moduleName}.controller.ts ===\n${controllerFile}`);
125
+ if (entityFile)
126
+ sections.push(`\n// === entities/${moduleName}.entity.ts ===\n${entityFile}`);
127
+ sections.push(`
128
+ // === Register in app.module.ts ===
129
+ // Add ${pascal}Module to the imports array in src/modules/app/app.module.ts`);
130
+ return {
131
+ content: [{ type: 'text', text: sections.join('\n') }],
132
+ };
133
+ });
134
+ }
135
+ function toCamelCase(str) {
136
+ return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
137
+ }
138
+ function toPascalCase(str) {
139
+ const camel = toCamelCase(str);
140
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
141
+ }
142
+ function toSnakeCase(str) {
143
+ return str.replace(/-/g, '_');
144
+ }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerQueueTools(server: McpServer): void;