@launchframe/mcp 1.0.1 → 1.1.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/server.js CHANGED
@@ -9,7 +9,7 @@ import { registerModuleTools } from './tools/modules.js';
9
9
  import { registerEntityTools } from './tools/entities.js';
10
10
  import { registerEnvTools } from './tools/env.js';
11
11
  import { registerVariantTools } from './tools/variants.js';
12
- // Phase 2: import { registerCliTools } from './tools/cli.js';
12
+ import { registerCliTools } from './tools/cli.js';
13
13
  export function createServer() {
14
14
  const server = new McpServer({ name: 'launchframe-mcp', version: '1.0.0' });
15
15
  registerAuthTools(server);
@@ -22,6 +22,6 @@ export function createServer() {
22
22
  registerEntityTools(server);
23
23
  registerEnvTools(server);
24
24
  registerVariantTools(server);
25
- // Phase 2: registerCliTools(server);
25
+ registerCliTools(server);
26
26
  return server;
27
27
  }
@@ -0,0 +1,2 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function registerCliTools(server: McpServer): void;
@@ -0,0 +1,314 @@
1
+ import { z } from 'zod';
2
+ import { execSync } from 'child_process';
3
+ export function registerCliTools(server) {
4
+ // ─── docker:* ────────────────────────────────────────────────────────────
5
+ server.tool('cli_docker_up', 'Start Docker services for a LaunchFrame project. Always runs detached (background). Use cli_docker_logs to inspect output afterward.', {
6
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
7
+ service: z.string().optional().describe('Specific service to start (e.g., "backend", "admin-portal"). Omit to start all services.'),
8
+ }, async ({ projectPath, service }) => {
9
+ try {
10
+ const svc = service ? ` ${service}` : '';
11
+ const output = execSync(`launchframe docker:up${svc} --detach`, { cwd: projectPath, encoding: 'utf8' });
12
+ return { content: [{ type: 'text', text: output || 'Services started in detached mode.' }] };
13
+ }
14
+ catch (error) {
15
+ return { content: [{ type: 'text', text: error.message }] };
16
+ }
17
+ });
18
+ server.tool('cli_docker_down', 'Stop all running Docker services for a LaunchFrame project.', {
19
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
20
+ }, async ({ projectPath }) => {
21
+ try {
22
+ const output = execSync('launchframe docker:down', { cwd: projectPath, encoding: 'utf8' });
23
+ return { content: [{ type: 'text', text: output || 'Services stopped.' }] };
24
+ }
25
+ catch (error) {
26
+ return { content: [{ type: 'text', text: error.message }] };
27
+ }
28
+ });
29
+ server.tool('cli_docker_build', 'Build Docker images for a LaunchFrame project (all services or a specific one).', {
30
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
31
+ service: z.string().optional().describe('Specific service to build (e.g., "backend"). Omit to build all.'),
32
+ }, async ({ projectPath, service }) => {
33
+ try {
34
+ const svc = service ? ` ${service}` : '';
35
+ const output = execSync(`launchframe docker:build${svc}`, { cwd: projectPath, encoding: 'utf8' });
36
+ return { content: [{ type: 'text', text: output || 'Build complete.' }] };
37
+ }
38
+ catch (error) {
39
+ return { content: [{ type: 'text', text: error.message }] };
40
+ }
41
+ });
42
+ server.tool('cli_docker_logs', 'Fetch a snapshot of Docker service logs (non-streaming). Returns the last N lines.', {
43
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
44
+ service: z.string().optional().describe('Specific service to get logs for (e.g., "backend"). Omit for all services.'),
45
+ tail: z.number().optional().describe('Number of log lines to return (default: 100)'),
46
+ }, async ({ projectPath, service, tail }) => {
47
+ try {
48
+ const svc = service ? ` ${service}` : '';
49
+ const lines = tail || 100;
50
+ const output = execSync(`launchframe docker:logs${svc} --no-follow --tail ${lines}`, { cwd: projectPath, encoding: 'utf8' });
51
+ return { content: [{ type: 'text', text: output || '(no output)' }] };
52
+ }
53
+ catch (error) {
54
+ return { content: [{ type: 'text', text: error.message }] };
55
+ }
56
+ });
57
+ server.tool('cli_docker_destroy', 'Destroy ALL Docker resources for a LaunchFrame project (containers, volumes, images, network). IRREVERSIBLE — all local data will be lost.', {
58
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
59
+ }, async ({ projectPath }) => {
60
+ try {
61
+ const output = execSync('launchframe docker:destroy --force', { cwd: projectPath, encoding: 'utf8' });
62
+ return { content: [{ type: 'text', text: output || 'All Docker resources destroyed.' }] };
63
+ }
64
+ catch (error) {
65
+ return { content: [{ type: 'text', text: error.message }] };
66
+ }
67
+ });
68
+ // ─── migration:* + database:* ────────────────────────────────────────────
69
+ server.tool('cli_migration_run', 'Run all pending TypeORM database migrations against the local database.', {
70
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
71
+ }, async ({ projectPath }) => {
72
+ try {
73
+ const output = execSync('launchframe migration:run', { cwd: projectPath, encoding: 'utf8' });
74
+ return { content: [{ type: 'text', text: output || 'Migrations applied.' }] };
75
+ }
76
+ catch (error) {
77
+ return { content: [{ type: 'text', text: error.message }] };
78
+ }
79
+ });
80
+ server.tool('cli_migration_create', 'Create a new empty TypeORM migration file with the given name.', {
81
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
82
+ name: z.string().describe('Migration name in PascalCase (e.g., "AddStripeCustomerId")'),
83
+ }, async ({ projectPath, name }) => {
84
+ try {
85
+ const output = execSync(`launchframe migration:create ${name}`, { cwd: projectPath, encoding: 'utf8' });
86
+ return { content: [{ type: 'text', text: output || 'Migration file created.' }] };
87
+ }
88
+ catch (error) {
89
+ return { content: [{ type: 'text', text: error.message }] };
90
+ }
91
+ });
92
+ server.tool('cli_migration_revert', 'Revert the most recently applied TypeORM database migration.', {
93
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
94
+ }, async ({ projectPath }) => {
95
+ try {
96
+ const output = execSync('launchframe migration:revert', { cwd: projectPath, encoding: 'utf8' });
97
+ return { content: [{ type: 'text', text: output || 'Migration reverted.' }] };
98
+ }
99
+ catch (error) {
100
+ return { content: [{ type: 'text', text: error.message }] };
101
+ }
102
+ });
103
+ server.tool('cli_database_query', 'Execute a SQL query against the local (or remote) database and return results. Use for SELECT queries, schema inspection, or data checks. Not suitable for long-running interactive sessions.', {
104
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
105
+ sql: z.string().describe('SQL to execute (e.g., "SELECT * FROM users LIMIT 10;")'),
106
+ remote: z.boolean().optional().describe('If true, query the production database via SSH instead of local'),
107
+ }, async ({ projectPath, sql, remote }) => {
108
+ try {
109
+ const remoteFlag = remote ? ' --remote' : '';
110
+ const output = execSync(`launchframe database:console${remoteFlag} --query ${JSON.stringify(sql)}`, { cwd: projectPath, encoding: 'utf8' });
111
+ return { content: [{ type: 'text', text: output || '(no output)' }] };
112
+ }
113
+ catch (error) {
114
+ return { content: [{ type: 'text', text: error.message }] };
115
+ }
116
+ });
117
+ // ─── service:* + module:* ─────────────────────────────────────────────────
118
+ server.tool('cli_service_list', 'List all available optional services that can be added to a LaunchFrame project (e.g., waitlist, docs, customers-portal).', {
119
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
120
+ }, async ({ projectPath }) => {
121
+ try {
122
+ const output = execSync('launchframe service:list', { cwd: projectPath, encoding: 'utf8' });
123
+ return { content: [{ type: 'text', text: output || '(no output)' }] };
124
+ }
125
+ catch (error) {
126
+ return { content: [{ type: 'text', text: error.message }] };
127
+ }
128
+ });
129
+ server.tool('cli_service_add', 'Install an optional service into a LaunchFrame project. Runs non-interactively (skips prompts). Env vars will be empty — configure them manually in infrastructure/.env afterward.', {
130
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
131
+ name: z.string().describe('Service name to install (e.g., "waitlist", "docs", "customers-portal")'),
132
+ }, async ({ projectPath, name }) => {
133
+ try {
134
+ const output = execSync(`launchframe service:add ${name} --yes`, { cwd: projectPath, encoding: 'utf8' });
135
+ return { content: [{ type: 'text', text: output || `Service "${name}" installed.` }] };
136
+ }
137
+ catch (error) {
138
+ return { content: [{ type: 'text', text: error.message }] };
139
+ }
140
+ });
141
+ server.tool('cli_module_list', 'List all available modules that can be added to a LaunchFrame project (e.g., feature-flags, multi-tenancy).', {
142
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
143
+ }, async ({ projectPath }) => {
144
+ try {
145
+ const output = execSync('launchframe module:list', { cwd: projectPath, encoding: 'utf8' });
146
+ return { content: [{ type: 'text', text: output || '(no output)' }] };
147
+ }
148
+ catch (error) {
149
+ return { content: [{ type: 'text', text: error.message }] };
150
+ }
151
+ });
152
+ server.tool('cli_module_add', 'Install a module into a LaunchFrame project. Runs non-interactively (skips confirmation). Rebuilds affected containers and restarts the stack in detached mode.', {
153
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
154
+ name: z.string().describe('Module name to install (e.g., "feature-flags")'),
155
+ }, async ({ projectPath, name }) => {
156
+ try {
157
+ const output = execSync(`launchframe module:add ${name} --yes`, { cwd: projectPath, encoding: 'utf8' });
158
+ return { content: [{ type: 'text', text: output || `Module "${name}" installed.` }] };
159
+ }
160
+ catch (error) {
161
+ return { content: [{ type: 'text', text: error.message }] };
162
+ }
163
+ });
164
+ // ─── deploy:* ────────────────────────────────────────────────────────────
165
+ server.tool('cli_deploy_build', 'Build production Docker images and push to GitHub Container Registry. Optionally build a specific service only.', {
166
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
167
+ service: z.string().optional().describe('Specific service to build and push (e.g., "backend"). Omit to build all.'),
168
+ }, async ({ projectPath, service }) => {
169
+ try {
170
+ const svc = service ? ` ${service}` : '';
171
+ const output = execSync(`launchframe deploy:build${svc}`, { cwd: projectPath, encoding: 'utf8' });
172
+ return { content: [{ type: 'text', text: output || 'Build complete.' }] };
173
+ }
174
+ catch (error) {
175
+ return { content: [{ type: 'text', text: error.message }] };
176
+ }
177
+ });
178
+ server.tool('cli_deploy_up', 'Deploy the latest images to the VPS and restart all production services via SSH.', {
179
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
180
+ }, async ({ projectPath }) => {
181
+ try {
182
+ const output = execSync('launchframe deploy:up', { cwd: projectPath, encoding: 'utf8' });
183
+ return { content: [{ type: 'text', text: output || 'Deployment complete.' }] };
184
+ }
185
+ catch (error) {
186
+ return { content: [{ type: 'text', text: error.message }] };
187
+ }
188
+ });
189
+ server.tool('cli_deploy_sync_features', 'Sync subscription plan features from the local database to the production database. Requires both local and remote Docker stacks to be running.', {
190
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
191
+ }, async ({ projectPath }) => {
192
+ try {
193
+ const output = execSync('launchframe deploy:sync-features --yes', { cwd: projectPath, encoding: 'utf8' });
194
+ return { content: [{ type: 'text', text: output || 'Features synced to production.' }] };
195
+ }
196
+ catch (error) {
197
+ return { content: [{ type: 'text', text: error.message }] };
198
+ }
199
+ });
200
+ // ─── waitlist:* ──────────────────────────────────────────────────────────
201
+ server.tool('cli_waitlist_up', 'Start the waitlist service locally using Docker Compose.', {
202
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
203
+ }, async ({ projectPath }) => {
204
+ try {
205
+ const output = execSync('launchframe waitlist:up', { cwd: projectPath, encoding: 'utf8' });
206
+ return { content: [{ type: 'text', text: output || 'Waitlist service started.' }] };
207
+ }
208
+ catch (error) {
209
+ return { content: [{ type: 'text', text: error.message }] };
210
+ }
211
+ });
212
+ server.tool('cli_waitlist_down', 'Stop the locally running waitlist service.', {
213
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
214
+ }, async ({ projectPath }) => {
215
+ try {
216
+ const output = execSync('launchframe waitlist:down', { cwd: projectPath, encoding: 'utf8' });
217
+ return { content: [{ type: 'text', text: output || 'Waitlist service stopped.' }] };
218
+ }
219
+ catch (error) {
220
+ return { content: [{ type: 'text', text: error.message }] };
221
+ }
222
+ });
223
+ server.tool('cli_waitlist_deploy', 'Build and deploy the waitlist service to the VPS via SSH.', {
224
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
225
+ }, async ({ projectPath }) => {
226
+ try {
227
+ const output = execSync('launchframe waitlist:deploy', { cwd: projectPath, encoding: 'utf8' });
228
+ return { content: [{ type: 'text', text: output || 'Waitlist deployed.' }] };
229
+ }
230
+ catch (error) {
231
+ return { content: [{ type: 'text', text: error.message }] };
232
+ }
233
+ });
234
+ server.tool('cli_waitlist_logs', 'Fetch a snapshot of waitlist service logs from the VPS (non-streaming). Returns the last N lines.', {
235
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
236
+ tail: z.number().optional().describe('Number of log lines to return (default: 100)'),
237
+ }, async ({ projectPath, tail }) => {
238
+ try {
239
+ const lines = tail || 100;
240
+ const output = execSync(`launchframe waitlist:logs --no-follow --tail ${lines}`, { cwd: projectPath, encoding: 'utf8' });
241
+ return { content: [{ type: 'text', text: output || '(no output)' }] };
242
+ }
243
+ catch (error) {
244
+ return { content: [{ type: 'text', text: error.message }] };
245
+ }
246
+ });
247
+ // ─── dev:* ───────────────────────────────────────────────────────────────
248
+ server.tool('cli_dev_add_user', 'Create a random test user in the local database. Generates a unique email + bcrypt password hash and inserts via docker exec. Creates a demo project if the project uses multi-tenancy.', {
249
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
250
+ }, async ({ projectPath }) => {
251
+ try {
252
+ const output = execSync('launchframe dev:add-user', { cwd: projectPath, encoding: 'utf8' });
253
+ return { content: [{ type: 'text', text: output || 'User created.' }] };
254
+ }
255
+ catch (error) {
256
+ return { content: [{ type: 'text', text: error.message }] };
257
+ }
258
+ });
259
+ server.tool('cli_dev_npm_install', 'Run npm install inside a service directory using a node:20-alpine Docker container (matches the build environment). Use this to add packages or update package-lock.json.', {
260
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
261
+ service: z.string().describe('Service directory name (e.g., "backend", "admin-portal", "website")'),
262
+ packages: z.array(z.string()).optional().describe('Package names to install (e.g., ["stripe", "zod"]). Omit to run plain npm install.'),
263
+ }, async ({ projectPath, service, packages }) => {
264
+ try {
265
+ const pkgs = packages && packages.length ? ' ' + packages.join(' ') : '';
266
+ const output = execSync(`launchframe dev:npm-install ${service}${pkgs}`, { cwd: projectPath, encoding: 'utf8' });
267
+ return { content: [{ type: 'text', text: output || 'npm install complete.' }] };
268
+ }
269
+ catch (error) {
270
+ return { content: [{ type: 'text', text: error.message }] };
271
+ }
272
+ });
273
+ server.tool('cli_dev_logo', 'Generate and inject logo/favicon assets across all frontend services from an SVG file. Defaults to <projectRoot>/logo.svg if no path is given.', {
274
+ projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
275
+ svgPath: z.string().optional().describe('Absolute path to the SVG logo file (defaults to <projectRoot>/logo.svg)'),
276
+ }, async ({ projectPath, svgPath }) => {
277
+ try {
278
+ const svg = svgPath ? ` ${svgPath}` : '';
279
+ const output = execSync(`launchframe dev:logo${svg}`, { cwd: projectPath, encoding: 'utf8' });
280
+ return { content: [{ type: 'text', text: output || 'Logo assets generated.' }] };
281
+ }
282
+ catch (error) {
283
+ return { content: [{ type: 'text', text: error.message }] };
284
+ }
285
+ });
286
+ // ─── cache:* ─────────────────────────────────────────────────────────────
287
+ server.tool('cli_cache_info', 'Show information about the local LaunchFrame service cache (location, size, cached services, last update time).', {}, async () => {
288
+ try {
289
+ const output = execSync('launchframe cache:info', { encoding: 'utf8' });
290
+ return { content: [{ type: 'text', text: output || '(no output)' }] };
291
+ }
292
+ catch (error) {
293
+ return { content: [{ type: 'text', text: error.message }] };
294
+ }
295
+ });
296
+ server.tool('cli_cache_update', 'Force-update the local LaunchFrame service cache by re-downloading cached services.', {}, async () => {
297
+ try {
298
+ const output = execSync('launchframe cache:update', { encoding: 'utf8' });
299
+ return { content: [{ type: 'text', text: output || 'Cache updated.' }] };
300
+ }
301
+ catch (error) {
302
+ return { content: [{ type: 'text', text: error.message }] };
303
+ }
304
+ });
305
+ server.tool('cli_cache_clear', 'Delete the entire local LaunchFrame service cache. Services will be re-downloaded on next use.', {}, async () => {
306
+ try {
307
+ const output = execSync('launchframe cache:clear --yes', { encoding: 'utf8' });
308
+ return { content: [{ type: 'text', text: output || 'Cache cleared.' }] };
309
+ }
310
+ catch (error) {
311
+ return { content: [{ type: 'text', text: error.message }] };
312
+ }
313
+ });
314
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchframe/mcp",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "LaunchFrame MCP Server — knowledge tools for AI agents building LaunchFrame projects",
5
5
  "bin": {
6
6
  "launchframe-mcp": "dist/index.js"