@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.
@@ -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 client to upload zip to S3
1619
+ // Record created, waiting for source zip upload or direct file registration/content
1578
1620
  "UPLOADING",
1579
- // Server is downloading from S3 and uploading to Vercel
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
- // Required for S3 presigned POST (policy, signature, key, etc.)
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
- function isCreateDeploymentResponse(obj) {
2571
- if (typeof obj !== "object" || obj === null) {
2572
- return false;
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
- const value = obj;
2575
- const idOk = "id" in value && (typeof value.id === "string" || typeof value.id === "number");
2576
- const urlOk = "uploadUrl" in value && typeof value.uploadUrl === "string" && value.uploadUrl.length > 0;
2577
- const fieldsOk = "uploadFields" in value && typeof value.uploadFields === "object" && value.uploadFields !== null;
2578
- return idOk && urlOk && fieldsOk;
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 by creating a presigned upload URL. Returns shell commands for the agent to execute locally: zip the source directory and upload to cloud storage. After uploading, call the start-deployment tool to trigger the build.",
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
- const isAbsolutePath = sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
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" ".env.local" "dist/*" "build/*" ".DS_Store" "*.log"
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. This tool zips files, uploads to cloud storage, and triggers deployment with optional environment variables and project settings.",
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
- const isAbsolutePath = sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
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
- for (const pattern of excludePatterns) {
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 startBody = {};
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,
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  registerInsforgeTools
4
- } from "./chunk-YH7HSQYJ.js";
4
+ } from "./chunk-MQZLKWM4.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-YH7HSQYJ.js";
4
+ } from "./chunk-MQZLKWM4.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.9",
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.47",
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.3",
84
+ "vite": "^8.0.5",
82
85
  "vitest": "^4.1.0"
83
86
  }
84
87
  }