@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.
- package/dist/{chunk-3HMBKVW5.js → chunk-HCVPTSOB.js} +342 -234
- package/dist/http-server.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +2 -2
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
2706
|
-
const
|
|
2707
|
-
|
|
2708
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
2798
|
-
|
|
2799
|
-
|
|
2800
|
-
"
|
|
2801
|
-
|
|
2802
|
-
|
|
2803
|
-
|
|
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
|
|
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
|
|
2815
|
-
|
|
2816
|
-
|
|
2817
|
-
|
|
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(`
|
|
2826
|
+
throw new Error(`Direct deployment response included unknown file path: ${file.path}`);
|
|
2825
2827
|
}
|
|
2826
|
-
|
|
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
|
|
2836
|
+
function buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory) {
|
|
2832
2837
|
const escapedDir = shellEsc(sourceDirectory);
|
|
2833
2838
|
const apiBaseUrlJson = JSON.stringify(API_BASE_URL);
|
|
2834
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
2915
|
-
const
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
|
|
2920
|
-
|
|
2921
|
-
|
|
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
|
-
|
|
2967
|
+
|
|
2968
|
+
await readJsonResponse(response);
|
|
2945
2969
|
}
|
|
2946
2970
|
|
|
2947
|
-
|
|
2948
|
-
|
|
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 {
|
|
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: [
|
|
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.
|
|
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(
|
|
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
|
-
|
|
3006
|
-
|
|
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
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
content: [{
|
|
3082
|
+
return {
|
|
3083
|
+
content: [
|
|
3084
|
+
{
|
|
3016
3085
|
type: "text",
|
|
3017
|
-
text: buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory
|
|
3018
|
-
}
|
|
3019
|
-
|
|
3020
|
-
}
|
|
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(
|
|
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(
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
|
|
3106
|
-
|
|
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
|
|
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(
|
|
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(
|
|
3120
|
-
|
|
3121
|
-
|
|
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
|
-
|
|
3132
|
-
if (!stats.isDirectory()) {
|
|
3200
|
+
if (!isAbsoluteSourcePath(sourceDirectory)) {
|
|
3133
3201
|
return {
|
|
3134
|
-
content: [
|
|
3135
|
-
|
|
3136
|
-
|
|
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
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
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
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
|
|
3180
|
-
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
|
|
3195
|
-
|
|
3196
|
-
|
|
3197
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3209
|
-
|
|
3266
|
+
headers: {
|
|
3267
|
+
"x-api-key": getApiKey(),
|
|
3268
|
+
"Content-Type": "application/json"
|
|
3269
|
+
}
|
|
3210
3270
|
});
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
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
|
-
|
|
3216
|
-
|
|
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(
|
|
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}`, {
|
|
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) => {
|
package/dist/http-server.js
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@insforge/mcp",
|
|
3
|
-
"version": "1.2.10-dev.
|
|
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
|
+
"@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",
|