@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.
- package/dist/content/auth/overview.md +64 -0
- package/dist/content/content/auth/overview.md +64 -0
- package/dist/content/content/credits/deduction.md +27 -0
- package/dist/content/content/credits/strategies.md +25 -0
- package/dist/content/content/crons/pattern.md +51 -0
- package/dist/content/content/entities/conventions.md +97 -0
- package/dist/content/content/env/conventions.md +123 -0
- package/dist/content/content/feature-gates/overview.md +74 -0
- package/dist/content/content/modules/structure.md +44 -0
- package/dist/content/content/queues/names.md +18 -0
- package/dist/content/content/variants/overview.md +67 -0
- package/dist/content/content/webhooks/architecture.md +53 -0
- package/dist/content/credits/deduction.md +27 -0
- package/dist/content/credits/strategies.md +25 -0
- package/dist/content/crons/pattern.md +51 -0
- package/dist/content/entities/conventions.md +97 -0
- package/dist/content/env/conventions.md +123 -0
- package/dist/content/feature-gates/overview.md +74 -0
- package/dist/content/modules/structure.md +44 -0
- package/dist/content/queues/names.md +18 -0
- package/dist/content/variants/overview.md +67 -0
- package/dist/content/webhooks/architecture.md +53 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/lib/content.d.ts +5 -0
- package/dist/lib/content.js +11 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +27 -0
- package/dist/tools/auth.d.ts +2 -0
- package/dist/tools/auth.js +108 -0
- package/dist/tools/credits.d.ts +2 -0
- package/dist/tools/credits.js +43 -0
- package/dist/tools/crons.d.ts +2 -0
- package/dist/tools/crons.js +76 -0
- package/dist/tools/entities.d.ts +2 -0
- package/dist/tools/entities.js +94 -0
- package/dist/tools/env.d.ts +2 -0
- package/dist/tools/env.js +6 -0
- package/dist/tools/feature-gates.d.ts +2 -0
- package/dist/tools/feature-gates.js +59 -0
- package/dist/tools/modules.d.ts +2 -0
- package/dist/tools/modules.js +144 -0
- package/dist/tools/queues.d.ts +2 -0
- package/dist/tools/queues.js +81 -0
- package/dist/tools/variants.d.ts +2 -0
- package/dist/tools/variants.js +6 -0
- package/dist/tools/webhooks.d.ts +2 -0
- package/dist/tools/webhooks.js +121 -0
- package/package.json +23 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { loadContent } from '../lib/content.js';
|
|
3
|
+
const QUEUE_NAMES = ['emails', 'api', 'webhooks'];
|
|
4
|
+
export function registerQueueTools(server) {
|
|
5
|
+
server.tool('queue_get_names', 'List all available Bull queues in LaunchFrame with their purpose and usage rules.', {}, async () => ({
|
|
6
|
+
content: [{ type: 'text', text: loadContent('queues/names.md') }],
|
|
7
|
+
}));
|
|
8
|
+
server.tool('queue_scaffold_producer', 'Get the code to inject and use a Bull queue as a producer in a NestJS service.', {
|
|
9
|
+
queueName: z
|
|
10
|
+
.enum(QUEUE_NAMES)
|
|
11
|
+
.describe('The queue to produce jobs for'),
|
|
12
|
+
}, async ({ queueName }) => {
|
|
13
|
+
const snippet = `import { InjectQueue } from '@nestjs/bull';
|
|
14
|
+
import { Injectable } from '@nestjs/common';
|
|
15
|
+
import { Queue } from 'bull';
|
|
16
|
+
import { BullQueueModule } from '../bull/bull.module';
|
|
17
|
+
|
|
18
|
+
// In your module imports array:
|
|
19
|
+
// BullQueueModule,
|
|
20
|
+
// BullModule.registerQueue({ name: '${queueName}' }), // from '@nestjs/bull'
|
|
21
|
+
|
|
22
|
+
@Injectable()
|
|
23
|
+
export class YourService {
|
|
24
|
+
constructor(
|
|
25
|
+
@InjectQueue('${queueName}') private readonly ${camelCase(queueName)}Queue: Queue,
|
|
26
|
+
) {}
|
|
27
|
+
|
|
28
|
+
async enqueueJob(data: Record<string, unknown>): Promise<void> {
|
|
29
|
+
await this.${camelCase(queueName)}Queue.add('job-name', data, {
|
|
30
|
+
attempts: 3,
|
|
31
|
+
backoff: { type: 'exponential', delay: 2000 },
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}`;
|
|
35
|
+
return {
|
|
36
|
+
content: [{ type: 'text', text: snippet }],
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
server.tool('queue_scaffold_processor', 'Get a Bull processor class scaffold for a given queue and job name.', {
|
|
40
|
+
queueName: z
|
|
41
|
+
.enum(QUEUE_NAMES)
|
|
42
|
+
.describe('The queue this processor listens to'),
|
|
43
|
+
jobName: z
|
|
44
|
+
.string()
|
|
45
|
+
.describe('The job name string passed to queue.add() (e.g. "send-email")'),
|
|
46
|
+
}, async ({ queueName, jobName }) => {
|
|
47
|
+
const className = toPascalCase(queueName) + 'Processor';
|
|
48
|
+
const snippet = `import { Processor, Process } from '@nestjs/bull';
|
|
49
|
+
import { Logger } from '@nestjs/common';
|
|
50
|
+
import { Job } from 'bull';
|
|
51
|
+
|
|
52
|
+
// Add this class to the providers array of your module.
|
|
53
|
+
// Your module must also import BullQueueModule and BullModule.registerQueue({ name: '${queueName}' }).
|
|
54
|
+
|
|
55
|
+
@Processor('${queueName}')
|
|
56
|
+
export class ${className} {
|
|
57
|
+
private readonly logger = new Logger(${className}.name);
|
|
58
|
+
|
|
59
|
+
@Process('${jobName}')
|
|
60
|
+
async handle(job: Job<Record<string, unknown>>): Promise<void> {
|
|
61
|
+
this.logger.log(\`Processing job \${job.id}\`);
|
|
62
|
+
try {
|
|
63
|
+
// TODO: implement job logic using job.data
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.logger.error(\`Job \${job.id} failed\`, error);
|
|
66
|
+
throw error; // Required — Bull needs the error to trigger retries
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}`;
|
|
70
|
+
return {
|
|
71
|
+
content: [{ type: 'text', text: snippet }],
|
|
72
|
+
};
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function camelCase(str) {
|
|
76
|
+
return str.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
77
|
+
}
|
|
78
|
+
function toPascalCase(str) {
|
|
79
|
+
const camel = camelCase(str);
|
|
80
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
81
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { loadContent } from '../lib/content.js';
|
|
2
|
+
export function registerVariantTools(server) {
|
|
3
|
+
server.tool('variant_get_overview', 'Get an overview of LaunchFrame project variants: Base (B2B single-tenant), Multi-tenant, and B2B2C. Covers differences, section marker syntax for CLI code generation, and coding guidelines per variant.', {}, async () => ({
|
|
4
|
+
content: [{ type: 'text', text: loadContent('variants/overview.md') }],
|
|
5
|
+
}));
|
|
6
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { loadContent } from '../lib/content.js';
|
|
3
|
+
const PROVIDERS = ['POLAR', 'PAYPAL', 'STRIPE'];
|
|
4
|
+
export function registerWebhookTools(server) {
|
|
5
|
+
server.tool('webhook_get_architecture', 'Get an overview of the LaunchFrame webhook architecture: receipt/processing separation, WebhookLog entity, Bull queue, and retry cron.', {}, async () => ({
|
|
6
|
+
content: [{ type: 'text', text: loadContent('webhooks/architecture.md') }],
|
|
7
|
+
}));
|
|
8
|
+
server.tool('webhook_scaffold_handler', 'Get a scaffold for a new webhook handler: controller receipt + Bull processor for a given provider and event type.', {
|
|
9
|
+
provider: z
|
|
10
|
+
.enum(PROVIDERS)
|
|
11
|
+
.describe('The webhook provider (POLAR, PAYPAL, or STRIPE)'),
|
|
12
|
+
eventType: z
|
|
13
|
+
.string()
|
|
14
|
+
.describe('The event type string from the provider (e.g. "subscription.created", "order.paid")'),
|
|
15
|
+
}, async ({ provider, eventType }) => {
|
|
16
|
+
const providerLower = provider.toLowerCase();
|
|
17
|
+
const providerPascal = provider.charAt(0) + provider.slice(1).toLowerCase();
|
|
18
|
+
const guardName = `${providerPascal}WebhookGuard`;
|
|
19
|
+
const processorClass = `${providerPascal}WebhooksProcessor`;
|
|
20
|
+
const jobName = eventType;
|
|
21
|
+
const snippet = `// ─── Controller (receipt — already exists for POLAR, add for other providers) ───
|
|
22
|
+
// File: src/modules/webhooks/controllers/${providerLower}-webhooks.controller.ts
|
|
23
|
+
|
|
24
|
+
import { Controller, Post, Req, Res, UseGuards } from '@nestjs/common';
|
|
25
|
+
import { InjectQueue } from '@nestjs/bull';
|
|
26
|
+
import { Queue } from 'bull';
|
|
27
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
28
|
+
import { Repository } from 'typeorm';
|
|
29
|
+
import { Public } from '../../auth/auth.decorator';
|
|
30
|
+
import { ${guardName} } from '../guards/${providerLower}-webhook.guard';
|
|
31
|
+
import { WebhookLog, WebhookProvider } from '../entities/webhook-log.entity';
|
|
32
|
+
|
|
33
|
+
@Controller('webhooks')
|
|
34
|
+
export class ${providerPascal}WebhooksController {
|
|
35
|
+
constructor(
|
|
36
|
+
@InjectQueue('webhooks') private readonly webhooksQueue: Queue,
|
|
37
|
+
@InjectRepository(WebhookLog) private readonly webhookLogRepo: Repository<WebhookLog>,
|
|
38
|
+
) {}
|
|
39
|
+
|
|
40
|
+
@Post('${providerLower}')
|
|
41
|
+
@Public()
|
|
42
|
+
@UseGuards(${guardName})
|
|
43
|
+
async handle(@Req() req: any, @Res() res: any): Promise<void> {
|
|
44
|
+
const log = this.webhookLogRepo.create({
|
|
45
|
+
provider: WebhookProvider.${provider},
|
|
46
|
+
eventType: req.body?.type ?? 'unknown',
|
|
47
|
+
webhookId: req.body?.id ?? null,
|
|
48
|
+
payload: req.body,
|
|
49
|
+
headers: req.headers,
|
|
50
|
+
});
|
|
51
|
+
const saved = await this.webhookLogRepo.save(log);
|
|
52
|
+
await this.webhooksQueue.add('process-webhook', { webhookLogId: saved.id }, {
|
|
53
|
+
attempts: 3,
|
|
54
|
+
backoff: { type: 'exponential', delay: 2000 },
|
|
55
|
+
});
|
|
56
|
+
res.status(200).send();
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ─── Processor (processing layer) ───
|
|
61
|
+
// File: src/modules/webhooks/processors/${providerLower}-webhooks.processor.ts
|
|
62
|
+
|
|
63
|
+
import { Processor, Process } from '@nestjs/bull';
|
|
64
|
+
import { Logger } from '@nestjs/common';
|
|
65
|
+
import { Job } from 'bull';
|
|
66
|
+
import { InjectRepository } from '@nestjs/typeorm';
|
|
67
|
+
import { Repository } from 'typeorm';
|
|
68
|
+
import { WebhookLog } from '../entities/webhook-log.entity';
|
|
69
|
+
|
|
70
|
+
@Processor('webhooks')
|
|
71
|
+
export class ${processorClass} {
|
|
72
|
+
private readonly logger = new Logger(${processorClass}.name);
|
|
73
|
+
|
|
74
|
+
constructor(
|
|
75
|
+
@InjectRepository(WebhookLog) private readonly webhookLogRepo: Repository<WebhookLog>,
|
|
76
|
+
) {}
|
|
77
|
+
|
|
78
|
+
@Process('process-webhook')
|
|
79
|
+
async handle(job: Job<{ webhookLogId: number }>): Promise<void> {
|
|
80
|
+
const log = await this.webhookLogRepo.findOneOrFail({ where: { id: job.data.webhookLogId } });
|
|
81
|
+
this.logger.log(\`Processing \${log.provider} webhook \${log.eventType} (log id: \${log.id})\`);
|
|
82
|
+
try {
|
|
83
|
+
switch (log.eventType) {
|
|
84
|
+
case '${jobName}':
|
|
85
|
+
await this.handle${toPascalCase(eventType)}(log.payload);
|
|
86
|
+
break;
|
|
87
|
+
default:
|
|
88
|
+
this.logger.warn(\`Unhandled event type: \${log.eventType}\`);
|
|
89
|
+
}
|
|
90
|
+
log.processed = true;
|
|
91
|
+
await this.webhookLogRepo.save(log);
|
|
92
|
+
} catch (error) {
|
|
93
|
+
log.retryCount += 1;
|
|
94
|
+
log.processingError = (error as Error).message;
|
|
95
|
+
await this.webhookLogRepo.save(log);
|
|
96
|
+
this.logger.error(\`Webhook \${log.id} failed (attempt \${log.retryCount})\`, error);
|
|
97
|
+
throw error; // Required — Bull needs the error to trigger retries
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
private async handle${toPascalCase(eventType)}(payload: Record<string, unknown>): Promise<void> {
|
|
102
|
+
// TODO: implement handler for '${eventType}'
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ─── Module registration ───
|
|
107
|
+
// Add to your webhooks module:
|
|
108
|
+
// imports: [BullQueueModule, BullModule.registerQueue({ name: 'webhooks' }), TypeOrmModule.forFeature([WebhookLog])]
|
|
109
|
+
// controllers: [${providerPascal}WebhooksController]
|
|
110
|
+
// providers: [${processorClass}]
|
|
111
|
+
`;
|
|
112
|
+
return {
|
|
113
|
+
content: [{ type: 'text', text: snippet }],
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
function toPascalCase(str) {
|
|
118
|
+
return str
|
|
119
|
+
.replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase())
|
|
120
|
+
.replace(/^./, c => c.toUpperCase());
|
|
121
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@launchframe/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "LaunchFrame MCP Server — knowledge tools for AI agents building LaunchFrame projects",
|
|
5
|
+
"bin": { "launchframe-mcp": "dist/index.js" },
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc && cp -r src/content dist/content",
|
|
9
|
+
"start": "node dist/index.js",
|
|
10
|
+
"prepublishOnly": "npm run build"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
14
|
+
"zod": "^3.23.0"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"typescript": "^5.4.0",
|
|
18
|
+
"@types/node": "^22.0.0"
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": { "access": "public" },
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"type": "module"
|
|
23
|
+
}
|