@launchframe/mcp 1.1.0 → 1.1.2

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.
@@ -15,14 +15,14 @@ All routes are protected by default via the global `BetterAuthGuard` (registered
15
15
  |------|-------------|
16
16
  | `business_user` | Default role for all registered users |
17
17
  | `superadmin` | Granted via admin panel; full access |
18
- | `regular_user` | B2B2C variant only — end-customer of the SaaS |
18
+ | `customer` | B2B2C variant only — end-customer of the SaaS |
19
19
 
20
20
  ## Session Flow
21
21
 
22
22
  1. Request hits `BetterAuthGuard`
23
23
  2. Guard checks for `@AllowAnonymous` / `@OptionalAuth` metadata
24
24
  3. Calls `auth.api.getSession({ headers })` via Better Auth
25
- 4. Rejects `regular_user` on non-`@CustomerPortal` routes
25
+ 4. Rejects `customer` on non-`@CustomerPortal` routes
26
26
  5. Attaches `request.session` and `request.user`
27
27
 
28
28
  ## Decorators
@@ -32,7 +32,7 @@ All routes are protected by default via the global `BetterAuthGuard` (registered
32
32
  | `@AllowAnonymous()` | Route is fully public — no auth check |
33
33
  | `@Public()` | Alias for `@AllowAnonymous()` |
34
34
  | `@OptionalAuth()` | Auth checked but not required; `request.user` may be undefined |
35
- | `@CustomerPortal()` | Allows `regular_user` role (B2B2C variant) |
35
+ | `@CustomerPortal()` | Allows `customer` role (B2B2C variant) |
36
36
  | `@UserSession()` | Param decorator — injects the `User` from session |
37
37
  | `@Session()` | Param decorator — injects full `{ user, session }` object |
38
38
 
@@ -20,7 +20,7 @@ Extends Base by adding workspace/project isolation.
20
20
 
21
21
  ### B2B2C
22
22
  Extends Base by adding a separate customer-facing experience (end-users of your customers).
23
- - Adds `regular_user` role
23
+ - Adds `customer` role
24
24
  - Adds `customers-portal` frontend service
25
25
  - Adds `@CustomerPortal()` route decorator for customer-only endpoints
26
26
  - B2B2C can also be combined with multi-tenancy
@@ -28,8 +28,8 @@ import { User } from '../users/user.entity';
28
28
  @OptionalAuth()
29
29
  @Get('route')
30
30
  handler(@UserSession() user?: User) { ... }`,
31
- customer_portal: `// Accessible by regular_user role (B2B2C variant only)
32
- // Without this decorator, regular_user gets 401
31
+ customer_portal: `// Accessible by customer role (B2B2C variant only)
32
+ // Without this decorator, customer gets 401
33
33
  import { CustomerPortal, UserSession } from '../auth/auth.decorator';
34
34
  import { User } from '../users/user.entity';
35
35
 
@@ -81,7 +81,7 @@ handler(@Session() session: { user: any; session: any }) {
81
81
  // Source: src/modules/auth/better-auth.guard.ts
82
82
  // Applied globally in app.module.ts as APP_GUARD.
83
83
  // Allows: business_user, superadmin
84
- // Blocks: unauthenticated, regular_user (unless @CustomerPortal())
84
+ // Blocks: unauthenticated, customer (unless @CustomerPortal())
85
85
  // You never need to add this manually.`,
86
86
  credits: `// CreditsGuard — deducts credits per request based on @DeductCredits(n).
87
87
  // Source: src/modules/credits/guards/credits.guard.ts
package/dist/tools/cli.js CHANGED
@@ -1,5 +1,33 @@
1
1
  import { z } from 'zod';
2
2
  import { execSync } from 'child_process';
3
+ /**
4
+ * Request human confirmation for a destructive action via MCP elicitation.
5
+ * Returns true if the user confirmed, false if declined/cancelled or if the
6
+ * client does not support elicitation (fails safe — aborts rather than proceeds).
7
+ */
8
+ async function confirmDestructive(server, message) {
9
+ try {
10
+ const result = await server.server.elicitInput({
11
+ mode: 'form',
12
+ message,
13
+ requestedSchema: {
14
+ type: 'object',
15
+ properties: {
16
+ confirmed: {
17
+ type: 'boolean',
18
+ title: 'Yes, proceed',
19
+ },
20
+ },
21
+ required: ['confirmed'],
22
+ },
23
+ });
24
+ return result.action === 'accept' && result.content?.confirmed === true;
25
+ }
26
+ catch {
27
+ // Client does not support elicitation — fail safe
28
+ return false;
29
+ }
30
+ }
3
31
  export function registerCliTools(server) {
4
32
  // ─── docker:* ────────────────────────────────────────────────────────────
5
33
  server.tool('cli_docker_up', 'Start Docker services for a LaunchFrame project. Always runs detached (background). Use cli_docker_logs to inspect output afterward.', {
@@ -54,10 +82,14 @@ export function registerCliTools(server) {
54
82
  return { content: [{ type: 'text', text: error.message }] };
55
83
  }
56
84
  });
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.', {
85
+ server.tool('cli_docker_destroy', 'Destroy ALL Docker resources for a LaunchFrame project (containers, volumes, images, network). IRREVERSIBLE — all local data including database volumes will be lost. Will prompt for confirmation before proceeding.', {
58
86
  projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
59
87
  }, async ({ projectPath }) => {
60
88
  try {
89
+ const confirmed = await confirmDestructive(server, '⚠️ This will permanently delete ALL Docker resources for this project: containers, volumes (including the local database), images, and the network. This cannot be undone. Proceed?');
90
+ if (!confirmed) {
91
+ return { content: [{ type: 'text', text: 'Aborted. No Docker resources were removed.' }] };
92
+ }
61
93
  const output = execSync('launchframe docker:destroy --force', { cwd: projectPath, encoding: 'utf8' });
62
94
  return { content: [{ type: 'text', text: output || 'All Docker resources destroyed.' }] };
63
95
  }
@@ -100,12 +132,18 @@ export function registerCliTools(server) {
100
132
  return { content: [{ type: 'text', text: error.message }] };
101
133
  }
102
134
  });
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.', {
135
+ 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. When remote=true, will prompt for confirmation before touching production.', {
104
136
  projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
105
137
  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'),
138
+ remote: z.boolean().optional().describe('If true, query the production database via SSH instead of local. Requires confirmation.'),
107
139
  }, async ({ projectPath, sql, remote }) => {
108
140
  try {
141
+ if (remote) {
142
+ const confirmed = await confirmDestructive(server, `⚠️ This will execute SQL against the PRODUCTION database:\n\n${sql}\n\nProceed?`);
143
+ if (!confirmed) {
144
+ return { content: [{ type: 'text', text: 'Aborted. Query not executed on production.' }] };
145
+ }
146
+ }
109
147
  const remoteFlag = remote ? ' --remote' : '';
110
148
  const output = execSync(`launchframe database:console${remoteFlag} --query ${JSON.stringify(sql)}`, { cwd: projectPath, encoding: 'utf8' });
111
149
  return { content: [{ type: 'text', text: output || '(no output)' }] };
@@ -186,10 +224,14 @@ export function registerCliTools(server) {
186
224
  return { content: [{ type: 'text', text: error.message }] };
187
225
  }
188
226
  });
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.', {
227
+ server.tool('cli_deploy_sync_features', 'Sync subscription plan features from the local database to the production database. DESTRUCTIVE: truncates subscription_plan_features on production. Will prompt for confirmation before proceeding.', {
190
228
  projectPath: z.string().describe('Absolute path to the LaunchFrame project root'),
191
229
  }, async ({ projectPath }) => {
192
230
  try {
231
+ const confirmed = await confirmDestructive(server, '⚠️ This will TRUNCATE subscription_plan_features on the PRODUCTION database and replace with local data. Proceed?');
232
+ if (!confirmed) {
233
+ return { content: [{ type: 'text', text: 'Aborted. No changes made to production.' }] };
234
+ }
193
235
  const output = execSync('launchframe deploy:sync-features --yes', { cwd: projectPath, encoding: 'utf8' });
194
236
  return { content: [{ type: 'text', text: output || 'Features synced to production.' }] };
195
237
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@launchframe/mcp",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "LaunchFrame MCP Server — knowledge tools for AI agents building LaunchFrame projects",
5
5
  "bin": {
6
6
  "launchframe-mcp": "dist/index.js"