@insforge/mcp 1.2.9 → 1.2.10-dev.1
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-YH7HSQYJ.js → chunk-MQZLKWM4.js} +434 -49
- package/dist/http-server.js +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -3
|
@@ -567,6 +567,28 @@ var authConfigSchema = z7.object({
|
|
|
567
567
|
updatedAt: z7.string()
|
|
568
568
|
// PostgreSQL timestamp
|
|
569
569
|
});
|
|
570
|
+
var smtpConfigSchema = z7.object({
|
|
571
|
+
id: z7.string().uuid(),
|
|
572
|
+
enabled: z7.boolean(),
|
|
573
|
+
host: z7.string(),
|
|
574
|
+
port: z7.number().int(),
|
|
575
|
+
username: z7.string(),
|
|
576
|
+
hasPassword: z7.boolean(),
|
|
577
|
+
// Never expose actual password
|
|
578
|
+
senderEmail: z7.string(),
|
|
579
|
+
senderName: z7.string(),
|
|
580
|
+
minIntervalSeconds: z7.number().int().min(0),
|
|
581
|
+
createdAt: z7.string(),
|
|
582
|
+
updatedAt: z7.string()
|
|
583
|
+
});
|
|
584
|
+
var emailTemplateSchema = z7.object({
|
|
585
|
+
id: z7.string().uuid(),
|
|
586
|
+
templateType: z7.string(),
|
|
587
|
+
subject: z7.string(),
|
|
588
|
+
bodyHtml: z7.string(),
|
|
589
|
+
createdAt: z7.string(),
|
|
590
|
+
updatedAt: z7.string()
|
|
591
|
+
});
|
|
570
592
|
var tokenPayloadSchema = z7.object({
|
|
571
593
|
sub: userIdSchema,
|
|
572
594
|
// Subject (user ID)
|
|
@@ -598,7 +620,8 @@ var createUserRequestSchema = z8.object({
|
|
|
598
620
|
email: emailSchema,
|
|
599
621
|
password: passwordSchema,
|
|
600
622
|
name: nameSchema.optional(),
|
|
601
|
-
redirectTo: z8.string().url().optional()
|
|
623
|
+
redirectTo: z8.string().url().optional(),
|
|
624
|
+
autoConfirm: z8.boolean().optional()
|
|
602
625
|
});
|
|
603
626
|
var createSessionRequestSchema = z8.object({
|
|
604
627
|
email: emailSchema,
|
|
@@ -744,6 +767,25 @@ var getPublicAuthConfigResponseSchema = z8.object({
|
|
|
744
767
|
allowedRedirectUrls: true
|
|
745
768
|
}).shape
|
|
746
769
|
});
|
|
770
|
+
var upsertSmtpConfigRequestSchema = z8.object({
|
|
771
|
+
enabled: z8.boolean(),
|
|
772
|
+
host: z8.string().min(1, "SMTP host is required"),
|
|
773
|
+
port: z8.union([z8.literal(25), z8.literal(465), z8.literal(587), z8.literal(2525)], {
|
|
774
|
+
errorMap: () => ({ message: "Port must be one of: 25, 465, 587, 2525" })
|
|
775
|
+
}),
|
|
776
|
+
username: z8.string().min(1, "SMTP username is required"),
|
|
777
|
+
password: z8.string().min(1, "SMTP password is required").optional(),
|
|
778
|
+
senderEmail: z8.string().email("Invalid sender email"),
|
|
779
|
+
senderName: z8.string().min(1, "Sender name is required"),
|
|
780
|
+
minIntervalSeconds: z8.number().int().min(0).default(60)
|
|
781
|
+
});
|
|
782
|
+
var updateEmailTemplateRequestSchema = z8.object({
|
|
783
|
+
subject: z8.string().min(1, "Subject is required"),
|
|
784
|
+
bodyHtml: z8.string().min(1, "Template body is required")
|
|
785
|
+
});
|
|
786
|
+
var listEmailTemplatesResponseSchema = z8.object({
|
|
787
|
+
data: z8.array(emailTemplateSchema)
|
|
788
|
+
});
|
|
747
789
|
var authErrorResponseSchema = z8.object({
|
|
748
790
|
error: z8.string(),
|
|
749
791
|
message: z8.string(),
|
|
@@ -1574,9 +1616,9 @@ var sendEmailResponseSchema = z20.object({});
|
|
|
1574
1616
|
import { z as z21 } from "zod";
|
|
1575
1617
|
var deploymentStatusSchema = z21.enum([
|
|
1576
1618
|
"WAITING",
|
|
1577
|
-
// Record created, waiting for
|
|
1619
|
+
// Record created, waiting for source zip upload or direct file registration/content
|
|
1578
1620
|
"UPLOADING",
|
|
1579
|
-
//
|
|
1621
|
+
// File uploads or Vercel deployment creation are in progress
|
|
1580
1622
|
"QUEUED",
|
|
1581
1623
|
// Vercel: deployment queued
|
|
1582
1624
|
"BUILDING",
|
|
@@ -1613,11 +1655,47 @@ var envVarSchema = z22.object({
|
|
|
1613
1655
|
key: z22.string(),
|
|
1614
1656
|
value: z22.string()
|
|
1615
1657
|
});
|
|
1658
|
+
var deploymentFilePathSchema = z22.string().min(1, "path is required").max(2048, "path is too long").refine((value) => !value.includes("\0"), "path cannot contain null bytes").refine((value) => !value.includes("\\"), "path must use forward slashes").refine((value) => !value.startsWith("/"), "path must be relative").refine((value) => value.split("/").every((part) => part !== "" && part !== "." && part !== ".."), "path cannot contain empty, current, or parent directory segments");
|
|
1659
|
+
var deploymentManifestFileEntrySchema = z22.object({
|
|
1660
|
+
path: deploymentFilePathSchema,
|
|
1661
|
+
sha: z22.string().regex(/^[a-f0-9]{40}$/i, "sha must be a SHA-1 hex digest"),
|
|
1662
|
+
size: z22.number().int().nonnegative()
|
|
1663
|
+
});
|
|
1664
|
+
var deploymentManifestFileSchema = deploymentManifestFileEntrySchema.extend({
|
|
1665
|
+
fileId: z22.string().uuid(),
|
|
1666
|
+
uploadedAt: z22.string().datetime().nullable()
|
|
1667
|
+
});
|
|
1616
1668
|
var createDeploymentResponseSchema = z22.object({
|
|
1617
1669
|
id: z22.string().uuid(),
|
|
1618
1670
|
uploadUrl: z22.string().url(),
|
|
1619
1671
|
uploadFields: z22.record(z22.string())
|
|
1620
|
-
|
|
1672
|
+
});
|
|
1673
|
+
var createDirectDeploymentResponseSchema = z22.object({
|
|
1674
|
+
id: z22.string().uuid(),
|
|
1675
|
+
status: deploymentSchema.shape.status
|
|
1676
|
+
});
|
|
1677
|
+
var createDeploymentManifestRequestSchema = z22.object({
|
|
1678
|
+
files: z22.array(deploymentManifestFileEntrySchema).min(1)
|
|
1679
|
+
}).superRefine(({ files }, ctx) => {
|
|
1680
|
+
const firstSeenByPath = /* @__PURE__ */ new Map();
|
|
1681
|
+
files.forEach((file, index) => {
|
|
1682
|
+
const existingIndex = firstSeenByPath.get(file.path);
|
|
1683
|
+
if (existingIndex !== void 0) {
|
|
1684
|
+
ctx.addIssue({
|
|
1685
|
+
code: z22.ZodIssueCode.custom,
|
|
1686
|
+
message: "duplicate file path",
|
|
1687
|
+
path: ["files", index, "path"]
|
|
1688
|
+
});
|
|
1689
|
+
return;
|
|
1690
|
+
}
|
|
1691
|
+
firstSeenByPath.set(file.path, index);
|
|
1692
|
+
});
|
|
1693
|
+
});
|
|
1694
|
+
var createDeploymentManifestResponseSchema = z22.object({
|
|
1695
|
+
files: z22.array(deploymentManifestFileSchema)
|
|
1696
|
+
});
|
|
1697
|
+
var uploadDeploymentFileResponseSchema = deploymentManifestFileSchema.extend({
|
|
1698
|
+
uploadedAt: z22.string().datetime()
|
|
1621
1699
|
});
|
|
1622
1700
|
var startDeploymentRequestSchema = z22.object({
|
|
1623
1701
|
projectSettings: projectSettingsSchema.optional(),
|
|
@@ -2562,23 +2640,320 @@ function registerFunctionTools(ctx) {
|
|
|
2562
2640
|
// src/shared/tools/deployment.ts
|
|
2563
2641
|
import { z as z29 } from "zod";
|
|
2564
2642
|
import fetch6 from "node-fetch";
|
|
2643
|
+
import { createHash } from "crypto";
|
|
2565
2644
|
import { promises as fs3, createWriteStream, createReadStream } from "fs";
|
|
2566
2645
|
import { tmpdir as tmpdir2 } from "os";
|
|
2567
|
-
import { join as join2 } from "path";
|
|
2646
|
+
import { join as join2, relative, sep } from "path";
|
|
2568
2647
|
import archiver from "archiver";
|
|
2569
2648
|
import FormData2 from "form-data";
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2649
|
+
var DIRECT_DEPLOYMENT_MIN_VERSION = "2.0.4";
|
|
2650
|
+
var EXCLUDED_DEPLOYMENT_SEGMENTS = /* @__PURE__ */ new Set(["node_modules", ".git", ".next", "dist", "build", ".insforge"]);
|
|
2651
|
+
var DirectDeploymentUnsupportedError = class extends Error {
|
|
2652
|
+
constructor() {
|
|
2653
|
+
super("Direct deployment endpoints are not available on this backend");
|
|
2654
|
+
this.name = "DirectDeploymentUnsupportedError";
|
|
2573
2655
|
}
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2656
|
+
};
|
|
2657
|
+
function isAbsoluteSourcePath(sourceDirectory) {
|
|
2658
|
+
return sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
|
|
2659
|
+
}
|
|
2660
|
+
function supportsDirectDeploymentVersion(backendVersion) {
|
|
2661
|
+
const cleanVersion = backendVersion.replace(/^v/, "").split("-")[0];
|
|
2662
|
+
const [major = 0, minor = 0, patch = 0] = cleanVersion.split(".").map(Number);
|
|
2663
|
+
const [minMajor, minMinor, minPatch] = DIRECT_DEPLOYMENT_MIN_VERSION.split(".").map(Number);
|
|
2664
|
+
if (major !== minMajor) return major > minMajor;
|
|
2665
|
+
if (minor !== minMinor) return minor > minMinor;
|
|
2666
|
+
return patch >= minPatch;
|
|
2667
|
+
}
|
|
2668
|
+
function shouldExcludeDeploymentPath(normalizedName) {
|
|
2669
|
+
const segments = normalizedName.split("/");
|
|
2670
|
+
if (segments.some((segment) => segment === ".env" || segment.startsWith(".env."))) {
|
|
2671
|
+
return true;
|
|
2672
|
+
}
|
|
2673
|
+
if (segments.some((segment) => EXCLUDED_DEPLOYMENT_SEGMENTS.has(segment))) {
|
|
2674
|
+
return true;
|
|
2675
|
+
}
|
|
2676
|
+
return normalizedName === ".DS_Store" || normalizedName.endsWith("/.DS_Store") || normalizedName.endsWith(".log");
|
|
2677
|
+
}
|
|
2678
|
+
function normalizeRelativePath(rootDirectory, absolutePath) {
|
|
2679
|
+
return relative(rootDirectory, absolutePath).split(sep).join("/").replace(/\\/g, "/");
|
|
2680
|
+
}
|
|
2681
|
+
async function hashFile(filePath) {
|
|
2682
|
+
const hash = createHash("sha1");
|
|
2683
|
+
let size = 0;
|
|
2684
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
2685
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
2686
|
+
size += buffer.length;
|
|
2687
|
+
hash.update(buffer);
|
|
2688
|
+
}
|
|
2689
|
+
return { sha: hash.digest("hex"), size };
|
|
2690
|
+
}
|
|
2691
|
+
function parseCreateDeploymentResponse(obj) {
|
|
2692
|
+
const result = createDeploymentResponseSchema.safeParse(obj);
|
|
2693
|
+
if (!result.success) {
|
|
2694
|
+
throw new Error("Unexpected response format from deployments endpoint");
|
|
2695
|
+
}
|
|
2696
|
+
return result.data;
|
|
2697
|
+
}
|
|
2698
|
+
function parseCreateDirectDeploymentResponse(obj) {
|
|
2699
|
+
const result = createDirectDeploymentResponseSchema.safeParse(obj);
|
|
2700
|
+
if (!result.success) {
|
|
2701
|
+
throw new Error("Unexpected response format from direct deployments endpoint");
|
|
2702
|
+
}
|
|
2703
|
+
return result.data;
|
|
2704
|
+
}
|
|
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");
|
|
2709
|
+
}
|
|
2710
|
+
return result.data;
|
|
2711
|
+
}
|
|
2712
|
+
async function collectDeploymentFiles(sourceDirectory) {
|
|
2713
|
+
const files = [];
|
|
2714
|
+
async function walk(currentDirectory) {
|
|
2715
|
+
const entries = await fs3.readdir(currentDirectory, { withFileTypes: true });
|
|
2716
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2717
|
+
for (const entry of entries) {
|
|
2718
|
+
const absolutePath = join2(currentDirectory, entry.name);
|
|
2719
|
+
const normalizedPath = normalizeRelativePath(sourceDirectory, absolutePath);
|
|
2720
|
+
if (!normalizedPath || shouldExcludeDeploymentPath(normalizedPath)) {
|
|
2721
|
+
continue;
|
|
2722
|
+
}
|
|
2723
|
+
if (entry.isDirectory()) {
|
|
2724
|
+
await walk(absolutePath);
|
|
2725
|
+
continue;
|
|
2726
|
+
}
|
|
2727
|
+
if (!entry.isFile()) {
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
const { sha, size } = await hashFile(absolutePath);
|
|
2731
|
+
files.push({
|
|
2732
|
+
absolutePath,
|
|
2733
|
+
path: normalizedPath,
|
|
2734
|
+
sha,
|
|
2735
|
+
size
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
}
|
|
2739
|
+
await walk(sourceDirectory);
|
|
2740
|
+
return files;
|
|
2741
|
+
}
|
|
2742
|
+
function buildStartBody(input) {
|
|
2743
|
+
const startBody = {};
|
|
2744
|
+
if (input.projectSettings) startBody.projectSettings = input.projectSettings;
|
|
2745
|
+
if (input.envVars) startBody.envVars = input.envVars;
|
|
2746
|
+
if (input.meta) startBody.meta = input.meta;
|
|
2747
|
+
return startBody;
|
|
2748
|
+
}
|
|
2749
|
+
async function createDirectDeploymentSession(API_BASE_URL, apiKey) {
|
|
2750
|
+
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
|
+
method: "POST",
|
|
2766
|
+
headers: {
|
|
2767
|
+
"x-api-key": apiKey,
|
|
2768
|
+
"Content-Type": "application/json"
|
|
2769
|
+
},
|
|
2770
|
+
body: JSON.stringify({ files })
|
|
2771
|
+
});
|
|
2772
|
+
if (response.status === 404) {
|
|
2773
|
+
throw new DirectDeploymentUnsupportedError();
|
|
2774
|
+
}
|
|
2775
|
+
const result = await handleApiResponse(response);
|
|
2776
|
+
return parseCreateDeploymentManifestResponse(result);
|
|
2777
|
+
}
|
|
2778
|
+
async function uploadDeploymentFileContent(API_BASE_URL, apiKey, deploymentId, file, localFile) {
|
|
2779
|
+
const response = await fetch6(
|
|
2780
|
+
`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/files/${encodeURIComponent(file.fileId)}/content`,
|
|
2781
|
+
{
|
|
2782
|
+
method: "PUT",
|
|
2783
|
+
headers: {
|
|
2784
|
+
"x-api-key": apiKey,
|
|
2785
|
+
"Content-Type": "application/octet-stream",
|
|
2786
|
+
"Content-Length": String(localFile.size)
|
|
2787
|
+
},
|
|
2788
|
+
body: createReadStream(localFile.absolutePath)
|
|
2789
|
+
}
|
|
2790
|
+
);
|
|
2791
|
+
if (response.status === 404) {
|
|
2792
|
+
throw new DirectDeploymentUnsupportedError();
|
|
2793
|
+
}
|
|
2794
|
+
await handleApiResponse(response);
|
|
2795
|
+
}
|
|
2796
|
+
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
|
+
});
|
|
2805
|
+
return handleApiResponse(response);
|
|
2806
|
+
}
|
|
2807
|
+
async function deployDirect(API_BASE_URL, apiKey, sourceDirectory, startBody) {
|
|
2808
|
+
const files = await collectDeploymentFiles(sourceDirectory);
|
|
2809
|
+
if (files.length === 0) {
|
|
2810
|
+
throw new Error("No deployable files found in source directory after applying exclusions.");
|
|
2811
|
+
}
|
|
2812
|
+
const createResult = await createDirectDeploymentSession(API_BASE_URL, apiKey);
|
|
2813
|
+
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);
|
|
2823
|
+
if (!localFile) {
|
|
2824
|
+
throw new Error(`Manifest response included unknown file path: ${manifestFile.path}`);
|
|
2825
|
+
}
|
|
2826
|
+
await uploadDeploymentFileContent(API_BASE_URL, apiKey, deploymentId, manifestFile, localFile);
|
|
2827
|
+
}
|
|
2828
|
+
const startResult = await startDeployment(API_BASE_URL, apiKey, deploymentId, startBody);
|
|
2829
|
+
return { deploymentId, fileCount: files.length, startResult };
|
|
2830
|
+
}
|
|
2831
|
+
function buildRemoteDirectUploadInstructions(API_BASE_URL, sourceDirectory, deploymentId) {
|
|
2832
|
+
const escapedDir = shellEsc(sourceDirectory);
|
|
2833
|
+
const apiBaseUrlJson = JSON.stringify(API_BASE_URL);
|
|
2834
|
+
const deploymentIdJson = JSON.stringify(deploymentId);
|
|
2835
|
+
return `Deployment prepared successfully using direct upload. Deployment ID: ${deploymentId}
|
|
2836
|
+
|
|
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.
|
|
2838
|
+
|
|
2839
|
+
\`\`\`bash
|
|
2840
|
+
cd ${escapedDir}
|
|
2841
|
+
INSFORGE_API_KEY="\${INSFORGE_API_KEY:?Set INSFORGE_API_KEY to your InsForge API key}" node --input-type=module <<'NODE'
|
|
2842
|
+
const { createHash } = await import('node:crypto');
|
|
2843
|
+
const { createReadStream } = await import('node:fs');
|
|
2844
|
+
const fs = await import('node:fs/promises');
|
|
2845
|
+
const path = await import('node:path');
|
|
2846
|
+
|
|
2847
|
+
const API_BASE_URL = ${apiBaseUrlJson};
|
|
2848
|
+
const API_KEY = process.env.INSFORGE_API_KEY;
|
|
2849
|
+
const deploymentId = ${deploymentIdJson};
|
|
2850
|
+
const EXCLUDED_SEGMENTS = new Set(['node_modules', '.git', '.next', 'dist', 'build', '.insforge']);
|
|
2851
|
+
|
|
2852
|
+
function shouldExcludeDeploymentPath(normalizedName) {
|
|
2853
|
+
const segments = normalizedName.split('/');
|
|
2854
|
+
if (segments.some((segment) => segment === '.env' || segment.startsWith('.env.'))) return true;
|
|
2855
|
+
if (segments.some((segment) => EXCLUDED_SEGMENTS.has(segment))) return true;
|
|
2856
|
+
return normalizedName === '.DS_Store' || normalizedName.endsWith('/.DS_Store') || normalizedName.endsWith('.log');
|
|
2857
|
+
}
|
|
2858
|
+
|
|
2859
|
+
async function readJsonResponse(response) {
|
|
2860
|
+
const text = await response.text();
|
|
2861
|
+
const data = text ? JSON.parse(text) : null;
|
|
2862
|
+
if (!response.ok) {
|
|
2863
|
+
throw new Error(data?.message || data?.error || \`Request failed with status \${response.status}\`);
|
|
2864
|
+
}
|
|
2865
|
+
return data;
|
|
2866
|
+
}
|
|
2867
|
+
|
|
2868
|
+
async function api(pathname, init = {}) {
|
|
2869
|
+
const headers = {
|
|
2870
|
+
'x-api-key': API_KEY,
|
|
2871
|
+
...(init.headers || {}),
|
|
2872
|
+
};
|
|
2873
|
+
const response = await fetch(\`\${API_BASE_URL}\${pathname}\`, { ...init, headers });
|
|
2874
|
+
return readJsonResponse(response);
|
|
2875
|
+
}
|
|
2876
|
+
|
|
2877
|
+
async function hashFile(filePath) {
|
|
2878
|
+
const hash = createHash('sha1');
|
|
2879
|
+
let size = 0;
|
|
2880
|
+
for await (const chunk of createReadStream(filePath)) {
|
|
2881
|
+
size += chunk.length;
|
|
2882
|
+
hash.update(chunk);
|
|
2883
|
+
}
|
|
2884
|
+
return { sha: hash.digest('hex'), size };
|
|
2885
|
+
}
|
|
2886
|
+
|
|
2887
|
+
async function collectFiles(rootDirectory) {
|
|
2888
|
+
const files = [];
|
|
2889
|
+
|
|
2890
|
+
async function walk(currentDirectory) {
|
|
2891
|
+
const entries = await fs.readdir(currentDirectory, { withFileTypes: true });
|
|
2892
|
+
entries.sort((a, b) => a.name.localeCompare(b.name));
|
|
2893
|
+
|
|
2894
|
+
for (const entry of entries) {
|
|
2895
|
+
const absolutePath = path.join(currentDirectory, entry.name);
|
|
2896
|
+
const normalizedPath = path.relative(rootDirectory, absolutePath).split(path.sep).join('/').replace(/\\\\/g, '/');
|
|
2897
|
+
|
|
2898
|
+
if (!normalizedPath || shouldExcludeDeploymentPath(normalizedPath)) continue;
|
|
2899
|
+
if (entry.isDirectory()) {
|
|
2900
|
+
await walk(absolutePath);
|
|
2901
|
+
continue;
|
|
2902
|
+
}
|
|
2903
|
+
if (!entry.isFile()) continue;
|
|
2904
|
+
|
|
2905
|
+
const { sha, size } = await hashFile(absolutePath);
|
|
2906
|
+
files.push({ absolutePath, path: normalizedPath, sha, size });
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
await walk(rootDirectory);
|
|
2911
|
+
return files;
|
|
2912
|
+
}
|
|
2913
|
+
|
|
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\`,
|
|
2933
|
+
{
|
|
2934
|
+
method: 'PUT',
|
|
2935
|
+
headers: {
|
|
2936
|
+
'x-api-key': API_KEY,
|
|
2937
|
+
'Content-Type': 'application/octet-stream',
|
|
2938
|
+
'Content-Length': String(localFile.size),
|
|
2939
|
+
},
|
|
2940
|
+
body: createReadStream(localFile.absolutePath),
|
|
2941
|
+
duplex: 'half',
|
|
2942
|
+
}
|
|
2943
|
+
);
|
|
2944
|
+
await readJsonResponse(uploadResponse);
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
console.log(\`Deployment files uploaded. Deployment ID: \${deploymentId}\`);
|
|
2948
|
+
console.log(\`Uploaded \${localFiles.length} files through the direct deployment proxy.\`);
|
|
2949
|
+
NODE
|
|
2950
|
+
\`\`\`
|
|
2951
|
+
|
|
2952
|
+
After the script succeeds, call the \`start-deployment\` tool with the printed deployment ID.`;
|
|
2579
2953
|
}
|
|
2580
2954
|
function registerDeploymentTools(ctx) {
|
|
2581
|
-
const { API_BASE_URL, isRemote, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
|
|
2955
|
+
const { API_BASE_URL, backendVersion, isRemote, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
|
|
2956
|
+
const supportsDirectDeployment = supportsDirectDeploymentVersion(backendVersion);
|
|
2582
2957
|
registerTool(
|
|
2583
2958
|
"get-container-logs",
|
|
2584
2959
|
"Get latest logs from a specific container/service. Use this to help debug problems with your app.",
|
|
@@ -2618,14 +2993,13 @@ function registerDeploymentTools(ctx) {
|
|
|
2618
2993
|
if (isRemote) {
|
|
2619
2994
|
registerTool(
|
|
2620
2995
|
"create-deployment",
|
|
2621
|
-
"Prepare a 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.",
|
|
2622
2997
|
{
|
|
2623
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 "."')
|
|
2624
2999
|
},
|
|
2625
3000
|
withUsageTracking("create-deployment", async ({ sourceDirectory }) => {
|
|
2626
3001
|
try {
|
|
2627
|
-
|
|
2628
|
-
if (!isAbsolutePath) {
|
|
3002
|
+
if (!isAbsoluteSourcePath(sourceDirectory)) {
|
|
2629
3003
|
return {
|
|
2630
3004
|
content: [{
|
|
2631
3005
|
type: "text",
|
|
@@ -2634,6 +3008,21 @@ function registerDeploymentTools(ctx) {
|
|
|
2634
3008
|
isError: true
|
|
2635
3009
|
};
|
|
2636
3010
|
}
|
|
3011
|
+
if (supportsDirectDeployment) {
|
|
3012
|
+
try {
|
|
3013
|
+
const createResult2 = await createDirectDeploymentSession(API_BASE_URL, getApiKey());
|
|
3014
|
+
return {
|
|
3015
|
+
content: [{
|
|
3016
|
+
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
|
+
}
|
|
3025
|
+
}
|
|
2637
3026
|
const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
|
|
2638
3027
|
method: "POST",
|
|
2639
3028
|
headers: {
|
|
@@ -2641,10 +3030,7 @@ function registerDeploymentTools(ctx) {
|
|
|
2641
3030
|
"Content-Type": "application/json"
|
|
2642
3031
|
}
|
|
2643
3032
|
});
|
|
2644
|
-
const createResult = await handleApiResponse(createResponse);
|
|
2645
|
-
if (!isCreateDeploymentResponse(createResult)) {
|
|
2646
|
-
throw new Error("Unexpected response format from deployments endpoint");
|
|
2647
|
-
}
|
|
3033
|
+
const createResult = parseCreateDeploymentResponse(await handleApiResponse(createResponse));
|
|
2648
3034
|
const { id: deploymentId, uploadUrl, uploadFields } = createResult;
|
|
2649
3035
|
const esc = shellEsc;
|
|
2650
3036
|
const curlFields = Object.entries(uploadFields).map(([key, value]) => `-F ${esc(`${key}=${value}`)}`).join(" \\\n ");
|
|
@@ -2656,7 +3042,7 @@ Please execute the following commands locally, then call the \`start-deployment\
|
|
|
2656
3042
|
|
|
2657
3043
|
## Step 1: Zip the source directory
|
|
2658
3044
|
\`\`\`bash
|
|
2659
|
-
cd ${escapedDir} && zip -r ${tmpZip} . -x "node_modules/*" ".git/*" ".next/*" ".env" "
|
|
3045
|
+
cd ${escapedDir} && zip -r ${tmpZip} . -x "node_modules/*" ".git/*" ".next/*" ".env*" "dist/*" "build/*" ".insforge/*" ".DS_Store" "*.log"
|
|
2660
3046
|
\`\`\`
|
|
2661
3047
|
|
|
2662
3048
|
## Step 2: Upload the zip file
|
|
@@ -2725,15 +3111,14 @@ Run each step in order. If any step fails, do not proceed to the next step.`;
|
|
|
2725
3111
|
} else {
|
|
2726
3112
|
registerTool(
|
|
2727
3113
|
"create-deployment",
|
|
2728
|
-
"Deploy source code from a directory.
|
|
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.",
|
|
2729
3115
|
{
|
|
2730
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 "."'),
|
|
2731
3117
|
...startDeploymentRequestSchema.shape
|
|
2732
3118
|
},
|
|
2733
3119
|
withUsageTracking("create-deployment", async ({ sourceDirectory, projectSettings, envVars, meta }) => {
|
|
2734
3120
|
try {
|
|
2735
|
-
|
|
2736
|
-
if (!isAbsolutePath) {
|
|
3121
|
+
if (!isAbsoluteSourcePath(sourceDirectory)) {
|
|
2737
3122
|
return {
|
|
2738
3123
|
content: [{
|
|
2739
3124
|
type: "text",
|
|
@@ -2763,6 +3148,26 @@ Run each step in order. If any step fails, do not proceed to the next step.`;
|
|
|
2763
3148
|
};
|
|
2764
3149
|
}
|
|
2765
3150
|
const resolvedSourceDir = sourceDirectory;
|
|
3151
|
+
const startBody = buildStartBody({ projectSettings, envVars, meta });
|
|
3152
|
+
if (supportsDirectDeployment) {
|
|
3153
|
+
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;
|
|
3168
|
+
}
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
2766
3171
|
const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
|
|
2767
3172
|
method: "POST",
|
|
2768
3173
|
headers: {
|
|
@@ -2770,10 +3175,7 @@ Run each step in order. If any step fails, do not proceed to the next step.`;
|
|
|
2770
3175
|
"Content-Type": "application/json"
|
|
2771
3176
|
}
|
|
2772
3177
|
});
|
|
2773
|
-
const createResult = await handleApiResponse(createResponse);
|
|
2774
|
-
if (!isCreateDeploymentResponse(createResult)) {
|
|
2775
|
-
throw new Error("Unexpected response format from deployments endpoint");
|
|
2776
|
-
}
|
|
3178
|
+
const createResult = parseCreateDeploymentResponse(await handleApiResponse(createResponse));
|
|
2777
3179
|
const { id: deploymentId, uploadUrl, uploadFields } = createResult;
|
|
2778
3180
|
const tmpZipPath = join2(tmpdir2(), `insforge-deploy-${deploymentId}.zip`);
|
|
2779
3181
|
try {
|
|
@@ -2783,15 +3185,9 @@ Run each step in order. If any step fails, do not proceed to the next step.`;
|
|
|
2783
3185
|
output.on("close", resolve);
|
|
2784
3186
|
output.on("error", reject);
|
|
2785
3187
|
archive.on("error", reject);
|
|
2786
|
-
const excludePatterns = ["node_modules", ".git", ".next", ".env", ".env.local", "dist", "build", ".DS_Store"];
|
|
2787
3188
|
archive.directory(resolvedSourceDir, false, (entry) => {
|
|
2788
3189
|
const normalizedName = entry.name.replace(/\\/g, "/");
|
|
2789
|
-
|
|
2790
|
-
if (normalizedName.startsWith(pattern + "/") || normalizedName === pattern || normalizedName.endsWith("/" + pattern) || normalizedName.includes("/" + pattern + "/")) {
|
|
2791
|
-
return false;
|
|
2792
|
-
}
|
|
2793
|
-
}
|
|
2794
|
-
if (normalizedName.endsWith(".log")) return false;
|
|
3190
|
+
if (shouldExcludeDeploymentPath(normalizedName)) return false;
|
|
2795
3191
|
return entry;
|
|
2796
3192
|
});
|
|
2797
3193
|
archive.pipe(output);
|
|
@@ -2819,19 +3215,7 @@ Run each step in order. If any step fails, do not proceed to the next step.`;
|
|
|
2819
3215
|
} finally {
|
|
2820
3216
|
await fs3.rm(tmpZipPath, { force: true }).catch(() => void 0);
|
|
2821
3217
|
}
|
|
2822
|
-
const
|
|
2823
|
-
if (projectSettings) startBody.projectSettings = projectSettings;
|
|
2824
|
-
if (envVars) startBody.envVars = envVars;
|
|
2825
|
-
if (meta) startBody.meta = meta;
|
|
2826
|
-
const startResponse = await fetch6(`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`, {
|
|
2827
|
-
method: "POST",
|
|
2828
|
-
headers: {
|
|
2829
|
-
"x-api-key": getApiKey(),
|
|
2830
|
-
"Content-Type": "application/json"
|
|
2831
|
-
},
|
|
2832
|
-
body: JSON.stringify(startBody)
|
|
2833
|
-
});
|
|
2834
|
-
const startResult = await handleApiResponse(startResponse);
|
|
3218
|
+
const startResult = await startDeployment(API_BASE_URL, getApiKey(), deploymentId, startBody);
|
|
2835
3219
|
return await addBackgroundContext({
|
|
2836
3220
|
content: [{
|
|
2837
3221
|
type: "text",
|
|
@@ -3004,6 +3388,7 @@ ${result.content}`
|
|
|
3004
3388
|
};
|
|
3005
3389
|
const ctx = {
|
|
3006
3390
|
API_BASE_URL,
|
|
3391
|
+
backendVersion,
|
|
3007
3392
|
isRemote,
|
|
3008
3393
|
registerTool,
|
|
3009
3394
|
withUsageTracking,
|
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.
|
|
3
|
+
"version": "1.2.10-dev.1",
|
|
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.48",
|
|
49
49
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
50
50
|
"archiver": "^7.0.1",
|
|
51
51
|
"commander": "^14.0.0",
|
|
@@ -61,6 +61,9 @@
|
|
|
61
61
|
"brace-expansion": "^5.0.5",
|
|
62
62
|
"glob": "^10.5.0",
|
|
63
63
|
"minimatch": "^9.0.7",
|
|
64
|
+
"@hono/node-server": "^1.19.13",
|
|
65
|
+
"hono": "^4.12.12",
|
|
66
|
+
"lodash": "^4.18.0",
|
|
64
67
|
"rollup": "^4.59.0"
|
|
65
68
|
},
|
|
66
69
|
"devDependencies": {
|
|
@@ -78,7 +81,7 @@
|
|
|
78
81
|
"tsx": "^4.7.0",
|
|
79
82
|
"typescript": "^5.3.3",
|
|
80
83
|
"typescript-eslint": "^8.57.1",
|
|
81
|
-
"vite": "^8.0.
|
|
84
|
+
"vite": "^8.0.5",
|
|
82
85
|
"vitest": "^4.1.0"
|
|
83
86
|
}
|
|
84
87
|
}
|