mcp-inflight 0.2.0 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +22 -79
  2. package/dist/index.js +777 -59
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,107 +1,50 @@
1
- # @inflight/mcp-sandbox
1
+ # mcp-inflight
2
2
 
3
- MCP server for sharing local prototypes via public URLs using CodeSandbox.
3
+ MCP server for sharing local prototypes via [InFlight](https://www.inflight.co).
4
4
 
5
5
  ## What it does
6
6
 
7
- This MCP server provides a `share` tool that:
8
-
9
- 1. Takes a local project directory
10
- 2. Uploads it to a CodeSandbox VM
11
- 3. Installs dependencies and starts the dev server
12
- 4. Returns a public URL where the app is running
13
-
14
- Supports React, Next.js, Vite, Node.js, and static HTML projects.
7
+ Share your local projects as live prototypes that stakeholders can interact with and provide feedback on - directly from Claude Code.
15
8
 
16
9
  ## Setup
17
10
 
18
- ### 1. Get a CodeSandbox API Key
19
-
20
- 1. Go to https://codesandbox.io and sign in (or create account)
21
- 2. Navigate to https://codesandbox.io/t/api
22
- 3. Click "Create Token"
23
- 4. Copy the token
24
-
25
- ### 2. Configure Claude Code
26
-
27
- Add to your Claude Code MCP config (`~/.claude/claude_code_config.json`):
28
-
29
- ```json
30
- {
31
- "mcpServers": {
32
- "sandbox": {
33
- "command": "node",
34
- "args": ["/path/to/inflight/packages/mcp-sandbox/dist/index.js"],
35
- "env": {
36
- "CSB_API_KEY": "your-api-key-here"
37
- }
38
- }
39
- }
40
- }
41
- ```
42
-
43
- Or if published to npm:
11
+ Add to your Claude Code config (`~/.claude.json`):
44
12
 
45
13
  ```json
46
14
  {
47
15
  "mcpServers": {
48
- "sandbox": {
16
+ "inflight": {
49
17
  "command": "npx",
50
- "args": ["@inflight/mcp-sandbox"],
51
- "env": {
52
- "CSB_API_KEY": "your-api-key-here"
53
- }
18
+ "args": ["-y", "mcp-inflight"]
54
19
  }
55
20
  }
56
21
  }
57
22
  ```
58
23
 
59
- ## Usage
60
-
61
- Once configured, Claude Code can share prototypes:
62
-
63
- ```text
64
- User: "Share this React app"
24
+ Then restart Claude Code.
65
25
 
66
- Claude: I'll share your project to get a public URL.
26
+ ## Usage
67
27
 
68
- [Calls share tool]
28
+ Use `/share` to share your current project:
69
29
 
70
- Your project is live at: https://abc123-3000.csb.app
71
30
  ```
31
+ User: /share
72
32
 
73
- ## Tool: share
74
-
75
- ### Parameters
76
-
77
- | Parameter | Type | Required | Description |
78
- |-----------|------|----------|-------------|
79
- | `path` | string | Yes | Absolute path to the project directory |
80
- | `port` | number | No | Port the dev server runs on (auto-detected) |
81
- | `command` | string | No | Custom start command (overrides auto-detection) |
82
-
83
- ### Project Type Detection
84
-
85
- The tool automatically detects project types:
33
+ Claude: Sharing your project...
34
+ Your prototype is live at: https://www.inflight.co/v/xyz789
35
+ ```
86
36
 
87
- - **Next.js**: Has `next` in dependencies → port 3000, `npm run dev`
88
- - **Vite**: Has `vite` in dependencies → port 5173, `npm run dev`
89
- - **Create React App**: Has `react-scripts` → port 3000, `npm start`
90
- - **Node.js**: Has `express`/`fastify`/etc → port 3000, `npm start`
91
- - **Static**: Has `index.html` without package.json → port 3000, `npx serve .`
37
+ ## Commands
92
38
 
93
- ## Development
39
+ - `/share` - Share current project as a live prototype
40
+ - `/sandbox-list` - List your deployed prototypes
41
+ - `/sandbox-sync` - Sync changes to an existing prototype
42
+ - `/sandbox-delete` - Delete a prototype
94
43
 
95
- ```bash
96
- # Install dependencies
97
- pnpm install
44
+ ## Supported Projects
98
45
 
99
- # Build
100
- pnpm build
46
+ Next.js, Vite, Create React App, Node.js, and static HTML.
101
47
 
102
- # Type check
103
- pnpm typecheck
48
+ ## License
104
49
 
105
- # Watch mode
106
- pnpm dev
107
- ```
50
+ MIT
package/dist/index.js CHANGED
@@ -11,8 +11,8 @@ import {
11
11
  } from "@modelcontextprotocol/sdk/types.js";
12
12
 
13
13
  // src/tools/deploy.ts
14
- import * as path4 from "path";
15
- import * as fs4 from "fs";
14
+ import * as path5 from "path";
15
+ import * as fs5 from "fs";
16
16
 
17
17
  // src/utils/detect-project.ts
18
18
  import * as fs from "fs";
@@ -46,6 +46,127 @@ function isMonorepo(projectPath, pkg) {
46
46
  (config) => fs.existsSync(path.join(projectPath, config))
47
47
  );
48
48
  }
49
+ function getWorkspacePatterns(projectPath, pkg) {
50
+ if (pkg.workspaces) {
51
+ if (Array.isArray(pkg.workspaces)) {
52
+ return pkg.workspaces;
53
+ }
54
+ if (pkg.workspaces.packages) {
55
+ return pkg.workspaces.packages;
56
+ }
57
+ }
58
+ const pnpmWorkspacePath = path.join(projectPath, "pnpm-workspace.yaml");
59
+ if (fs.existsSync(pnpmWorkspacePath)) {
60
+ try {
61
+ const content = fs.readFileSync(pnpmWorkspacePath, "utf-8");
62
+ const packagesMatch = content.match(/packages:\s*\n((?:\s+-\s+['"]?[^\n]+['"]?\n?)+)/);
63
+ if (packagesMatch) {
64
+ const patterns = packagesMatch[1].split("\n").map((line) => line.trim().replace(/^-\s+['"]?|['"]?$/g, "")).filter(Boolean);
65
+ return patterns;
66
+ }
67
+ } catch {
68
+ }
69
+ }
70
+ return ["apps/*", "packages/*"];
71
+ }
72
+ function expandWorkspacePattern(projectPath, pattern) {
73
+ const results = [];
74
+ if (pattern.endsWith("/*")) {
75
+ const basePath = pattern.slice(0, -2);
76
+ const fullBasePath = path.join(projectPath, basePath);
77
+ if (fs.existsSync(fullBasePath)) {
78
+ try {
79
+ const entries = fs.readdirSync(fullBasePath, { withFileTypes: true });
80
+ for (const entry of entries) {
81
+ if (entry.isDirectory() && !entry.name.startsWith(".")) {
82
+ const pkgPath = path.join(fullBasePath, entry.name, "package.json");
83
+ if (fs.existsSync(pkgPath)) {
84
+ results.push(path.join(basePath, entry.name));
85
+ }
86
+ }
87
+ }
88
+ } catch {
89
+ }
90
+ }
91
+ } else if (!pattern.includes("*")) {
92
+ const fullPath = path.join(projectPath, pattern);
93
+ if (fs.existsSync(path.join(fullPath, "package.json"))) {
94
+ results.push(pattern);
95
+ }
96
+ }
97
+ return results;
98
+ }
99
+ function detectWorkspaceType(workspacePath) {
100
+ const pkgPath = path.join(workspacePath, "package.json");
101
+ if (!fs.existsSync(pkgPath)) {
102
+ return { type: "node", port: 3e3, startCommand: "npm run dev", hasDevScript: false };
103
+ }
104
+ try {
105
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
106
+ const scripts = pkg.scripts ?? {};
107
+ const hasDevScript = !!scripts.dev || !!scripts.start;
108
+ let type = "node";
109
+ let port = 3e3;
110
+ if (hasDependency(pkg, "next")) {
111
+ type = "nextjs";
112
+ port = 3e3;
113
+ } else if (hasDependency(pkg, "vite")) {
114
+ type = "vite";
115
+ port = 5173;
116
+ } else if (hasDependency(pkg, "react-scripts")) {
117
+ type = "cra";
118
+ port = 3e3;
119
+ }
120
+ if (scripts.dev) {
121
+ const extractedPort = extractPortFromScript(scripts.dev);
122
+ if (extractedPort) port = extractedPort;
123
+ }
124
+ const startCommand = scripts.dev ? "dev" : scripts.start ? "start" : "dev";
125
+ return { type, port, startCommand, hasDevScript };
126
+ } catch {
127
+ return { type: "node", port: 3e3, startCommand: "dev", hasDevScript: false };
128
+ }
129
+ }
130
+ function discoverMonorepoWorkspaces(projectPath, pkg) {
131
+ const patterns = getWorkspacePatterns(projectPath, pkg);
132
+ const workspaces = [];
133
+ for (const pattern of patterns) {
134
+ const expandedPaths = expandWorkspacePattern(projectPath, pattern);
135
+ for (const relativePath of expandedPaths) {
136
+ const fullPath = path.join(projectPath, relativePath);
137
+ const info = detectWorkspaceType(fullPath);
138
+ const pkgPath = path.join(fullPath, "package.json");
139
+ let name = path.basename(relativePath);
140
+ try {
141
+ const pkgContent = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
142
+ if (pkgContent.name) {
143
+ name = pkgContent.name;
144
+ }
145
+ } catch {
146
+ }
147
+ workspaces.push({
148
+ name,
149
+ path: relativePath,
150
+ type: info.type,
151
+ port: info.port,
152
+ startCommand: info.startCommand,
153
+ hasDevScript: info.hasDevScript
154
+ });
155
+ }
156
+ }
157
+ workspaces.sort((a, b) => {
158
+ if (a.hasDevScript !== b.hasDevScript) {
159
+ return a.hasDevScript ? -1 : 1;
160
+ }
161
+ const aIsApp = a.path.startsWith("apps/");
162
+ const bIsApp = b.path.startsWith("apps/");
163
+ if (aIsApp !== bIsApp) {
164
+ return aIsApp ? -1 : 1;
165
+ }
166
+ return a.name.localeCompare(b.name);
167
+ });
168
+ return workspaces;
169
+ }
49
170
  function extractPortFromScript(script) {
50
171
  const portPatterns = [
51
172
  /--port[=\s]+(\d+)/,
@@ -301,6 +422,7 @@ function detectProjectType(projectPath) {
301
422
  isMonorepo: false,
302
423
  availableScripts: [],
303
424
  suggestedScripts: [],
425
+ workspaces: [],
304
426
  compatibility: defaultCompatibility,
305
427
  sizing
306
428
  };
@@ -339,6 +461,7 @@ function detectProjectType(projectPath) {
339
461
  type = "node";
340
462
  defaultPort = 3e3;
341
463
  }
464
+ const workspaces = isRepo ? discoverMonorepoWorkspaces(projectPath, packageJson) : [];
342
465
  if (bestScript) {
343
466
  return {
344
467
  type,
@@ -349,6 +472,7 @@ function detectProjectType(projectPath) {
349
472
  isMonorepo: isRepo,
350
473
  availableScripts,
351
474
  suggestedScripts,
475
+ workspaces,
352
476
  compatibility,
353
477
  sizing
354
478
  };
@@ -363,6 +487,7 @@ function detectProjectType(projectPath) {
363
487
  isMonorepo: isRepo,
364
488
  availableScripts,
365
489
  suggestedScripts,
490
+ workspaces,
366
491
  compatibility,
367
492
  sizing
368
493
  };
@@ -419,8 +544,8 @@ function readProjectFiles(projectPath, relativePath = "", includeEnvFiles = fals
419
544
  }
420
545
  return files;
421
546
  }
422
- var MAX_CHUNK_SIZE = 500 * 1024;
423
- var CHUNK_THRESHOLD = 800 * 1024;
547
+ var MAX_CHUNK_SIZE = 2 * 1024 * 1024;
548
+ var CHUNK_THRESHOLD = 3 * 1024 * 1024;
424
549
  function calculateTotalSize(files) {
425
550
  return Object.values(files).reduce((sum, content) => sum + content.length, 0);
426
551
  }
@@ -736,39 +861,59 @@ async function consumeSSEStream(response, onStatus, onComplete, onError) {
736
861
  }
737
862
  const decoder = new TextDecoder();
738
863
  let buffer = "";
739
- while (true) {
740
- const { done, value } = await reader.read();
741
- if (done) break;
742
- buffer += decoder.decode(value, { stream: true });
743
- const lines = buffer.split("\n");
744
- buffer = lines.pop() || "";
745
- let currentEvent = "";
746
- let currentData = "";
747
- for (const line of lines) {
748
- if (line.startsWith("event: ")) {
749
- currentEvent = line.slice(7).trim();
750
- } else if (line.startsWith("data: ")) {
751
- currentData = line.slice(6);
752
- } else if (line === "" && currentEvent && currentData) {
753
- try {
754
- const data = JSON.parse(currentData);
755
- switch (currentEvent) {
756
- case "status":
757
- onStatus(data.message);
758
- break;
759
- case "complete":
760
- onComplete(data);
761
- break;
762
- case "error":
763
- onError(data.message);
764
- break;
864
+ let receivedAnyData = false;
865
+ let lastStatusMessage = "";
866
+ try {
867
+ while (true) {
868
+ const { done, value } = await reader.read();
869
+ if (done) break;
870
+ receivedAnyData = true;
871
+ buffer += decoder.decode(value, { stream: true });
872
+ const lines = buffer.split("\n");
873
+ buffer = lines.pop() || "";
874
+ let currentEvent = "";
875
+ let currentData = "";
876
+ for (const line of lines) {
877
+ if (line.startsWith("event: ")) {
878
+ currentEvent = line.slice(7).trim();
879
+ } else if (line.startsWith("data: ")) {
880
+ currentData = line.slice(6);
881
+ } else if (line === "" && currentEvent && currentData) {
882
+ try {
883
+ const data = JSON.parse(currentData);
884
+ switch (currentEvent) {
885
+ case "status":
886
+ lastStatusMessage = data.message;
887
+ onStatus(data.message);
888
+ break;
889
+ case "complete":
890
+ onComplete(data);
891
+ break;
892
+ case "error":
893
+ onError(data.message || "Server returned an error");
894
+ break;
895
+ }
896
+ } catch (parseError) {
897
+ console.error("Failed to parse SSE data:", currentData, parseError);
898
+ onError(`Failed to parse server response: ${currentData.slice(0, 100)}`);
765
899
  }
766
- } catch {
900
+ currentEvent = "";
901
+ currentData = "";
767
902
  }
768
- currentEvent = "";
769
- currentData = "";
770
903
  }
771
904
  }
905
+ if (!receivedAnyData) {
906
+ onError("Server closed connection without sending any data");
907
+ }
908
+ } catch (streamError) {
909
+ const errorMsg = streamError instanceof Error ? streamError.message : String(streamError);
910
+ if (errorMsg.includes("aborted") || errorMsg.includes("timeout")) {
911
+ onError(`Request timed out. Last status: ${lastStatusMessage || "none"}`);
912
+ } else if (errorMsg.includes("network") || errorMsg.includes("fetch")) {
913
+ onError(`Network error: ${errorMsg}`);
914
+ } else {
915
+ onError(`Stream error: ${errorMsg}. Last status: ${lastStatusMessage || "none"}`);
916
+ }
772
917
  }
773
918
  }
774
919
  async function deployProject(files, options, log) {
@@ -953,8 +1098,17 @@ async function createSandbox(options, log) {
953
1098
  })
954
1099
  });
955
1100
  if (!response.ok) {
956
- const data2 = await response.json().catch(() => ({ error: "Unknown error" }));
957
- throw new APIError(data2.error || "Failed to create sandbox", response.status);
1101
+ const text = await response.text().catch(() => "");
1102
+ let errorMessage = `Failed to create sandbox (HTTP ${response.status})`;
1103
+ try {
1104
+ const data2 = JSON.parse(text);
1105
+ errorMessage = data2.error || data2.message || errorMessage;
1106
+ } catch {
1107
+ if (text) {
1108
+ errorMessage = text.slice(0, 200);
1109
+ }
1110
+ }
1111
+ throw new APIError(errorMessage, response.status);
958
1112
  }
959
1113
  const data = await response.json();
960
1114
  if (!data.success) {
@@ -983,8 +1137,17 @@ async function uploadChunk(sandboxId, files, chunkIndex, totalChunks, log) {
983
1137
  })
984
1138
  });
985
1139
  if (!response.ok) {
986
- const data2 = await response.json().catch(() => ({ error: "Unknown error" }));
987
- throw new APIError(data2.error || `Failed to upload chunk ${chunkIndex}`, response.status);
1140
+ const text = await response.text().catch(() => "");
1141
+ let errorMessage = `Failed to upload chunk ${chunkIndex} (HTTP ${response.status})`;
1142
+ try {
1143
+ const data2 = JSON.parse(text);
1144
+ errorMessage = data2.error || data2.message || errorMessage;
1145
+ } catch {
1146
+ if (text) {
1147
+ errorMessage = text.slice(0, 200);
1148
+ }
1149
+ }
1150
+ throw new APIError(errorMessage, response.status);
988
1151
  }
989
1152
  const data = await response.json();
990
1153
  if (!data.success) {
@@ -1160,15 +1323,504 @@ function autoCommitForShare(projectPath, inflightVersionId) {
1160
1323
  }
1161
1324
  }
1162
1325
 
1326
+ // src/utils/patch-embed.ts
1327
+ function alreadyAllowsInflight(content) {
1328
+ return content.includes("inflight.co") && (content.includes("frame-ancestors") || content.includes("X-Frame-Options"));
1329
+ }
1330
+ function checkNextConfig(content, filePath) {
1331
+ if (alreadyAllowsInflight(content)) {
1332
+ return { canEmbed: true, issues: [], configFile: filePath };
1333
+ }
1334
+ const issues = [];
1335
+ if (content.includes("X-Frame-Options")) {
1336
+ const xfoMatch = content.match(/X-Frame-Options['":\s]+['"]?(DENY|SAMEORIGIN)/i);
1337
+ if (xfoMatch) {
1338
+ issues.push(`X-Frame-Options is set to ${xfoMatch[1]} which blocks embedding`);
1339
+ }
1340
+ }
1341
+ if (content.includes("frame-ancestors")) {
1342
+ const cspMatch = content.match(/frame-ancestors['":\s]+['"]?([^'"}\n]+)/i);
1343
+ if (cspMatch) {
1344
+ const value = cspMatch[1].toLowerCase();
1345
+ if ((value.includes("'none'") || value.includes("'self'")) && !value.includes("inflight")) {
1346
+ issues.push(`CSP frame-ancestors is restrictive: ${cspMatch[1].trim()}`);
1347
+ }
1348
+ }
1349
+ }
1350
+ if (content.includes("async headers()") || content.includes("headers:")) {
1351
+ if (issues.length === 0) {
1352
+ issues.push("Has custom headers configuration - may need to add inflight.co to frame-ancestors");
1353
+ }
1354
+ }
1355
+ return {
1356
+ canEmbed: issues.length === 0,
1357
+ issues,
1358
+ configFile: filePath
1359
+ };
1360
+ }
1361
+ function checkMiddleware(content, filePath) {
1362
+ if (alreadyAllowsInflight(content)) {
1363
+ return { canEmbed: true, issues: [], configFile: filePath };
1364
+ }
1365
+ const issues = [];
1366
+ if (content.includes("X-Frame-Options")) {
1367
+ const xfoMatch = content.match(/['"](X-Frame-Options)['"]\s*,\s*['"]?(DENY|SAMEORIGIN)['"]?/i);
1368
+ if (xfoMatch) {
1369
+ issues.push(`Middleware sets X-Frame-Options to ${xfoMatch[2]} which blocks embedding`);
1370
+ }
1371
+ }
1372
+ if (content.includes("frame-ancestors")) {
1373
+ const cspMatch = content.match(/frame-ancestors\s+([^'"}\n;]+)/i);
1374
+ if (cspMatch) {
1375
+ const value = cspMatch[1].toLowerCase();
1376
+ if ((value.includes("'none'") || value.includes("'self'")) && !value.includes("inflight")) {
1377
+ issues.push(`CSP frame-ancestors is restrictive: ${cspMatch[1].trim()}`);
1378
+ }
1379
+ }
1380
+ }
1381
+ return {
1382
+ canEmbed: issues.length === 0,
1383
+ issues,
1384
+ configFile: filePath
1385
+ };
1386
+ }
1387
+ function checkViteConfig(content, filePath) {
1388
+ if (content.includes(".csb.app") || content.includes(".codesandbox.io")) {
1389
+ return { canEmbed: true, issues: [], configFile: filePath };
1390
+ }
1391
+ const issues = [];
1392
+ if (content.includes("allowedHosts")) {
1393
+ const match = content.match(/allowedHosts\s*:\s*(?:'([^']*)'|"([^"]*)"|(\[[^\]]*\]))/);
1394
+ if (match) {
1395
+ const value = match[1] || match[2] || match[3] || "";
1396
+ if (!value.includes(".csb.app") && !value.includes("codesandbox") && value !== "true") {
1397
+ issues.push(`allowedHosts is restrictive - needs .csb.app and .codesandbox.io`);
1398
+ }
1399
+ }
1400
+ }
1401
+ return {
1402
+ canEmbed: issues.length === 0,
1403
+ issues,
1404
+ configFile: filePath
1405
+ };
1406
+ }
1407
+ function checkHeadersFile(content, filePath) {
1408
+ if (alreadyAllowsInflight(content)) {
1409
+ return { canEmbed: true, issues: [], configFile: filePath };
1410
+ }
1411
+ const issues = [];
1412
+ if (content.includes("X-Frame-Options")) {
1413
+ const xfoMatch = content.match(/X-Frame-Options:\s*(DENY|SAMEORIGIN)/i);
1414
+ if (xfoMatch) {
1415
+ issues.push(`_headers file sets X-Frame-Options: ${xfoMatch[1]} which blocks embedding`);
1416
+ }
1417
+ }
1418
+ if (content.includes("frame-ancestors")) {
1419
+ const cspMatch = content.match(/frame-ancestors\s+([^\n;]+)/i);
1420
+ if (cspMatch) {
1421
+ const value = cspMatch[1].toLowerCase();
1422
+ if ((value.includes("'none'") || value.includes("'self'")) && !value.includes("inflight")) {
1423
+ issues.push(`_headers file has restrictive frame-ancestors: ${cspMatch[1].trim()}`);
1424
+ }
1425
+ }
1426
+ }
1427
+ return {
1428
+ canEmbed: issues.length === 0,
1429
+ issues,
1430
+ configFile: filePath
1431
+ };
1432
+ }
1433
+ function checkVercelJson(content, filePath) {
1434
+ if (alreadyAllowsInflight(content)) {
1435
+ return { canEmbed: true, issues: [], configFile: filePath };
1436
+ }
1437
+ const issues = [];
1438
+ try {
1439
+ const config = JSON.parse(content);
1440
+ if (config.headers) {
1441
+ for (const headerConfig of config.headers) {
1442
+ for (const header of headerConfig.headers || []) {
1443
+ if (header.key?.toLowerCase() === "x-frame-options") {
1444
+ if (header.value?.toUpperCase() === "DENY" || header.value?.toUpperCase() === "SAMEORIGIN") {
1445
+ issues.push(`vercel.json sets X-Frame-Options: ${header.value} which blocks embedding`);
1446
+ }
1447
+ }
1448
+ if (header.key?.toLowerCase() === "content-security-policy" && header.value?.includes("frame-ancestors")) {
1449
+ const value = header.value.toLowerCase();
1450
+ if ((value.includes("'none'") || value.includes("'self'")) && !value.includes("inflight")) {
1451
+ issues.push(`vercel.json has restrictive CSP frame-ancestors`);
1452
+ }
1453
+ }
1454
+ }
1455
+ }
1456
+ }
1457
+ } catch {
1458
+ }
1459
+ return {
1460
+ canEmbed: issues.length === 0,
1461
+ issues,
1462
+ configFile: filePath
1463
+ };
1464
+ }
1465
+ function isMiddlewareFile(filePath) {
1466
+ const fileName = filePath.split("/").pop() || "";
1467
+ if (fileName === "middleware.ts" || fileName === "middleware.js") {
1468
+ return true;
1469
+ }
1470
+ if (filePath.includes("/middleware") && (fileName.endsWith(".ts") || fileName.endsWith(".js"))) {
1471
+ return true;
1472
+ }
1473
+ return false;
1474
+ }
1475
+ var INFLIGHT_FRAME_ANCESTORS = "frame-ancestors 'self' https://*.inflight.co https://inflight.co";
1476
+ function patchSupabaseCookies(content) {
1477
+ if (!content.includes("createServerClient") || !content.includes("setAll")) {
1478
+ return null;
1479
+ }
1480
+ if (content.includes("sameSite") && content.includes("none")) {
1481
+ return null;
1482
+ }
1483
+ const setAllPattern = /(cookiesToSet\.forEach\s*\(\s*\(\s*\{\s*name\s*,\s*value\s*,\s*options\s*\}\s*\)\s*=>\s*)([\w.]+\.cookies\.set\s*\(\s*name\s*,\s*value\s*,\s*options\s*\))/g;
1484
+ if (!setAllPattern.test(content)) {
1485
+ return null;
1486
+ }
1487
+ setAllPattern.lastIndex = 0;
1488
+ const patched = content.replace(setAllPattern, (_match, prefix, setCookieCall) => {
1489
+ const patchedSetCall = setCookieCall.replace(
1490
+ /\.cookies\.set\s*\(\s*name\s*,\s*value\s*,\s*options\s*\)/,
1491
+ ".cookies.set(name, value, { ...options, sameSite: 'none', secure: true })"
1492
+ );
1493
+ return prefix + patchedSetCall;
1494
+ });
1495
+ return patched !== content ? patched : null;
1496
+ }
1497
+ function patchMiddleware(content) {
1498
+ if (alreadyAllowsInflight(content)) {
1499
+ return null;
1500
+ }
1501
+ if (!content.includes("X-Frame-Options")) {
1502
+ return null;
1503
+ }
1504
+ const xfoRegex = /(\s*)([\w.]+\.headers\.set\s*\(\s*['"]X-Frame-Options['"]\s*,\s*['"][^'"]*['"]\s*\)\s*;?)/gi;
1505
+ if (!xfoRegex.test(content)) {
1506
+ return null;
1507
+ }
1508
+ xfoRegex.lastIndex = 0;
1509
+ const patched = content.replace(xfoRegex, (match, indent, xfoLine) => {
1510
+ const varMatch = xfoLine.match(/([\w.]+)\.headers\.set/);
1511
+ const varName = varMatch ? varMatch[1] : "response";
1512
+ const cspLine = `${indent}${varName}.headers.set("Content-Security-Policy", "${INFLIGHT_FRAME_ANCESTORS}");`;
1513
+ return `${match}
1514
+ ${cspLine}`;
1515
+ });
1516
+ return patched !== content ? patched : null;
1517
+ }
1518
+ function patchNextConfig(content) {
1519
+ if (alreadyAllowsInflight(content)) {
1520
+ return null;
1521
+ }
1522
+ if (!content.includes("X-Frame-Options") && !content.includes("frame-ancestors")) {
1523
+ return null;
1524
+ }
1525
+ return null;
1526
+ }
1527
+ function patchHeadersFile(content) {
1528
+ if (alreadyAllowsInflight(content)) {
1529
+ return null;
1530
+ }
1531
+ if (!content.includes("X-Frame-Options") && !content.includes("frame-ancestors")) {
1532
+ return null;
1533
+ }
1534
+ let patched = content;
1535
+ patched = patched.replace(/^\s*X-Frame-Options:\s*(DENY|SAMEORIGIN)\s*$/gim, "");
1536
+ if (patched.includes("/*")) {
1537
+ patched = patched.replace(/(\/\*\s*\n)/, `$1 Content-Security-Policy: ${INFLIGHT_FRAME_ANCESTORS}
1538
+ `);
1539
+ }
1540
+ return patched !== content ? patched : null;
1541
+ }
1542
+ function patchVercelJson(content) {
1543
+ if (alreadyAllowsInflight(content)) {
1544
+ return null;
1545
+ }
1546
+ try {
1547
+ const config = JSON.parse(content);
1548
+ let modified = false;
1549
+ if (config.headers) {
1550
+ for (const headerConfig of config.headers) {
1551
+ if (headerConfig.headers) {
1552
+ headerConfig.headers = headerConfig.headers.filter((h) => {
1553
+ if (h.key?.toLowerCase() === "x-frame-options") {
1554
+ if (h.value?.toUpperCase() === "DENY" || h.value?.toUpperCase() === "SAMEORIGIN") {
1555
+ modified = true;
1556
+ return false;
1557
+ }
1558
+ }
1559
+ return true;
1560
+ });
1561
+ const hasCSP = headerConfig.headers.some(
1562
+ (h) => h.key?.toLowerCase() === "content-security-policy"
1563
+ );
1564
+ if (!hasCSP && modified) {
1565
+ headerConfig.headers.push({
1566
+ key: "Content-Security-Policy",
1567
+ value: INFLIGHT_FRAME_ANCESTORS
1568
+ });
1569
+ }
1570
+ }
1571
+ }
1572
+ }
1573
+ return modified ? JSON.stringify(config, null, 2) : null;
1574
+ } catch {
1575
+ return null;
1576
+ }
1577
+ }
1578
+ function patchFilesForEmbed(files) {
1579
+ const patchedFiles = [];
1580
+ const remainingIssues = [];
1581
+ const result = { ...files };
1582
+ for (const [filePath, content] of Object.entries(files)) {
1583
+ const fileName = filePath.split("/").pop() || "";
1584
+ let patched = null;
1585
+ if (isMiddlewareFile(filePath)) {
1586
+ let middlewareContent = content;
1587
+ let middlewarePatched = false;
1588
+ const xfoPatch = patchMiddleware(middlewareContent);
1589
+ if (xfoPatch) {
1590
+ middlewareContent = xfoPatch;
1591
+ middlewarePatched = true;
1592
+ } else if (middlewareContent.includes("X-Frame-Options") && !alreadyAllowsInflight(middlewareContent)) {
1593
+ remainingIssues.push(`${filePath}: Could not auto-patch X-Frame-Options`);
1594
+ }
1595
+ const cookiePatch = patchSupabaseCookies(middlewareContent);
1596
+ if (cookiePatch) {
1597
+ middlewareContent = cookiePatch;
1598
+ middlewarePatched = true;
1599
+ }
1600
+ if (middlewarePatched) {
1601
+ result[filePath] = middlewareContent;
1602
+ patchedFiles.push(filePath);
1603
+ }
1604
+ }
1605
+ if (fileName === "next.config.js" || fileName === "next.config.mjs" || fileName === "next.config.ts") {
1606
+ patched = patchNextConfig(content);
1607
+ if (patched) {
1608
+ result[filePath] = patched;
1609
+ patchedFiles.push(filePath);
1610
+ } else if ((content.includes("X-Frame-Options") || content.includes("frame-ancestors")) && !alreadyAllowsInflight(content)) {
1611
+ remainingIssues.push(`${filePath}: Has custom headers - may need manual review`);
1612
+ }
1613
+ }
1614
+ if (fileName === "_headers") {
1615
+ patched = patchHeadersFile(content);
1616
+ if (patched) {
1617
+ result[filePath] = patched;
1618
+ patchedFiles.push(filePath);
1619
+ }
1620
+ }
1621
+ if (fileName === "vercel.json") {
1622
+ patched = patchVercelJson(content);
1623
+ if (patched) {
1624
+ result[filePath] = patched;
1625
+ patchedFiles.push(filePath);
1626
+ }
1627
+ }
1628
+ if (filePath.includes("supabase") && fileName === "server.ts") {
1629
+ patched = patchSupabaseCookies(content);
1630
+ if (patched) {
1631
+ result[filePath] = patched;
1632
+ patchedFiles.push(filePath);
1633
+ }
1634
+ }
1635
+ }
1636
+ return {
1637
+ files: result,
1638
+ patchedFiles,
1639
+ remainingIssues
1640
+ };
1641
+ }
1642
+ function checkEmbedCompatibility(files) {
1643
+ const allIssues = [];
1644
+ for (const [filePath, content] of Object.entries(files)) {
1645
+ const fileName = filePath.split("/").pop() || "";
1646
+ if (fileName === "next.config.js" || fileName === "next.config.mjs" || fileName === "next.config.ts") {
1647
+ const result = checkNextConfig(content, filePath);
1648
+ if (!result.canEmbed) {
1649
+ allIssues.push({ file: filePath, issues: result.issues });
1650
+ }
1651
+ }
1652
+ if (isMiddlewareFile(filePath)) {
1653
+ const result = checkMiddleware(content, filePath);
1654
+ if (!result.canEmbed) {
1655
+ allIssues.push({ file: filePath, issues: result.issues });
1656
+ }
1657
+ }
1658
+ if (fileName === "vite.config.js" || fileName === "vite.config.ts") {
1659
+ const result = checkViteConfig(content, filePath);
1660
+ if (!result.canEmbed) {
1661
+ allIssues.push({ file: filePath, issues: result.issues });
1662
+ }
1663
+ }
1664
+ if (fileName === "_headers") {
1665
+ const result = checkHeadersFile(content, filePath);
1666
+ if (!result.canEmbed) {
1667
+ allIssues.push({ file: filePath, issues: result.issues });
1668
+ }
1669
+ }
1670
+ if (fileName === "vercel.json") {
1671
+ const result = checkVercelJson(content, filePath);
1672
+ if (!result.canEmbed) {
1673
+ allIssues.push({ file: filePath, issues: result.issues });
1674
+ }
1675
+ }
1676
+ }
1677
+ if (allIssues.length > 0) {
1678
+ return {
1679
+ canEmbed: false,
1680
+ issues: allIssues[0].issues,
1681
+ configFile: allIssues[0].file
1682
+ };
1683
+ }
1684
+ return { canEmbed: true, issues: [], configFile: null };
1685
+ }
1686
+
1687
+ // src/utils/workspace-bundle.ts
1688
+ import * as fs4 from "fs";
1689
+ import * as path4 from "path";
1690
+ function findMonorepoRoot(startPath) {
1691
+ let current = startPath;
1692
+ const root = path4.parse(current).root;
1693
+ while (current !== root) {
1694
+ if (fs4.existsSync(path4.join(current, "pnpm-workspace.yaml"))) {
1695
+ return current;
1696
+ }
1697
+ const pkgPath = path4.join(current, "package.json");
1698
+ if (fs4.existsSync(pkgPath)) {
1699
+ try {
1700
+ const pkg = JSON.parse(fs4.readFileSync(pkgPath, "utf-8"));
1701
+ if (pkg.workspaces) {
1702
+ return current;
1703
+ }
1704
+ } catch {
1705
+ }
1706
+ }
1707
+ current = path4.dirname(current);
1708
+ }
1709
+ return null;
1710
+ }
1711
+ function hasWorkspaceDependencies(pkgContent) {
1712
+ try {
1713
+ const pkg = JSON.parse(pkgContent);
1714
+ const allDeps = {
1715
+ ...pkg.dependencies,
1716
+ ...pkg.devDependencies
1717
+ };
1718
+ for (const version of Object.values(allDeps)) {
1719
+ if (version.startsWith("workspace:")) {
1720
+ return true;
1721
+ }
1722
+ }
1723
+ } catch {
1724
+ }
1725
+ return false;
1726
+ }
1727
+ function detectMonorepoDeployment(projectPath, packageManager, originalStartCommand) {
1728
+ const pkgPath = path4.join(projectPath, "package.json");
1729
+ if (!fs4.existsSync(pkgPath)) {
1730
+ return {
1731
+ rootPath: projectPath,
1732
+ appPath: null,
1733
+ isMonorepo: false,
1734
+ startCommand: null,
1735
+ installCommand: null
1736
+ };
1737
+ }
1738
+ const pkgContent = fs4.readFileSync(pkgPath, "utf-8");
1739
+ if (!hasWorkspaceDependencies(pkgContent)) {
1740
+ return {
1741
+ rootPath: projectPath,
1742
+ appPath: null,
1743
+ isMonorepo: false,
1744
+ startCommand: null,
1745
+ installCommand: null
1746
+ };
1747
+ }
1748
+ const monorepoRoot = findMonorepoRoot(projectPath);
1749
+ if (!monorepoRoot || monorepoRoot === projectPath) {
1750
+ return {
1751
+ rootPath: projectPath,
1752
+ appPath: null,
1753
+ isMonorepo: false,
1754
+ startCommand: null,
1755
+ installCommand: null
1756
+ };
1757
+ }
1758
+ const appPath = path4.relative(monorepoRoot, projectPath);
1759
+ let packageName = null;
1760
+ try {
1761
+ const pkg = JSON.parse(pkgContent);
1762
+ packageName = pkg.name || null;
1763
+ } catch {
1764
+ }
1765
+ let startCommand;
1766
+ let installCommand;
1767
+ if (packageManager === "pnpm") {
1768
+ const scriptMatch = originalStartCommand.match(/(?:pnpm|npm run|yarn)\s+(\S+)/);
1769
+ const scriptName = scriptMatch ? scriptMatch[1] : "dev";
1770
+ if (packageName) {
1771
+ startCommand = `pnpm --filter "${packageName}" ${scriptName}`;
1772
+ } else {
1773
+ startCommand = `pnpm --filter "./${appPath}" ${scriptName}`;
1774
+ }
1775
+ installCommand = "pnpm install";
1776
+ } else if (packageManager === "yarn") {
1777
+ const scriptMatch = originalStartCommand.match(/(?:yarn|npm run)\s+(\S+)/);
1778
+ const scriptName = scriptMatch ? scriptMatch[1] : "dev";
1779
+ if (packageName) {
1780
+ startCommand = `yarn workspace ${packageName} ${scriptName}`;
1781
+ } else {
1782
+ startCommand = `cd ${appPath} && yarn ${scriptName}`;
1783
+ }
1784
+ installCommand = "yarn install";
1785
+ } else {
1786
+ const scriptMatch = originalStartCommand.match(/(?:npm run|npm)\s+(\S+)/);
1787
+ const scriptName = scriptMatch ? scriptMatch[1] : "dev";
1788
+ if (packageName) {
1789
+ startCommand = `npm run ${scriptName} --workspace=${packageName}`;
1790
+ } else {
1791
+ startCommand = `cd ${appPath} && npm run ${scriptName}`;
1792
+ }
1793
+ installCommand = "npm install";
1794
+ }
1795
+ return {
1796
+ rootPath: monorepoRoot,
1797
+ appPath,
1798
+ isMonorepo: true,
1799
+ startCommand,
1800
+ installCommand
1801
+ };
1802
+ }
1803
+ function readMonorepoFiles(projectPath, packageManager, originalStartCommand, includeEnvFiles = false) {
1804
+ const monorepoResult = detectMonorepoDeployment(projectPath, packageManager, originalStartCommand);
1805
+ const files = readProjectFiles(monorepoResult.rootPath, "", includeEnvFiles);
1806
+ return {
1807
+ files,
1808
+ rootPath: monorepoResult.rootPath,
1809
+ isMonorepo: monorepoResult.isMonorepo,
1810
+ startCommand: monorepoResult.startCommand,
1811
+ installCommand: monorepoResult.installCommand
1812
+ };
1813
+ }
1814
+
1163
1815
  // src/tools/deploy.ts
1164
1816
  async function deployPrototype(args, log) {
1165
1817
  const startTime = Date.now();
1166
1818
  await log(`Starting share for: ${args.path}`);
1167
- const projectPath = path4.resolve(args.path);
1168
- if (!fs4.existsSync(projectPath)) {
1819
+ const projectPath = path5.resolve(args.path);
1820
+ if (!fs5.existsSync(projectPath)) {
1169
1821
  throw new Error(`Project path does not exist: ${projectPath}`);
1170
1822
  }
1171
- if (!fs4.statSync(projectPath).isDirectory()) {
1823
+ if (!fs5.statSync(projectPath).isDirectory()) {
1172
1824
  throw new Error(`Path is not a directory: ${projectPath}`);
1173
1825
  }
1174
1826
  if (!isAuthenticated()) {
@@ -1217,17 +1869,55 @@ async function deployPrototype(args, log) {
1217
1869
  if (includeEnv) {
1218
1870
  await log("Including .env files as requested");
1219
1871
  }
1220
- const files = readProjectFiles(projectPath, "", includeEnv);
1872
+ const monorepoResult = readMonorepoFiles(
1873
+ projectPath,
1874
+ projectInfo.detectedPackageManager,
1875
+ projectInfo.startCommand,
1876
+ includeEnv
1877
+ );
1878
+ let files = monorepoResult.files;
1221
1879
  const fileCount = Object.keys(files).length;
1222
- const totalSize = Object.values(files).reduce((sum, content) => sum + content.length, 0);
1223
- await log(`Found ${fileCount} files (${(totalSize / 1024).toFixed(1)} KB)`);
1224
1880
  if (fileCount === 0) {
1225
1881
  throw new Error("No files found in project directory");
1226
1882
  }
1883
+ if (monorepoResult.isMonorepo) {
1884
+ await log(`Monorepo detected - including full workspace from ${path5.basename(monorepoResult.rootPath)}`);
1885
+ if (monorepoResult.startCommand) {
1886
+ projectInfo.startCommand = monorepoResult.startCommand;
1887
+ await log(`Adjusted start command: ${projectInfo.startCommand}`);
1888
+ }
1889
+ if (monorepoResult.installCommand) {
1890
+ projectInfo.installCommand = monorepoResult.installCommand;
1891
+ await log(`Adjusted install command: ${projectInfo.installCommand}`);
1892
+ }
1893
+ }
1894
+ let embedCheck = checkEmbedCompatibility(files);
1895
+ if (!embedCheck.canEmbed) {
1896
+ await log(`Embedding issues detected in ${embedCheck.configFile}:`, "warning");
1897
+ for (const issue of embedCheck.issues) {
1898
+ await log(` - ${issue}`, "warning");
1899
+ }
1900
+ await log("Patching files to allow InFlight embedding...");
1901
+ const patchResult = patchFilesForEmbed(files);
1902
+ if (patchResult.patchedFiles.length > 0) {
1903
+ files = patchResult.files;
1904
+ for (const patchedFile of patchResult.patchedFiles) {
1905
+ await log(` \u2713 Patched: ${patchedFile}`);
1906
+ }
1907
+ embedCheck = checkEmbedCompatibility(files);
1908
+ }
1909
+ if (patchResult.remainingIssues.length > 0) {
1910
+ for (const issue of patchResult.remainingIssues) {
1911
+ await log(` \u26A0\uFE0F ${issue}`, "warning");
1912
+ }
1913
+ }
1914
+ }
1915
+ const totalSize = Object.values(files).reduce((sum, content) => sum + content.length, 0);
1916
+ await log(`Found ${fileCount} files (${(totalSize / 1024 / 1024).toFixed(1)} MB)`);
1227
1917
  let existingSandbox;
1228
1918
  try {
1229
1919
  const sandboxes = await listSandboxes();
1230
- const projectName = path4.basename(projectPath);
1920
+ const projectName = path5.basename(projectPath);
1231
1921
  existingSandbox = sandboxes.find(
1232
1922
  (s) => s.projectName === projectName && s.status === "running"
1233
1923
  );
@@ -1257,7 +1947,13 @@ async function deployPrototype(args, log) {
1257
1947
  projectType: projectInfo.type,
1258
1948
  synced: true,
1259
1949
  inflightUrl,
1260
- inflightVersionId
1950
+ inflightVersionId,
1951
+ startCommand: projectInfo.startCommand,
1952
+ port: projectInfo.port,
1953
+ packageManager: projectInfo.detectedPackageManager,
1954
+ isMonorepo: projectInfo.isMonorepo,
1955
+ vmTier: projectInfo.sizing.recommendedTier,
1956
+ embedCheck
1261
1957
  };
1262
1958
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
1263
1959
  await log(`Sync complete in ${elapsed}s`);
@@ -1282,7 +1978,7 @@ async function deployPrototype(args, log) {
1282
1978
  startCommand: projectInfo.startCommand,
1283
1979
  port: projectInfo.port,
1284
1980
  vmTier: projectInfo.sizing.recommendedTier,
1285
- projectName: path4.basename(projectPath),
1981
+ projectName: path5.basename(projectPath),
1286
1982
  workspaceId: args.workspaceId,
1287
1983
  gitInfo: gitInfo || void 0
1288
1984
  },
@@ -1294,7 +1990,13 @@ async function deployPrototype(args, log) {
1294
1990
  projectType: projectInfo.type,
1295
1991
  synced: false,
1296
1992
  inflightUrl: deployResult.inflightUrl,
1297
- inflightVersionId: deployResult.versionId
1993
+ inflightVersionId: deployResult.versionId,
1994
+ startCommand: projectInfo.startCommand,
1995
+ port: projectInfo.port,
1996
+ packageManager: projectInfo.detectedPackageManager,
1997
+ isMonorepo: projectInfo.isMonorepo,
1998
+ vmTier: projectInfo.sizing.recommendedTier,
1999
+ embedCheck
1298
2000
  };
1299
2001
  } else {
1300
2002
  await log("Deploying via InFlight API...");
@@ -1306,7 +2008,7 @@ async function deployPrototype(args, log) {
1306
2008
  startCommand: projectInfo.startCommand,
1307
2009
  port: projectInfo.port,
1308
2010
  vmTier: projectInfo.sizing.recommendedTier,
1309
- projectName: path4.basename(projectPath),
2011
+ projectName: path5.basename(projectPath),
1310
2012
  workspaceId: args.workspaceId,
1311
2013
  gitInfo: gitInfo || void 0
1312
2014
  },
@@ -1318,7 +2020,13 @@ async function deployPrototype(args, log) {
1318
2020
  projectType: projectInfo.type,
1319
2021
  synced: false,
1320
2022
  inflightUrl: deployResult.inflightUrl,
1321
- inflightVersionId: deployResult.versionId
2023
+ inflightVersionId: deployResult.versionId,
2024
+ startCommand: projectInfo.startCommand,
2025
+ port: projectInfo.port,
2026
+ packageManager: projectInfo.detectedPackageManager,
2027
+ isMonorepo: projectInfo.isMonorepo,
2028
+ vmTier: projectInfo.sizing.recommendedTier,
2029
+ embedCheck
1322
2030
  };
1323
2031
  }
1324
2032
  const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
@@ -1340,13 +2048,13 @@ async function deployPrototype(args, log) {
1340
2048
  }
1341
2049
 
1342
2050
  // src/server.ts
1343
- import * as path5 from "path";
1344
- import * as fs5 from "fs";
2051
+ import * as path6 from "path";
2052
+ import * as fs6 from "fs";
1345
2053
  function createServer2() {
1346
2054
  const server = new Server(
1347
2055
  {
1348
2056
  name: "mcp-inflight",
1349
- version: "0.2.0"
2057
+ version: "0.2.2"
1350
2058
  },
1351
2059
  {
1352
2060
  capabilities: {
@@ -1370,21 +2078,21 @@ function createServer2() {
1370
2078
  tools: [
1371
2079
  {
1372
2080
  name: "share",
1373
- description: "Share a local project by deploying it to a public URL and creating an InFlight version for feedback. Supports React, Next.js, Vite, Node.js, and static HTML projects. Returns the InFlight URL. Requires InFlight authentication (will prompt if not authenticated).",
2081
+ description: "Share a local project by deploying it to a public URL and creating an InFlight version for feedback. IMPORTANT: Before calling this tool, you MUST check for iframe embedding blockers: 1. Search for middleware.ts files and check for X-Frame-Options headers (DENY or SAMEORIGIN block embedding). 2. Check next.config.js/ts for headers() that set X-Frame-Options or restrictive frame-ancestors. 3. If blocking headers are found, ask the user if you should add a CSP frame-ancestors header to allow inflight.co to embed the prototype. The fix is to add: Content-Security-Policy: frame-ancestors 'self' https://*.inflight.co https://inflight.co 4. For monorepos, determine which app/package to deploy and provide the specific path. 5. Check package.json scripts to find the correct dev command and port. Common patterns: Next.js (port 3000), Vite (port 5173), CRA (port 3000). Returns detected configuration, embed check results, and InFlight URL. Requires InFlight authentication.",
1374
2082
  inputSchema: {
1375
2083
  type: "object",
1376
2084
  properties: {
1377
2085
  path: {
1378
2086
  type: "string",
1379
- description: "Absolute path to the project directory to share"
2087
+ description: "Absolute path to the project directory to share. For monorepos, provide the path to the specific app/package (e.g., '/path/to/monorepo/apps/web')."
1380
2088
  },
1381
2089
  port: {
1382
2090
  type: "number",
1383
- description: "Port the dev server runs on (optional, auto-detected based on project type)"
2091
+ description: "Port the dev server runs on. Auto-detected from package.json scripts, but provide explicitly if known (e.g., from 'next dev -p 3001')."
1384
2092
  },
1385
2093
  command: {
1386
2094
  type: "string",
1387
- description: "Custom start command (optional, overrides auto-detection). Example: 'npm run dev' or 'node server.js'"
2095
+ description: "Start command for the dev server. Auto-detected but provide explicitly for custom setups. Examples: 'npm run dev', 'pnpm dev', 'node server.js'."
1388
2096
  },
1389
2097
  includeEnvFiles: {
1390
2098
  type: "boolean",
@@ -1479,7 +2187,17 @@ function createServer2() {
1479
2187
  text: JSON.stringify(
1480
2188
  {
1481
2189
  success: true,
1482
- projectType: result.projectType,
2190
+ // Detected configuration - communicate this to the user
2191
+ detectedConfig: {
2192
+ projectType: result.projectType,
2193
+ startCommand: result.startCommand,
2194
+ port: result.port,
2195
+ packageManager: result.packageManager,
2196
+ isMonorepo: result.isMonorepo,
2197
+ vmTier: result.vmTier
2198
+ },
2199
+ // Embed check results - shows if there are iframe embedding issues
2200
+ embedCheck: result.embedCheck,
1483
2201
  synced: result.synced ?? false,
1484
2202
  inflightUrl: result.inflightUrl,
1485
2203
  inflightVersionId: result.inflightVersionId,
@@ -1498,8 +2216,8 @@ function createServer2() {
1498
2216
  await log("Authenticating with InFlight...");
1499
2217
  await authenticate(log);
1500
2218
  }
1501
- const resolvedPath = path5.resolve(projectPath);
1502
- if (!fs5.existsSync(resolvedPath)) {
2219
+ const resolvedPath = path6.resolve(projectPath);
2220
+ if (!fs6.existsSync(resolvedPath)) {
1503
2221
  throw new Error(`Project path does not exist: ${resolvedPath}`);
1504
2222
  }
1505
2223
  const files = readProjectFiles(resolvedPath, "", includeEnvFiles ?? false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-inflight",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server for sharing prototypes via InFlight",
5
5
  "author": "InFlight <hello@inflight.co>",
6
6
  "homepage": "https://github.com/inflight/mcp-inflight",