@insforge/mcp 1.2.10-dev.0 → 1.2.10-dev.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.
@@ -1670,11 +1670,7 @@ var createDeploymentResponseSchema = z22.object({
1670
1670
  uploadUrl: z22.string().url(),
1671
1671
  uploadFields: z22.record(z22.string())
1672
1672
  });
1673
- var createDirectDeploymentResponseSchema = z22.object({
1674
- id: z22.string().uuid(),
1675
- status: deploymentSchema.shape.status
1676
- });
1677
- var createDeploymentManifestRequestSchema = z22.object({
1673
+ var createDirectDeploymentRequestSchema = z22.object({
1678
1674
  files: z22.array(deploymentManifestFileEntrySchema).min(1)
1679
1675
  }).superRefine(({ files }, ctx) => {
1680
1676
  const firstSeenByPath = /* @__PURE__ */ new Map();
@@ -1691,7 +1687,9 @@ var createDeploymentManifestRequestSchema = z22.object({
1691
1687
  firstSeenByPath.set(file.path, index);
1692
1688
  });
1693
1689
  });
1694
- var createDeploymentManifestResponseSchema = z22.object({
1690
+ var createDirectDeploymentResponseSchema = z22.object({
1691
+ id: z22.string().uuid(),
1692
+ status: deploymentSchema.shape.status,
1695
1693
  files: z22.array(deploymentManifestFileSchema)
1696
1694
  });
1697
1695
  var uploadDeploymentFileResponseSchema = deploymentManifestFileSchema.extend({
@@ -2647,7 +2645,16 @@ import { join as join2, relative, sep } from "path";
2647
2645
  import archiver from "archiver";
2648
2646
  import FormData2 from "form-data";
2649
2647
  var DIRECT_DEPLOYMENT_MIN_VERSION = "2.0.5";
2650
- var EXCLUDED_DEPLOYMENT_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", ".insforge"]);
2648
+ var DEFAULT_DIRECT_UPLOAD_CONCURRENCY = 8;
2649
+ var MAX_DIRECT_UPLOAD_CONCURRENCY = 32;
2650
+ var EXCLUDED_DEPLOYMENT_SEGMENTS = /* @__PURE__ */ new Set([
2651
+ "node_modules",
2652
+ ".git",
2653
+ ".next",
2654
+ "dist",
2655
+ "build",
2656
+ ".insforge"
2657
+ ]);
2651
2658
  var DirectDeploymentUnsupportedError = class extends Error {
2652
2659
  constructor() {
2653
2660
  super("Direct deployment endpoints are not available on this backend");
@@ -2702,12 +2709,22 @@ function parseCreateDirectDeploymentResponse(obj) {
2702
2709
  }
2703
2710
  return result.data;
2704
2711
  }
2705
- function parseCreateDeploymentManifestResponse(obj) {
2706
- const result = createDeploymentManifestResponseSchema.safeParse(obj);
2707
- if (!result.success) {
2708
- throw new Error("Unexpected response format from deployment manifest endpoint");
2712
+ function getDirectUploadConcurrency() {
2713
+ const parsed = Number.parseInt(process.env.INSFORGE_DEPLOY_UPLOAD_CONCURRENCY ?? "", 10);
2714
+ const requested = Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_DIRECT_UPLOAD_CONCURRENCY;
2715
+ return Math.min(requested, MAX_DIRECT_UPLOAD_CONCURRENCY);
2716
+ }
2717
+ async function runWithConcurrency(items, concurrency, worker) {
2718
+ let nextIndex = 0;
2719
+ async function runWorker() {
2720
+ while (nextIndex < items.length) {
2721
+ const index = nextIndex;
2722
+ nextIndex++;
2723
+ await worker(items[index], index);
2724
+ }
2709
2725
  }
2710
- return result.data;
2726
+ const workerCount = Math.min(concurrency, items.length);
2727
+ await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
2711
2728
  }
2712
2729
  async function collectDeploymentFiles(sourceDirectory) {
2713
2730
  const files = [];
@@ -2746,22 +2763,8 @@ function buildStartBody(input) {
2746
2763
  if (input.meta) startBody.meta = input.meta;
2747
2764
  return startBody;
2748
2765
  }
2749
- async function createDirectDeploymentSession(API_BASE_URL, apiKey) {
2766
+ async function createDirectDeploymentSession(API_BASE_URL, apiKey, files) {
2750
2767
  const response = await fetch6(`${API_BASE_URL}/api/deployments/direct`, {
2751
- method: "POST",
2752
- headers: {
2753
- "x-api-key": apiKey,
2754
- "Content-Type": "application/json"
2755
- }
2756
- });
2757
- if (response.status === 404) {
2758
- throw new DirectDeploymentUnsupportedError();
2759
- }
2760
- const result = await handleApiResponse(response);
2761
- return parseCreateDirectDeploymentResponse(result);
2762
- }
2763
- async function createDeploymentManifest(API_BASE_URL, apiKey, deploymentId, files) {
2764
- const response = await fetch6(`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/manifest`, {
2765
2768
  method: "POST",
2766
2769
  headers: {
2767
2770
  "x-api-key": apiKey,
@@ -2773,7 +2776,7 @@ async function createDeploymentManifest(API_BASE_URL, apiKey, deploymentId, file
2773
2776
  throw new DirectDeploymentUnsupportedError();
2774
2777
  }
2775
2778
  const result = await handleApiResponse(response);
2776
- return parseCreateDeploymentManifestResponse(result);
2779
+ return parseCreateDirectDeploymentResponse(result);
2777
2780
  }
2778
2781
  async function uploadDeploymentFileContent(API_BASE_URL, apiKey, deploymentId, file, localFile) {
2779
2782
  const response = await fetch6(
@@ -2794,14 +2797,17 @@ async function uploadDeploymentFileContent(API_BASE_URL, apiKey, deploymentId, f
2794
2797
  await handleApiResponse(response);
2795
2798
  }
2796
2799
  async function startDeployment(API_BASE_URL, apiKey, deploymentId, startBody) {
2797
- const response = await fetch6(`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`, {
2798
- method: "POST",
2799
- headers: {
2800
- "x-api-key": apiKey,
2801
- "Content-Type": "application/json"
2802
- },
2803
- body: JSON.stringify(startBody)
2804
- });
2800
+ const response = await fetch6(
2801
+ `${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`,
2802
+ {
2803
+ method: "POST",
2804
+ headers: {
2805
+ "x-api-key": apiKey,
2806
+ "Content-Type": "application/json"
2807
+ },
2808
+ body: JSON.stringify(startBody)
2809
+ }
2810
+ );
2805
2811
  return handleApiResponse(response);
2806
2812
  }
2807
2813
  async function deployDirect(API_BASE_URL, apiKey, sourceDirectory, startBody) {
@@ -2809,32 +2815,30 @@ async function deployDirect(API_BASE_URL, apiKey, sourceDirectory, startBody) {
2809
2815
  if (files.length === 0) {
2810
2816
  throw new Error("No deployable files found in source directory after applying exclusions.");
2811
2817
  }
2812
- const createResult = await createDirectDeploymentSession(API_BASE_URL, apiKey);
2818
+ const manifestFiles = files.map(({ path, sha, size }) => ({ path, sha, size }));
2819
+ const createResult = await createDirectDeploymentSession(API_BASE_URL, apiKey, manifestFiles);
2813
2820
  const deploymentId = String(createResult.id);
2814
- const manifest = await createDeploymentManifest(
2815
- API_BASE_URL,
2816
- apiKey,
2817
- deploymentId,
2818
- files.map(({ path, sha, size }) => ({ path, sha, size }))
2819
- );
2820
- const localFilesByPath = new Map(files.map((file) => [file.path, file]));
2821
- for (const manifestFile of manifest.files) {
2822
- const localFile = localFilesByPath.get(manifestFile.path);
2821
+ const localFileByPath = new Map(files.map((file) => [file.path, file]));
2822
+ const uploadConcurrency = getDirectUploadConcurrency();
2823
+ await runWithConcurrency(createResult.files, uploadConcurrency, async (file) => {
2824
+ const localFile = localFileByPath.get(file.path);
2823
2825
  if (!localFile) {
2824
- throw new Error(`Manifest response included unknown file path: ${manifestFile.path}`);
2826
+ throw new Error(`Direct deployment response included unknown file path: ${file.path}`);
2825
2827
  }
2826
- await uploadDeploymentFileContent(API_BASE_URL, apiKey, deploymentId, manifestFile, localFile);
2827
- }
2828
+ if (localFile.sha !== file.sha || localFile.size !== file.size) {
2829
+ throw new Error(`Direct deployment response did not match local file metadata: ${file.path}`);
2830
+ }
2831
+ await uploadDeploymentFileContent(API_BASE_URL, apiKey, deploymentId, file, localFile);
2832
+ });
2828
2833
  const startResult = await startDeployment(API_BASE_URL, apiKey, deploymentId, startBody);
2829
- return { deploymentId, fileCount: files.length, startResult };
2834
+ return { deploymentId, fileCount: files.length, uploadConcurrency, startResult };
2830
2835
  }
2831
- function buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory, deploymentId) {
2836
+ function buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory) {
2832
2837
  const escapedDir = shellEsc(sourceDirectory);
2833
2838
  const apiBaseUrlJson = JSON.stringify(API_BASE_URL);
2834
- const deploymentIdJson = JSON.stringify(deploymentId);
2835
- return `Deployment prepared successfully using direct upload. Deployment ID: ${deploymentId}
2839
+ return `Direct deployment upload is available for this backend.
2836
2840
 
2837
- Please execute the following command locally from a shell that has INSFORGE_API_KEY set, then call the \`start-deployment\` tool with the deployment ID printed by the script.
2841
+ Please execute the following command locally from a shell that has INSFORGE_API_KEY set, then call the \`start-deployment\` tool with the deployment ID printed by the script. Set \`INSFORGE_DEPLOY_UPLOAD_CONCURRENCY\` if you want to tune parallel uploads; the default is 8 and the maximum is 32.
2838
2842
 
2839
2843
  \`\`\`bash
2840
2844
  cd ${escapedDir}
@@ -2846,7 +2850,8 @@ const path = await import('node:path');
2846
2850
 
2847
2851
  const API_BASE_URL = ${apiBaseUrlJson};
2848
2852
  const API_KEY = process.env.INSFORGE_API_KEY;
2849
- const deploymentId = ${deploymentIdJson};
2853
+ const DEFAULT_UPLOAD_CONCURRENCY = 8;
2854
+ const MAX_UPLOAD_CONCURRENCY = 32;
2850
2855
  const EXCLUDED_SEGMENTS = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.insforge']);
2851
2856
 
2852
2857
  function shouldExcludeDeploymentPath(normalizedName) {
@@ -2858,9 +2863,17 @@ function shouldExcludeDeploymentPath(normalizedName) {
2858
2863
 
2859
2864
  async function readJsonResponse(response) {
2860
2865
  const text = await response.text();
2861
- const data = text ? JSON.parse(text) : null;
2866
+ let data = null;
2867
+ if (text) {
2868
+ try {
2869
+ data = JSON.parse(text);
2870
+ } catch {
2871
+ data = text;
2872
+ }
2873
+ }
2862
2874
  if (!response.ok) {
2863
- throw new Error(data?.message || data?.error || \`Request failed with status \${response.status}\`);
2875
+ const message = data && typeof data === 'object' ? data.message || data.error : null;
2876
+ throw new Error(message || 'Request failed with status ' + response.status);
2864
2877
  }
2865
2878
  return data;
2866
2879
  }
@@ -2870,10 +2883,31 @@ async function api(pathname, init = {}) {
2870
2883
  'x-api-key': API_KEY,
2871
2884
  ...(init.headers || {}),
2872
2885
  };
2873
- const response = await fetch(\`\${API_BASE_URL}\${pathname}\`, { ...init, headers });
2886
+ const response = await fetch(API_BASE_URL + pathname, { ...init, headers });
2874
2887
  return readJsonResponse(response);
2875
2888
  }
2876
2889
 
2890
+ function getUploadConcurrency() {
2891
+ const parsed = Number.parseInt(process.env.INSFORGE_DEPLOY_UPLOAD_CONCURRENCY || '', 10);
2892
+ const requested = Number.isSafeInteger(parsed) && parsed > 0 ? parsed : DEFAULT_UPLOAD_CONCURRENCY;
2893
+ return Math.min(requested, MAX_UPLOAD_CONCURRENCY);
2894
+ }
2895
+
2896
+ async function runWithConcurrency(items, concurrency, worker) {
2897
+ let nextIndex = 0;
2898
+
2899
+ async function runWorker() {
2900
+ while (nextIndex < items.length) {
2901
+ const index = nextIndex;
2902
+ nextIndex += 1;
2903
+ await worker(items[index], index);
2904
+ }
2905
+ }
2906
+
2907
+ const workerCount = Math.min(concurrency, items.length);
2908
+ await Promise.all(Array.from({ length: workerCount }, () => runWorker()));
2909
+ }
2910
+
2877
2911
  async function hashFile(filePath) {
2878
2912
  const hash = createHash('sha1');
2879
2913
  let size = 0;
@@ -2911,25 +2945,14 @@ async function collectFiles(rootDirectory) {
2911
2945
  return files;
2912
2946
  }
2913
2947
 
2914
- const rootDirectory = process.cwd();
2915
- const localFiles = await collectFiles(rootDirectory);
2916
- if (localFiles.length === 0) throw new Error('No deployable files found after applying exclusions.');
2917
-
2918
- const manifest = await api(\`/api/deployments/\${encodeURIComponent(deploymentId)}/manifest\`, {
2919
- method: 'POST',
2920
- headers: { 'Content-Type': 'application/json' },
2921
- body: JSON.stringify({
2922
- files: localFiles.map(({ path, sha, size }) => ({ path, sha, size })),
2923
- }),
2924
- });
2925
-
2926
- const localFilesByPath = new Map(localFiles.map((file) => [file.path, file]));
2927
- for (const remoteFile of manifest.files) {
2928
- const localFile = localFilesByPath.get(remoteFile.path);
2929
- if (!localFile) throw new Error(\`Manifest response included unknown path: \${remoteFile.path}\`);
2930
-
2931
- const uploadResponse = await fetch(
2932
- \`\${API_BASE_URL}/api/deployments/\${encodeURIComponent(deploymentId)}/files/\${encodeURIComponent(remoteFile.fileId)}/content\`,
2948
+ async function uploadFile(deploymentId, manifestFile, localFile) {
2949
+ const response = await fetch(
2950
+ API_BASE_URL +
2951
+ '/api/deployments/' +
2952
+ encodeURIComponent(deploymentId) +
2953
+ '/files/' +
2954
+ encodeURIComponent(manifestFile.fileId) +
2955
+ '/content',
2933
2956
  {
2934
2957
  method: 'PUT',
2935
2958
  headers: {
@@ -2941,18 +2964,59 @@ for (const remoteFile of manifest.files) {
2941
2964
  duplex: 'half',
2942
2965
  }
2943
2966
  );
2944
- await readJsonResponse(uploadResponse);
2967
+
2968
+ await readJsonResponse(response);
2945
2969
  }
2946
2970
 
2947
- console.log(\`Deployment files uploaded. Deployment ID: \${deploymentId}\`);
2948
- console.log(\`Uploaded \${localFiles.length} files through the direct deployment proxy.\`);
2971
+ const rootDirectory = process.cwd();
2972
+ const localFiles = await collectFiles(rootDirectory);
2973
+ if (localFiles.length === 0) throw new Error('No deployable files found after applying exclusions.');
2974
+
2975
+ const createResult = await api('/api/deployments/direct', {
2976
+ method: 'POST',
2977
+ headers: { 'Content-Type': 'application/json' },
2978
+ body: JSON.stringify({
2979
+ files: localFiles.map(({ path, sha, size }) => ({ path, sha, size })),
2980
+ }),
2981
+ });
2982
+
2983
+ if (!createResult || !Array.isArray(createResult.files)) {
2984
+ throw new Error('Direct deployment endpoint returned an unexpected response.');
2985
+ }
2986
+
2987
+ const deploymentId = createResult.id;
2988
+ const localFileByPath = new Map(localFiles.map((file) => [file.path, file]));
2989
+ const uploadConcurrency = getUploadConcurrency();
2990
+ console.log('Created deployment. Deployment ID: ' + deploymentId);
2991
+
2992
+ await runWithConcurrency(createResult.files, uploadConcurrency, async (manifestFile) => {
2993
+ const localFile = localFileByPath.get(manifestFile.path);
2994
+ if (!localFile) throw new Error('Backend returned an unknown file path: ' + manifestFile.path);
2995
+ if (localFile.sha !== manifestFile.sha || localFile.size !== manifestFile.size) {
2996
+ throw new Error('Backend file metadata mismatch for: ' + manifestFile.path);
2997
+ }
2998
+ await uploadFile(deploymentId, manifestFile, localFile);
2999
+ });
3000
+
3001
+ console.log('Deployment files uploaded. Deployment ID: ' + deploymentId);
3002
+ console.log('Uploaded ' + createResult.files.length + ' files through direct deployment proxy with concurrency ' + uploadConcurrency + '.');
2949
3003
  NODE
2950
3004
  \`\`\`
2951
3005
 
2952
- After the script succeeds, call the \`start-deployment\` tool with the printed deployment ID.`;
3006
+ After the script succeeds, call the \`start-deployment\` tool with the printed deployment ID.
3007
+
3008
+ If the upload is interrupted after the deployment ID is printed, query \`system.deployment_files\` with the raw SQL tool for that \`deployment_id\` to inspect \`uploaded_at\`. You can rerun \`create-deployment\` to create a fresh deployment, or upload missing file IDs to \`PUT /api/deployments/:id/files/:fileId/content\` using the queried manifest rows.`;
2953
3009
  }
2954
3010
  function registerDeploymentTools(ctx) {
2955
- const { API_BASE_URL, backendVersion, isRemote, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
3011
+ const {
3012
+ API_BASE_URL,
3013
+ backendVersion,
3014
+ isRemote,
3015
+ registerTool,
3016
+ withUsageTracking,
3017
+ getApiKey,
3018
+ addBackgroundContext
3019
+ } = ctx;
2956
3020
  const supportsDirectDeployment = supportsDirectDeploymentVersion(backendVersion);
2957
3021
  registerTool(
2958
3022
  "get-container-logs",
@@ -2979,7 +3043,9 @@ function registerDeploymentTools(ctx) {
2979
3043
  }
2980
3044
  const result = await handleApiResponse(response);
2981
3045
  return await addBackgroundContext({
2982
- content: [{ type: "text", text: formatSuccessMessage(`Latest logs from ${source}`, result) }]
3046
+ content: [
3047
+ { type: "text", text: formatSuccessMessage(`Latest logs from ${source}`, result) }
3048
+ ]
2983
3049
  });
2984
3050
  } catch (error) {
2985
3051
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
@@ -2993,35 +3059,34 @@ function registerDeploymentTools(ctx) {
2993
3059
  if (isRemote) {
2994
3060
  registerTool(
2995
3061
  "create-deployment",
2996
- "Prepare a deployment upload. For backends v2.0.5 and newer, returns direct file upload commands. Older backends use the legacy zip upload flow. After uploading, call the start-deployment tool to trigger the build.",
3062
+ "Prepare a deployment upload. Direct-capable backends return direct file upload commands. Older backends use the legacy zip upload flow. After uploading, call the start-deployment tool to trigger the build.",
2997
3063
  {
2998
- sourceDirectory: z29.string().describe('Absolute path to the source directory containing files to deploy (e.g., /Users/name/project). Do not use relative paths like "."')
3064
+ sourceDirectory: z29.string().describe(
3065
+ 'Absolute path to the source directory containing files to deploy (e.g., /Users/name/project). Do not use relative paths like "."'
3066
+ )
2999
3067
  },
3000
3068
  withUsageTracking("create-deployment", async ({ sourceDirectory }) => {
3001
3069
  try {
3002
3070
  if (!isAbsoluteSourcePath(sourceDirectory)) {
3003
3071
  return {
3004
- content: [{
3005
- type: "text",
3006
- text: `Error: sourceDirectory must be an absolute path, not a relative path like "${sourceDirectory}". Please provide the full path to the source directory (e.g., /Users/name/project on macOS/Linux or C:\\Users\\name\\project on Windows).`
3007
- }],
3072
+ content: [
3073
+ {
3074
+ type: "text",
3075
+ text: `Error: sourceDirectory must be an absolute path, not a relative path like "${sourceDirectory}". Please provide the full path to the source directory (e.g., /Users/name/project on macOS/Linux or C:\\Users\\name\\project on Windows).`
3076
+ }
3077
+ ],
3008
3078
  isError: true
3009
3079
  };
3010
3080
  }
3011
3081
  if (supportsDirectDeployment) {
3012
- try {
3013
- const createResult2 = await createDirectDeploymentSession(API_BASE_URL, getApiKey());
3014
- return {
3015
- content: [{
3082
+ return {
3083
+ content: [
3084
+ {
3016
3085
  type: "text",
3017
- text: buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory, String(createResult2.id))
3018
- }]
3019
- };
3020
- } catch (error) {
3021
- if (!(error instanceof DirectDeploymentUnsupportedError)) {
3022
- throw error;
3023
- }
3024
- }
3086
+ text: buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory)
3087
+ }
3088
+ ]
3089
+ };
3025
3090
  }
3026
3091
  const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
3027
3092
  method: "POST",
@@ -3030,7 +3095,9 @@ function registerDeploymentTools(ctx) {
3030
3095
  "Content-Type": "application/json"
3031
3096
  }
3032
3097
  });
3033
- const createResult = parseCreateDeploymentResponse(await handleApiResponse(createResponse));
3098
+ const createResult = parseCreateDeploymentResponse(
3099
+ await handleApiResponse(createResponse)
3100
+ );
3034
3101
  const { id: deploymentId, uploadUrl, uploadFields } = createResult;
3035
3102
  const esc = shellEsc;
3036
3103
  const curlFields = Object.entries(uploadFields).map(([key, value]) => `-F ${esc(`${key}=${value}`)}`).join(" \\\n ");
@@ -3078,158 +3145,194 @@ Run each step in order. If any step fails, do not proceed to the next step.`;
3078
3145
  deploymentId: z29.string().describe("The deployment ID returned by create-deployment"),
3079
3146
  ...startDeploymentRequestSchema.shape
3080
3147
  },
3081
- withUsageTracking("start-deployment", async ({ deploymentId, projectSettings, envVars, meta }) => {
3082
- try {
3083
- const startBody = {};
3084
- if (projectSettings) startBody.projectSettings = projectSettings;
3085
- if (envVars) startBody.envVars = envVars;
3086
- if (meta) startBody.meta = meta;
3087
- const startResponse = await fetch6(`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`, {
3088
- method: "POST",
3089
- headers: {
3090
- "x-api-key": getApiKey(),
3091
- "Content-Type": "application/json"
3092
- },
3093
- body: JSON.stringify(startBody)
3094
- });
3095
- const startResult = await handleApiResponse(startResponse);
3096
- return await addBackgroundContext({
3097
- content: [{
3098
- type: "text",
3099
- text: formatSuccessMessage("Deployment started", startResult) + "\n\nNote: You can check deployment status by querying the system.deployments table."
3100
- }]
3101
- });
3102
- } catch (error) {
3103
- const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
3104
- return {
3105
- content: [{ type: "text", text: `Error starting deployment: ${errMsg}` }],
3106
- isError: true
3107
- };
3148
+ withUsageTracking(
3149
+ "start-deployment",
3150
+ async ({ deploymentId, projectSettings, envVars, meta }) => {
3151
+ try {
3152
+ const startBody = {};
3153
+ if (projectSettings) startBody.projectSettings = projectSettings;
3154
+ if (envVars) startBody.envVars = envVars;
3155
+ if (meta) startBody.meta = meta;
3156
+ const startResponse = await fetch6(
3157
+ `${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`,
3158
+ {
3159
+ method: "POST",
3160
+ headers: {
3161
+ "x-api-key": getApiKey(),
3162
+ "Content-Type": "application/json"
3163
+ },
3164
+ body: JSON.stringify(startBody)
3165
+ }
3166
+ );
3167
+ const startResult = await handleApiResponse(startResponse);
3168
+ return await addBackgroundContext({
3169
+ content: [
3170
+ {
3171
+ type: "text",
3172
+ text: formatSuccessMessage("Deployment started", startResult) + "\n\nNote: You can check deployment status by querying the system.deployments table."
3173
+ }
3174
+ ]
3175
+ });
3176
+ } catch (error) {
3177
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
3178
+ return {
3179
+ content: [{ type: "text", text: `Error starting deployment: ${errMsg}` }],
3180
+ isError: true
3181
+ };
3182
+ }
3108
3183
  }
3109
- })
3184
+ )
3110
3185
  );
3111
3186
  } else {
3112
3187
  registerTool(
3113
3188
  "create-deployment",
3114
- "Deploy source code from a directory. Uses direct file upload for backends v2.0.5 and newer, with legacy zip upload fallback for older projects.",
3189
+ "Deploy source code from a directory. Uses parallel direct file uploads for direct-capable backends, with legacy zip upload fallback for older projects.",
3115
3190
  {
3116
- sourceDirectory: z29.string().describe('Absolute path to the source directory containing files to deploy (e.g., /Users/name/project or C:\\Users\\name\\project). Do not use relative paths like "."'),
3191
+ sourceDirectory: z29.string().describe(
3192
+ 'Absolute path to the source directory containing files to deploy (e.g., /Users/name/project or C:\\Users\\name\\project). Do not use relative paths like "."'
3193
+ ),
3117
3194
  ...startDeploymentRequestSchema.shape
3118
3195
  },
3119
- withUsageTracking("create-deployment", async ({ sourceDirectory, projectSettings, envVars, meta }) => {
3120
- try {
3121
- if (!isAbsoluteSourcePath(sourceDirectory)) {
3122
- return {
3123
- content: [{
3124
- type: "text",
3125
- text: `Error: sourceDirectory must be an absolute path, not a relative path like "${sourceDirectory}". Please provide the full path to the source directory (e.g., /Users/name/project on macOS/Linux or C:\\Users\\name\\project on Windows).`
3126
- }],
3127
- isError: true
3128
- };
3129
- }
3196
+ withUsageTracking(
3197
+ "create-deployment",
3198
+ async ({ sourceDirectory, projectSettings, envVars, meta }) => {
3130
3199
  try {
3131
- const stats = await fs3.stat(sourceDirectory);
3132
- if (!stats.isDirectory()) {
3200
+ if (!isAbsoluteSourcePath(sourceDirectory)) {
3133
3201
  return {
3134
- content: [{
3135
- type: "text",
3136
- text: `Error: "${sourceDirectory}" is not a directory. Please provide a path to a directory containing the source code.`
3137
- }],
3202
+ content: [
3203
+ {
3204
+ type: "text",
3205
+ text: `Error: sourceDirectory must be an absolute path, not a relative path like "${sourceDirectory}". Please provide the full path to the source directory (e.g., /Users/name/project on macOS/Linux or C:\\Users\\name\\project on Windows).`
3206
+ }
3207
+ ],
3138
3208
  isError: true
3139
3209
  };
3140
3210
  }
3141
- } catch {
3142
- return {
3143
- content: [{
3144
- type: "text",
3145
- text: `Error: Directory "${sourceDirectory}" does not exist or is not accessible. Please verify the path is correct.`
3146
- }],
3147
- isError: true
3148
- };
3149
- }
3150
- const resolvedSourceDir = sourceDirectory;
3151
- const startBody = buildStartBody({ projectSettings, envVars, meta });
3152
- if (supportsDirectDeployment) {
3153
3211
  try {
3154
- const { fileCount, startResult: startResult2 } = await deployDirect(API_BASE_URL, getApiKey(), resolvedSourceDir, startBody);
3155
- return await addBackgroundContext({
3156
- content: [{
3157
- type: "text",
3158
- text: formatSuccessMessage("Deployment started", startResult2) + `
3159
-
3160
- Uploaded ${fileCount} files through the direct deployment proxy.
3161
-
3162
- Note: You can check deployment status by querying the system.deployments table.`
3163
- }]
3164
- });
3165
- } catch (error) {
3166
- if (!(error instanceof DirectDeploymentUnsupportedError)) {
3167
- throw error;
3212
+ const stats = await fs3.stat(sourceDirectory);
3213
+ if (!stats.isDirectory()) {
3214
+ return {
3215
+ content: [
3216
+ {
3217
+ type: "text",
3218
+ text: `Error: "${sourceDirectory}" is not a directory. Please provide a path to a directory containing the source code.`
3219
+ }
3220
+ ],
3221
+ isError: true
3222
+ };
3168
3223
  }
3224
+ } catch {
3225
+ return {
3226
+ content: [
3227
+ {
3228
+ type: "text",
3229
+ text: `Error: Directory "${sourceDirectory}" does not exist or is not accessible. Please verify the path is correct.`
3230
+ }
3231
+ ],
3232
+ isError: true
3233
+ };
3169
3234
  }
3170
- }
3171
- const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
3172
- method: "POST",
3173
- headers: {
3174
- "x-api-key": getApiKey(),
3175
- "Content-Type": "application/json"
3176
- }
3177
- });
3178
- const createResult = parseCreateDeploymentResponse(await handleApiResponse(createResponse));
3179
- const { id: deploymentId, uploadUrl, uploadFields } = createResult;
3180
- const tmpZipPath = join2(tmpdir2(), `insforge-deploy-${deploymentId}.zip`);
3181
- try {
3182
- await new Promise((resolve, reject) => {
3183
- const archive = archiver("zip", { zlib: { level: 9 } });
3184
- const output = createWriteStream(tmpZipPath);
3185
- output.on("close", resolve);
3186
- output.on("error", reject);
3187
- archive.on("error", reject);
3188
- archive.directory(resolvedSourceDir, false, (entry) => {
3189
- const normalizedName = entry.name.replace(/\\/g, "/");
3190
- if (shouldExcludeDeploymentPath(normalizedName)) return false;
3191
- return entry;
3192
- });
3193
- archive.pipe(output);
3194
- archive.finalize();
3195
- });
3196
- const { size: zipSize } = await fs3.stat(tmpZipPath);
3197
- const uploadFormData = new FormData2();
3198
- for (const [key, value] of Object.entries(uploadFields)) {
3199
- uploadFormData.append(key, value);
3235
+ const resolvedSourceDir = sourceDirectory;
3236
+ const startBody = buildStartBody({ projectSettings, envVars, meta });
3237
+ if (supportsDirectDeployment) {
3238
+ try {
3239
+ const { fileCount, uploadConcurrency, startResult: startResult2 } = await deployDirect(
3240
+ API_BASE_URL,
3241
+ getApiKey(),
3242
+ resolvedSourceDir,
3243
+ startBody
3244
+ );
3245
+ const uploadSummary = `Uploaded ${fileCount} files through direct deployment proxy with concurrency ${uploadConcurrency}.`;
3246
+ return await addBackgroundContext({
3247
+ content: [
3248
+ {
3249
+ type: "text",
3250
+ text: formatSuccessMessage("Deployment started", startResult2) + `
3251
+
3252
+ ${uploadSummary}
3253
+
3254
+ Note: You can check deployment status by querying the system.deployments table. If file uploads are interrupted, inspect system.deployment_files with the raw SQL tool to see which files are already uploaded before retrying missing files.`
3255
+ }
3256
+ ]
3257
+ });
3258
+ } catch (error) {
3259
+ if (!(error instanceof DirectDeploymentUnsupportedError)) {
3260
+ throw error;
3261
+ }
3262
+ }
3200
3263
  }
3201
- uploadFormData.append("file", createReadStream(tmpZipPath), {
3202
- filename: "deployment.zip",
3203
- contentType: "application/zip",
3204
- knownLength: zipSize
3205
- });
3206
- const uploadResponse = await fetch6(uploadUrl, {
3264
+ const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
3207
3265
  method: "POST",
3208
- body: uploadFormData,
3209
- headers: uploadFormData.getHeaders()
3266
+ headers: {
3267
+ "x-api-key": getApiKey(),
3268
+ "Content-Type": "application/json"
3269
+ }
3210
3270
  });
3211
- if (!uploadResponse.ok) {
3212
- const uploadError = await uploadResponse.text();
3213
- throw new Error(`Failed to upload zip file: ${uploadError}`);
3271
+ const createResult = parseCreateDeploymentResponse(
3272
+ await handleApiResponse(createResponse)
3273
+ );
3274
+ const { id: deploymentId, uploadUrl, uploadFields } = createResult;
3275
+ const tmpZipPath = join2(tmpdir2(), `insforge-deploy-${deploymentId}.zip`);
3276
+ try {
3277
+ await new Promise((resolve, reject) => {
3278
+ const archive = archiver("zip", { zlib: { level: 9 } });
3279
+ const output = createWriteStream(tmpZipPath);
3280
+ output.on("close", resolve);
3281
+ output.on("error", reject);
3282
+ archive.on("error", reject);
3283
+ archive.directory(resolvedSourceDir, false, (entry) => {
3284
+ const normalizedName = entry.name.replace(/\\/g, "/");
3285
+ if (shouldExcludeDeploymentPath(normalizedName)) return false;
3286
+ return entry;
3287
+ });
3288
+ archive.pipe(output);
3289
+ archive.finalize();
3290
+ });
3291
+ const { size: zipSize } = await fs3.stat(tmpZipPath);
3292
+ const uploadFormData = new FormData2();
3293
+ for (const [key, value] of Object.entries(uploadFields)) {
3294
+ uploadFormData.append(key, value);
3295
+ }
3296
+ uploadFormData.append("file", createReadStream(tmpZipPath), {
3297
+ filename: "deployment.zip",
3298
+ contentType: "application/zip",
3299
+ knownLength: zipSize
3300
+ });
3301
+ const uploadResponse = await fetch6(uploadUrl, {
3302
+ method: "POST",
3303
+ body: uploadFormData,
3304
+ headers: uploadFormData.getHeaders()
3305
+ });
3306
+ if (!uploadResponse.ok) {
3307
+ const uploadError = await uploadResponse.text();
3308
+ throw new Error(`Failed to upload zip file: ${uploadError}`);
3309
+ }
3310
+ } finally {
3311
+ await fs3.rm(tmpZipPath, { force: true }).catch(() => void 0);
3214
3312
  }
3215
- } finally {
3216
- await fs3.rm(tmpZipPath, { force: true }).catch(() => void 0);
3313
+ const startResult = await startDeployment(
3314
+ API_BASE_URL,
3315
+ getApiKey(),
3316
+ deploymentId,
3317
+ startBody
3318
+ );
3319
+ return await addBackgroundContext({
3320
+ content: [
3321
+ {
3322
+ type: "text",
3323
+ text: formatSuccessMessage("Deployment started", startResult) + "\n\nNote: You can check deployment status by querying the system.deployments table."
3324
+ }
3325
+ ]
3326
+ });
3327
+ } catch (error) {
3328
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
3329
+ return {
3330
+ content: [{ type: "text", text: `Error creating deployment: ${errMsg}` }],
3331
+ isError: true
3332
+ };
3217
3333
  }
3218
- const startResult = await startDeployment(API_BASE_URL, getApiKey(), deploymentId, startBody);
3219
- return await addBackgroundContext({
3220
- content: [{
3221
- type: "text",
3222
- text: formatSuccessMessage("Deployment started", startResult) + "\n\nNote: You can check deployment status by querying the system.deployments table."
3223
- }]
3224
- });
3225
- } catch (error) {
3226
- const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
3227
- return {
3228
- content: [{ type: "text", text: `Error creating deployment: ${errMsg}` }],
3229
- isError: true
3230
- };
3231
3334
  }
3232
- })
3335
+ )
3233
3336
  );
3234
3337
  }
3235
3338
  }
@@ -3290,7 +3393,10 @@ async function fetchBackendVersion(apiBaseUrl) {
3290
3393
  return health.version;
3291
3394
  } catch (error) {
3292
3395
  if (error instanceof Error && error.name === "AbortError") {
3293
- throw new Error(`Health check timed out after 10s \u2014 is the backend running at ${apiBaseUrl}?`, { cause: error });
3396
+ throw new Error(
3397
+ `Health check timed out after 10s \u2014 is the backend running at ${apiBaseUrl}?`,
3398
+ { cause: error }
3399
+ );
3294
3400
  }
3295
3401
  throw error;
3296
3402
  } finally {
@@ -3313,7 +3419,9 @@ async function registerInsforgeTools(server, config = {}) {
3313
3419
  } catch (error) {
3314
3420
  const msg = error instanceof Error ? error.message : String(error);
3315
3421
  console.error(`Failed to fetch backend version: ${msg}`);
3316
- throw new Error(`Cannot initialize tools: backend at ${API_BASE_URL} is unreachable. ${msg}`, { cause: error });
3422
+ throw new Error(`Cannot initialize tools: backend at ${API_BASE_URL} is unreachable. ${msg}`, {
3423
+ cause: error
3424
+ });
3317
3425
  }
3318
3426
  let toolCount = 0;
3319
3427
  const registerTool = (toolName, ...args) => {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  registerInsforgeTools
4
- } from "./chunk-3HMBKVW5.js";
4
+ } from "./chunk-HCVPTSOB.js";
5
5
 
6
6
  // src/http/server.ts
7
7
  import "dotenv/config";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  registerInsforgeTools
4
- } from "./chunk-3HMBKVW5.js";
4
+ } from "./chunk-HCVPTSOB.js";
5
5
 
6
6
  // src/stdio/index.ts
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@insforge/mcp",
3
- "version": "1.2.10-dev.0",
3
+ "version": "1.2.10-dev.2",
4
4
  "description": "MCP (Model Context Protocol) server for Insforge backend-as-a-service",
5
5
  "mcpName": "io.github.InsForge/insforge-mcp",
6
6
  "type": "module",
@@ -45,7 +45,7 @@
45
45
  "server.json"
46
46
  ],
47
47
  "dependencies": {
48
- "@insforge/shared-schemas": "1.1.48",
48
+ "@insforge/shared-schemas": "1.1.49",
49
49
  "@modelcontextprotocol/sdk": "^1.27.1",
50
50
  "archiver": "^7.0.1",
51
51
  "commander": "^14.0.0",