mcp-inflight 0.2.3 → 0.3.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.
Files changed (3) hide show
  1. package/README.md +58 -1
  2. package/dist/index.js +185 -126
  3. package/package.json +8 -8
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, 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.
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
- 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) {
@@ -933,7 +986,8 @@ async function deployProject(files, options, log) {
933
986
  vmTier: options.vmTier || "Micro",
934
987
  projectName: options.projectName,
935
988
  workspaceId: options.workspaceId || auth.defaultWorkspaceId,
936
- gitInfo: options.gitInfo
989
+ gitInfo: options.gitInfo,
990
+ diffSummary: options.diffSummary
937
991
  })
938
992
  });
939
993
  if (!response.ok && !response.headers.get("content-type")?.includes("text/event-stream")) {
@@ -1125,7 +1179,7 @@ async function uploadChunk(sandboxId, files, chunkIndex, totalChunks, log) {
1125
1179
  throw new NotAuthenticatedError();
1126
1180
  }
1127
1181
  const fileCount = Object.keys(files).length;
1128
- const chunkSize = Object.values(files).reduce((sum, content) => sum + content.length, 0);
1182
+ const chunkSize = calculateTotalSize(files);
1129
1183
  await log(`Uploading chunk ${chunkIndex + 1}/${totalChunks} (${fileCount} files, ${(chunkSize / 1024).toFixed(1)} KB)...`);
1130
1184
  const response = await fetch(`${INFLIGHT_API}/api/mcp/sandbox/${sandboxId}/upload`, {
1131
1185
  method: "POST",
@@ -1174,7 +1228,8 @@ async function finalizeSandbox(sandboxId, options, log) {
1174
1228
  port: options.port,
1175
1229
  projectName: options.projectName,
1176
1230
  workspaceId: options.workspaceId,
1177
- gitInfo: options.gitInfo
1231
+ gitInfo: options.gitInfo,
1232
+ diffSummary: options.diffSummary
1178
1233
  })
1179
1234
  });
1180
1235
  if (!response.ok && !response.headers.get("content-type")?.includes("text/event-stream")) {
@@ -1231,7 +1286,8 @@ async function deployProjectChunked(fileChunks, options, log) {
1231
1286
  port: options.port,
1232
1287
  projectName: options.projectName,
1233
1288
  workspaceId,
1234
- gitInfo: options.gitInfo
1289
+ gitInfo: options.gitInfo,
1290
+ diffSummary: options.diffSummary
1235
1291
  },
1236
1292
  log
1237
1293
  );
@@ -1384,8 +1440,30 @@ function checkMiddleware(content, filePath) {
1384
1440
  configFile: filePath
1385
1441
  };
1386
1442
  }
1443
+ function configAllowsCodeSandbox(content) {
1444
+ const literalRegex = /['"`](https?:\/\/[^\s'"`]+|[^'"`\s]+)['"`]/g;
1445
+ let match;
1446
+ while ((match = literalRegex.exec(content)) !== null) {
1447
+ const literal = match[1];
1448
+ let hostname = "";
1449
+ try {
1450
+ if (/^https?:\/\//i.test(literal)) {
1451
+ const url = new URL(literal);
1452
+ hostname = url.hostname.toLowerCase();
1453
+ } else {
1454
+ hostname = literal.toLowerCase();
1455
+ }
1456
+ } catch {
1457
+ hostname = literal.toLowerCase();
1458
+ }
1459
+ if (hostname === "csb.app" || hostname === "codesandbox.io" || hostname.endsWith(".csb.app") || hostname.endsWith(".codesandbox.io")) {
1460
+ return true;
1461
+ }
1462
+ }
1463
+ return false;
1464
+ }
1387
1465
  function checkViteConfig(content, filePath) {
1388
- if (content.includes(".csb.app") || content.includes(".codesandbox.io")) {
1466
+ if (configAllowsCodeSandbox(content)) {
1389
1467
  return { canEmbed: true, issues: [], configFile: filePath };
1390
1468
  }
1391
1469
  const issues = [];
@@ -1579,7 +1657,11 @@ function patchFilesForEmbed(files) {
1579
1657
  const patchedFiles = [];
1580
1658
  const remainingIssues = [];
1581
1659
  const result = { ...files };
1582
- for (const [filePath, content] of Object.entries(files)) {
1660
+ for (const [filePath, file] of Object.entries(files)) {
1661
+ const content = getTextContent(file);
1662
+ if (content === void 0) {
1663
+ continue;
1664
+ }
1583
1665
  const fileName = filePath.split("/").pop() || "";
1584
1666
  let patched = null;
1585
1667
  if (isMiddlewareFile(filePath)) {
@@ -1641,7 +1723,11 @@ function patchFilesForEmbed(files) {
1641
1723
  }
1642
1724
  function checkEmbedCompatibility(files) {
1643
1725
  const allIssues = [];
1644
- for (const [filePath, content] of Object.entries(files)) {
1726
+ for (const [filePath, file] of Object.entries(files)) {
1727
+ const content = getTextContent(file);
1728
+ if (content === void 0) {
1729
+ continue;
1730
+ }
1645
1731
  const fileName = filePath.split("/").pop() || "";
1646
1732
  if (fileName === "next.config.js" || fileName === "next.config.mjs" || fileName === "next.config.ts") {
1647
1733
  const result = checkNextConfig(content, filePath);
@@ -1843,6 +1929,9 @@ async function deployPrototype(args, log) {
1843
1929
  const dirtyMarker = gitInfo.isDirty ? " (modified)" : "";
1844
1930
  await log(`Git: ${gitInfo.branch}@${gitInfo.commitShort}${dirtyMarker}`);
1845
1931
  }
1932
+ if (args.diffSummary) {
1933
+ await log(`Diff summary provided: ${args.diffSummary.keyChanges.length} key changes`);
1934
+ }
1846
1935
  if (!projectInfo.compatibility.canDeploy) {
1847
1936
  throw new Error(
1848
1937
  `Project cannot be deployed: ${projectInfo.compatibility.issues.join(", ")}`
@@ -1912,126 +2001,76 @@ async function deployPrototype(args, log) {
1912
2001
  }
1913
2002
  }
1914
2003
  }
1915
- const totalSize = Object.values(files).reduce((sum, content) => sum + content.length, 0);
2004
+ const totalSize = calculateTotalSize(files);
1916
2005
  await log(`Found ${fileCount} files (${(totalSize / 1024 / 1024).toFixed(1)} MB)`);
1917
- let existingSandbox;
1918
- try {
1919
- const sandboxes = await listSandboxes();
1920
- const projectName = path5.basename(projectPath);
1921
- existingSandbox = sandboxes.find(
1922
- (s) => s.projectName === projectName && s.status === "running"
1923
- );
1924
- } catch {
1925
- }
1926
2006
  let result;
1927
- if (existingSandbox) {
1928
- await log(`Found existing sandbox: ${existingSandbox.sandboxId}`);
1929
- await log("Syncing files...");
1930
- try {
1931
- const syncResult = await syncProject(
1932
- existingSandbox.sandboxId,
1933
- files,
1934
- {
1935
- restartServer: true,
1936
- startCommand: projectInfo.startCommand,
1937
- port: projectInfo.port,
1938
- gitInfo: gitInfo || void 0
1939
- },
1940
- log
1941
- );
1942
- const inflightUrl = existingSandbox.inflightUrl || "";
1943
- const inflightVersionId = existingSandbox.inflightVersionId || "";
1944
- result = {
1945
- url: syncResult.sandboxUrl,
1946
- sandboxId: existingSandbox.sandboxId,
1947
- projectType: projectInfo.type,
1948
- synced: true,
1949
- inflightUrl,
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
1957
- };
1958
- const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
1959
- await log(`Sync complete in ${elapsed}s`);
1960
- } catch (syncError) {
1961
- const errorMsg = syncError instanceof Error ? syncError.message : String(syncError);
1962
- await log(`Sync failed, creating new sandbox: ${errorMsg}`, "warning");
1963
- existingSandbox = void 0;
1964
- }
1965
- }
1966
- if (!existingSandbox) {
1967
- const useChunkedUpload = needsChunkedUpload(files);
1968
- if (useChunkedUpload) {
1969
- const totalSize2 = calculateTotalSize(files);
1970
- await log(`Large project detected (${(totalSize2 / 1024 / 1024).toFixed(1)} MB), using chunked upload...`);
1971
- const fileChunks = chunkFiles(files);
1972
- await log(`Split into ${fileChunks.length} chunks`);
1973
- const deployResult = await deployProjectChunked(
1974
- fileChunks,
1975
- {
1976
- projectType: projectInfo.type,
1977
- installCommand: projectInfo.installCommand || void 0,
1978
- startCommand: projectInfo.startCommand,
1979
- port: projectInfo.port,
1980
- vmTier: projectInfo.sizing.recommendedTier,
1981
- projectName: path5.basename(projectPath),
1982
- workspaceId: args.workspaceId,
1983
- gitInfo: gitInfo || void 0
1984
- },
1985
- log
1986
- );
1987
- result = {
1988
- url: deployResult.sandboxUrl,
1989
- sandboxId: deployResult.sandboxId,
2007
+ const useChunkedUpload = needsChunkedUpload(files);
2008
+ if (useChunkedUpload) {
2009
+ const totalSize2 = calculateTotalSize(files);
2010
+ await log(`Large project detected (${(totalSize2 / 1024 / 1024).toFixed(1)} MB), using chunked upload...`);
2011
+ const fileChunks = chunkFiles(files);
2012
+ await log(`Split into ${fileChunks.length} chunks`);
2013
+ const deployResult = await deployProjectChunked(
2014
+ fileChunks,
2015
+ {
1990
2016
  projectType: projectInfo.type,
1991
- synced: false,
1992
- inflightUrl: deployResult.inflightUrl,
1993
- inflightVersionId: deployResult.versionId,
2017
+ installCommand: projectInfo.installCommand || void 0,
1994
2018
  startCommand: projectInfo.startCommand,
1995
2019
  port: projectInfo.port,
1996
- packageManager: projectInfo.detectedPackageManager,
1997
- isMonorepo: projectInfo.isMonorepo,
1998
2020
  vmTier: projectInfo.sizing.recommendedTier,
1999
- embedCheck
2000
- };
2001
- } else {
2002
- await log("Deploying via InFlight API...");
2003
- const deployResult = await deployProject(
2004
- files,
2005
- {
2006
- projectType: projectInfo.type,
2007
- installCommand: projectInfo.installCommand || void 0,
2008
- startCommand: projectInfo.startCommand,
2009
- port: projectInfo.port,
2010
- vmTier: projectInfo.sizing.recommendedTier,
2011
- projectName: path5.basename(projectPath),
2012
- workspaceId: args.workspaceId,
2013
- gitInfo: gitInfo || void 0
2014
- },
2015
- log
2016
- );
2017
- result = {
2018
- url: deployResult.sandboxUrl,
2019
- sandboxId: deployResult.sandboxId,
2021
+ projectName: path5.basename(projectPath),
2022
+ workspaceId: args.workspaceId,
2023
+ gitInfo: gitInfo || void 0,
2024
+ diffSummary: args.diffSummary || void 0
2025
+ },
2026
+ log
2027
+ );
2028
+ result = {
2029
+ url: deployResult.sandboxUrl,
2030
+ sandboxId: deployResult.sandboxId,
2031
+ projectType: projectInfo.type,
2032
+ inflightUrl: deployResult.inflightUrl,
2033
+ inflightVersionId: deployResult.versionId,
2034
+ startCommand: projectInfo.startCommand,
2035
+ port: projectInfo.port,
2036
+ packageManager: projectInfo.detectedPackageManager,
2037
+ isMonorepo: projectInfo.isMonorepo,
2038
+ vmTier: projectInfo.sizing.recommendedTier,
2039
+ embedCheck
2040
+ };
2041
+ } else {
2042
+ await log("Deploying via InFlight API...");
2043
+ const deployResult = await deployProject(
2044
+ files,
2045
+ {
2020
2046
  projectType: projectInfo.type,
2021
- synced: false,
2022
- inflightUrl: deployResult.inflightUrl,
2023
- inflightVersionId: deployResult.versionId,
2047
+ installCommand: projectInfo.installCommand || void 0,
2024
2048
  startCommand: projectInfo.startCommand,
2025
2049
  port: projectInfo.port,
2026
- packageManager: projectInfo.detectedPackageManager,
2027
- isMonorepo: projectInfo.isMonorepo,
2028
2050
  vmTier: projectInfo.sizing.recommendedTier,
2029
- embedCheck
2030
- };
2031
- }
2032
- const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
2033
- await log(`Deploy complete in ${elapsed}s`);
2051
+ projectName: path5.basename(projectPath),
2052
+ workspaceId: args.workspaceId,
2053
+ gitInfo: gitInfo || void 0,
2054
+ diffSummary: args.diffSummary || void 0
2055
+ },
2056
+ log
2057
+ );
2058
+ result = {
2059
+ url: deployResult.sandboxUrl,
2060
+ sandboxId: deployResult.sandboxId,
2061
+ projectType: projectInfo.type,
2062
+ inflightUrl: deployResult.inflightUrl,
2063
+ inflightVersionId: deployResult.versionId,
2064
+ startCommand: projectInfo.startCommand,
2065
+ port: projectInfo.port,
2066
+ packageManager: projectInfo.detectedPackageManager,
2067
+ isMonorepo: projectInfo.isMonorepo,
2068
+ vmTier: projectInfo.sizing.recommendedTier,
2069
+ embedCheck
2070
+ };
2034
2071
  }
2072
+ const elapsed = ((Date.now() - startTime) / 1e3).toFixed(1);
2073
+ await log(`Deploy complete in ${elapsed}s`);
2035
2074
  if (hadUncommittedChanges && result.inflightVersionId) {
2036
2075
  const newCommit = autoCommitForShare(projectPath, result.inflightVersionId);
2037
2076
  if (newCommit) {
@@ -2105,6 +2144,27 @@ function createServer2() {
2105
2144
  workspaceId: {
2106
2145
  type: "string",
2107
2146
  description: "InFlight workspace ID to create the version in. If not provided, uses the last-used workspace or the user's first workspace."
2147
+ },
2148
+ diffSummary: {
2149
+ type: "object",
2150
+ description: "Claude-generated summary of git changes for review question generation. Provide this when sharing a feature branch with commits.",
2151
+ properties: {
2152
+ summary: {
2153
+ type: "string",
2154
+ description: "A 2-3 sentence product-focused summary of the changes. Focus on user-visible changes, not internal code details."
2155
+ },
2156
+ keyChanges: {
2157
+ type: "array",
2158
+ items: { type: "string" },
2159
+ description: "List of key user-visible changes (e.g., 'Added dark mode toggle', 'Redesigned checkout flow')"
2160
+ },
2161
+ affectedAreas: {
2162
+ type: "array",
2163
+ items: { type: "string" },
2164
+ description: "UI/feature areas affected by the changes (e.g., 'Settings page', 'Navigation', 'Forms')"
2165
+ }
2166
+ },
2167
+ required: ["summary", "keyChanges"]
2108
2168
  }
2109
2169
  },
2110
2170
  required: ["path"]
@@ -2179,7 +2239,7 @@ function createServer2() {
2179
2239
  if (name === "share") {
2180
2240
  const deployArgs = args;
2181
2241
  const result = await deployPrototype(deployArgs, log);
2182
- const message = result.synced ? `Files synced. InFlight: ${result.inflightUrl}` : `Project shared successfully. InFlight: ${result.inflightUrl}`;
2242
+ const message = `Project shared successfully. InFlight: ${result.inflightUrl}`;
2183
2243
  return {
2184
2244
  content: [
2185
2245
  {
@@ -2198,7 +2258,6 @@ function createServer2() {
2198
2258
  },
2199
2259
  // Embed check results - shows if there are iframe embedding issues
2200
2260
  embedCheck: result.embedCheck,
2201
- synced: result.synced ?? false,
2202
2261
  inflightUrl: result.inflightUrl,
2203
2262
  inflightVersionId: result.inflightVersionId,
2204
2263
  message
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-inflight",
3
- "version": "0.2.3",
3
+ "version": "0.3.0",
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",
@@ -13,11 +13,6 @@
13
13
  "bin": {
14
14
  "mcp-inflight": "dist/index.js"
15
15
  },
16
- "scripts": {
17
- "build": "tsup",
18
- "dev": "tsup --watch",
19
- "typecheck": "tsc --noEmit"
20
- },
21
16
  "dependencies": {
22
17
  "@modelcontextprotocol/sdk": "^1.0.0"
23
18
  },
@@ -36,5 +31,10 @@
36
31
  "share",
37
32
  "deploy"
38
33
  ],
39
- "license": "MIT"
40
- }
34
+ "license": "MIT",
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "typecheck": "tsc --noEmit"
39
+ }
40
+ }