mcp-inflight 0.2.3 → 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.
- package/README.md +58 -1
- package/dist/index.js +96 -13
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -49,7 +49,39 @@ The MCP server provides these tools:
|
|
|
49
49
|
|
|
50
50
|
## Supported Projects
|
|
51
51
|
|
|
52
|
-
Next.js
|
|
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.
|
|
53
85
|
|
|
54
86
|
## Slash Commands (Optional)
|
|
55
87
|
|
|
@@ -80,6 +112,31 @@ cp packages/mcp-inflight/commands/*.md ~/.claude/commands/
|
|
|
80
112
|
| `/share` | Share a project to InFlight (shortcut for the share tool) |
|
|
81
113
|
| `/inflight` | Menu for managing prototypes: list, sync, delete, login, logout |
|
|
82
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
|
|
139
|
+
|
|
83
140
|
## License
|
|
84
141
|
|
|
85
142
|
MIT
|
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
|
-
|
|
540
|
-
|
|
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,
|
|
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]
|
|
560
|
-
for (const [filePath,
|
|
561
|
-
const fileSize =
|
|
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]:
|
|
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] =
|
|
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 =
|
|
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 (
|
|
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,
|
|
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,
|
|
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 =
|
|
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 {
|
package/package.json
CHANGED