mcp-inflight 0.2.2 → 0.2.4

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 +101 -9
  2. package/dist/index.js +97 -14
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -25,25 +25,117 @@ Then restart Claude Code.
25
25
 
26
26
  ## Usage
27
27
 
28
- Use `/share` to share your current project:
28
+ Ask Claude to share your project:
29
29
 
30
30
  ```
31
- User: /share
31
+ User: Share this project on InFlight
32
32
 
33
- Claude: Sharing your project...
33
+ Claude: I'll share your project to InFlight...
34
34
  Your prototype is live at: https://www.inflight.co/v/xyz789
35
35
  ```
36
36
 
37
- ## Commands
37
+ ## Tools
38
38
 
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
39
+ The MCP server provides these tools:
40
+
41
+ | Tool | Description |
42
+ |------|-------------|
43
+ | `share` | Share a local project as a live prototype |
44
+ | `prototype_list` | List your deployed prototypes |
45
+ | `prototype_sync` | Sync changes to an existing prototype |
46
+ | `prototype_delete` | Delete a prototype |
47
+ | `login` | Login to InFlight (opens browser) |
48
+ | `logout` | Logout from InFlight |
43
49
 
44
50
  ## Supported Projects
45
51
 
46
- Next.js, Vite, Create React App, Node.js, and static HTML.
52
+ - **Next.js** (App Router and Pages Router)
53
+ - **Vite** (React, Vue, Svelte, etc.)
54
+ - **Create React App**
55
+ - **Node.js**
56
+ - **Static HTML**
57
+
58
+ ## Features
59
+
60
+ ### Binary File Support
61
+
62
+ The MCP server automatically handles binary files including:
63
+
64
+ - **Images**: PNG, JPG, JPEG, GIF, WebP, ICO, SVG, BMP, TIFF, AVIF
65
+ - **Fonts**: WOFF, WOFF2, TTF, EOT, OTF
66
+ - **Media**: MP3, MP4, WebM, OGG, WAV
67
+ - **Other**: PDF, ZIP
68
+
69
+ Binary files are base64 encoded during upload and properly decoded in the sandbox, ensuring your `/public` folder assets work correctly.
70
+
71
+ ### Monorepo Support
72
+
73
+ Automatically detects and bundles workspace dependencies for monorepo projects using pnpm, npm, or yarn workspaces.
74
+
75
+ ### Automatic Patching
76
+
77
+ The server automatically patches configuration files for CodeSandbox compatibility:
78
+
79
+ - **Vite**: Adds `allowedHosts` for CodeSandbox domains
80
+ - **Next.js**: Configures CSP headers to allow iframe embedding
81
+
82
+ ### Large Project Support
83
+
84
+ Projects over 3MB are automatically chunked into smaller uploads to handle Vercel's serverless function body size limits.
85
+
86
+ ## Slash Commands (Optional)
87
+
88
+ For a better experience, install the included slash commands. These provide `/share` and `/inflight` commands in Claude Code.
89
+
90
+ ### Installation
91
+
92
+ Copy the command files to your Claude Code commands directory:
93
+
94
+ ```bash
95
+ # Create the commands directory if it doesn't exist
96
+ mkdir -p ~/.claude/commands
97
+
98
+ # Copy the commands (run from the mcp-inflight package directory, or adjust the path)
99
+ cp node_modules/mcp-inflight/commands/*.md ~/.claude/commands/
100
+ ```
101
+
102
+ Or if you cloned the repo:
103
+
104
+ ```bash
105
+ cp packages/mcp-inflight/commands/*.md ~/.claude/commands/
106
+ ```
107
+
108
+ ### Available Commands
109
+
110
+ | Command | Description |
111
+ |---------|-------------|
112
+ | `/share` | Share a project to InFlight (shortcut for the share tool) |
113
+ | `/inflight` | Menu for managing prototypes: list, sync, delete, login, logout |
114
+
115
+ ## Troubleshooting
116
+
117
+ ### Server Startup Timeout
118
+
119
+ If you see "Timeout: Waiting for server to start took longer than 120s", this usually means:
120
+
121
+ 1. The project is large and takes a while to start
122
+ 2. There's an error in the dev server startup
123
+
124
+ The deployment logs will show the exact command being used. Try running it locally to verify it works.
125
+
126
+ ### Missing Images or Assets
127
+
128
+ If images from your `/public` folder aren't loading, ensure:
129
+
130
+ 1. The file extension is in the supported binary file list
131
+ 2. The file path is correct (paths are relative to project root)
132
+
133
+ ### Authentication Issues
134
+
135
+ If you're getting authentication errors:
136
+
137
+ 1. Run the `login` tool to re-authenticate
138
+ 2. Check that your InFlight account is active
47
139
 
48
140
  ## License
49
141
 
package/dist/index.js CHANGED
@@ -496,6 +496,52 @@ function detectProjectType(projectPath) {
496
496
  // src/utils/file-utils.ts
497
497
  import * as fs2 from "fs";
498
498
  import * as path2 from "path";
499
+ var BINARY_EXTENSIONS = [
500
+ // Images
501
+ ".png",
502
+ ".jpg",
503
+ ".jpeg",
504
+ ".gif",
505
+ ".webp",
506
+ ".ico",
507
+ ".svg",
508
+ ".bmp",
509
+ ".tiff",
510
+ ".avif",
511
+ // Fonts
512
+ ".woff",
513
+ ".woff2",
514
+ ".ttf",
515
+ ".eot",
516
+ ".otf",
517
+ // Other binary
518
+ ".pdf",
519
+ ".zip",
520
+ ".mp3",
521
+ ".mp4",
522
+ ".webm",
523
+ ".ogg",
524
+ ".wav"
525
+ ];
526
+ function isBinaryFile(filename) {
527
+ const ext = path2.extname(filename).toLowerCase();
528
+ return BINARY_EXTENSIONS.includes(ext);
529
+ }
530
+ function getFileContent(file) {
531
+ return typeof file === "string" ? file : file.content;
532
+ }
533
+ function getFileSize(file) {
534
+ return getFileContent(file).length;
535
+ }
536
+ function getTextContent(file) {
537
+ if (typeof file === "string") {
538
+ return file;
539
+ }
540
+ if (file.encoding === "utf-8") {
541
+ return file.content;
542
+ }
543
+ return void 0;
544
+ }
499
545
  var ALWAYS_IGNORE = [
500
546
  "node_modules",
501
547
  ".git",
@@ -536,8 +582,15 @@ function readProjectFiles(projectPath, relativePath = "", includeEnvFiles = fals
536
582
  } else if (entry.isFile()) {
537
583
  const filePath = path2.join(projectPath, entryRelativePath);
538
584
  try {
539
- const content = fs2.readFileSync(filePath, "utf-8");
540
- files[entryRelativePath] = content;
585
+ if (isBinaryFile(entry.name)) {
586
+ const buffer = fs2.readFileSync(filePath);
587
+ const content = buffer.toString("base64");
588
+ console.log(`[MCP] Read binary file: ${entryRelativePath}, original size=${buffer.length}, base64 length=${content.length}`);
589
+ files[entryRelativePath] = { content, encoding: "base64" };
590
+ } else {
591
+ const content = fs2.readFileSync(filePath, "utf-8");
592
+ files[entryRelativePath] = content;
593
+ }
541
594
  } catch {
542
595
  }
543
596
  }
@@ -547,7 +600,7 @@ function readProjectFiles(projectPath, relativePath = "", includeEnvFiles = fals
547
600
  var MAX_CHUNK_SIZE = 2 * 1024 * 1024;
548
601
  var CHUNK_THRESHOLD = 3 * 1024 * 1024;
549
602
  function calculateTotalSize(files) {
550
- return Object.values(files).reduce((sum, content) => sum + content.length, 0);
603
+ return Object.values(files).reduce((sum, file) => sum + getFileSize(file), 0);
551
604
  }
552
605
  function needsChunkedUpload(files) {
553
606
  return calculateTotalSize(files) > CHUNK_THRESHOLD;
@@ -556,16 +609,16 @@ function chunkFiles(files, maxChunkSize = MAX_CHUNK_SIZE) {
556
609
  const chunks = [];
557
610
  let currentChunk = {};
558
611
  let currentSize = 0;
559
- const entries = Object.entries(files).sort((a, b) => a[1].length - b[1].length);
560
- for (const [filePath, content] of entries) {
561
- const fileSize = content.length;
612
+ const entries = Object.entries(files).sort((a, b) => getFileSize(a[1]) - getFileSize(b[1]));
613
+ for (const [filePath, file] of entries) {
614
+ const fileSize = getFileSize(file);
562
615
  if (fileSize > maxChunkSize) {
563
616
  if (Object.keys(currentChunk).length > 0) {
564
617
  chunks.push(currentChunk);
565
618
  currentChunk = {};
566
619
  currentSize = 0;
567
620
  }
568
- chunks.push({ [filePath]: content });
621
+ chunks.push({ [filePath]: file });
569
622
  continue;
570
623
  }
571
624
  if (currentSize + fileSize > maxChunkSize && Object.keys(currentChunk).length > 0) {
@@ -573,7 +626,7 @@ function chunkFiles(files, maxChunkSize = MAX_CHUNK_SIZE) {
573
626
  currentChunk = {};
574
627
  currentSize = 0;
575
628
  }
576
- currentChunk[filePath] = content;
629
+ currentChunk[filePath] = file;
577
630
  currentSize += fileSize;
578
631
  }
579
632
  if (Object.keys(currentChunk).length > 0) {
@@ -1125,7 +1178,7 @@ async function uploadChunk(sandboxId, files, chunkIndex, totalChunks, log) {
1125
1178
  throw new NotAuthenticatedError();
1126
1179
  }
1127
1180
  const fileCount = Object.keys(files).length;
1128
- const chunkSize = Object.values(files).reduce((sum, content) => sum + content.length, 0);
1181
+ const chunkSize = calculateTotalSize(files);
1129
1182
  await log(`Uploading chunk ${chunkIndex + 1}/${totalChunks} (${fileCount} files, ${(chunkSize / 1024).toFixed(1)} KB)...`);
1130
1183
  const response = await fetch(`${INFLIGHT_API}/api/mcp/sandbox/${sandboxId}/upload`, {
1131
1184
  method: "POST",
@@ -1384,8 +1437,30 @@ function checkMiddleware(content, filePath) {
1384
1437
  configFile: filePath
1385
1438
  };
1386
1439
  }
1440
+ function configAllowsCodeSandbox(content) {
1441
+ const literalRegex = /['"`](https?:\/\/[^\s'"`]+|[^'"`\s]+)['"`]/g;
1442
+ let match;
1443
+ while ((match = literalRegex.exec(content)) !== null) {
1444
+ const literal = match[1];
1445
+ let hostname = "";
1446
+ try {
1447
+ if (/^https?:\/\//i.test(literal)) {
1448
+ const url = new URL(literal);
1449
+ hostname = url.hostname.toLowerCase();
1450
+ } else {
1451
+ hostname = literal.toLowerCase();
1452
+ }
1453
+ } catch {
1454
+ hostname = literal.toLowerCase();
1455
+ }
1456
+ if (hostname === "csb.app" || hostname === "codesandbox.io" || hostname.endsWith(".csb.app") || hostname.endsWith(".codesandbox.io")) {
1457
+ return true;
1458
+ }
1459
+ }
1460
+ return false;
1461
+ }
1387
1462
  function checkViteConfig(content, filePath) {
1388
- if (content.includes(".csb.app") || content.includes(".codesandbox.io")) {
1463
+ if (configAllowsCodeSandbox(content)) {
1389
1464
  return { canEmbed: true, issues: [], configFile: filePath };
1390
1465
  }
1391
1466
  const issues = [];
@@ -1579,7 +1654,11 @@ function patchFilesForEmbed(files) {
1579
1654
  const patchedFiles = [];
1580
1655
  const remainingIssues = [];
1581
1656
  const result = { ...files };
1582
- for (const [filePath, content] of Object.entries(files)) {
1657
+ for (const [filePath, file] of Object.entries(files)) {
1658
+ const content = getTextContent(file);
1659
+ if (content === void 0) {
1660
+ continue;
1661
+ }
1583
1662
  const fileName = filePath.split("/").pop() || "";
1584
1663
  let patched = null;
1585
1664
  if (isMiddlewareFile(filePath)) {
@@ -1641,7 +1720,11 @@ function patchFilesForEmbed(files) {
1641
1720
  }
1642
1721
  function checkEmbedCompatibility(files) {
1643
1722
  const allIssues = [];
1644
- for (const [filePath, content] of Object.entries(files)) {
1723
+ for (const [filePath, file] of Object.entries(files)) {
1724
+ const content = getTextContent(file);
1725
+ if (content === void 0) {
1726
+ continue;
1727
+ }
1645
1728
  const fileName = filePath.split("/").pop() || "";
1646
1729
  if (fileName === "next.config.js" || fileName === "next.config.mjs" || fileName === "next.config.ts") {
1647
1730
  const result = checkNextConfig(content, filePath);
@@ -1912,7 +1995,7 @@ async function deployPrototype(args, log) {
1912
1995
  }
1913
1996
  }
1914
1997
  }
1915
- const totalSize = Object.values(files).reduce((sum, content) => sum + content.length, 0);
1998
+ const totalSize = calculateTotalSize(files);
1916
1999
  await log(`Found ${fileCount} files (${(totalSize / 1024 / 1024).toFixed(1)} MB)`);
1917
2000
  let existingSandbox;
1918
2001
  try {
@@ -2054,7 +2137,7 @@ function createServer2() {
2054
2137
  const server = new Server(
2055
2138
  {
2056
2139
  name: "mcp-inflight",
2057
- version: "0.2.2"
2140
+ version: "0.2.3"
2058
2141
  },
2059
2142
  {
2060
2143
  capabilities: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-inflight",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
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",