@insforge/mcp 1.1.7-dev.2 → 1.1.7-dev.20

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.
@@ -4,6 +4,8 @@
4
4
  import { z as z14 } from "zod";
5
5
  import fetch2 from "node-fetch";
6
6
  import { promises as fs } from "fs";
7
+ import { exec } from "child_process";
8
+ import { promisify } from "util";
7
9
 
8
10
  // src/shared/response-handler.ts
9
11
  async function handleApiResponse(response) {
@@ -161,8 +163,8 @@ var deleteTableResponse = z2.object({
161
163
  });
162
164
  var rawSQLRequestSchema = z2.object({
163
165
  query: z2.string().min(1, "Query is required"),
164
- params: z2.array(z2.any()).optional()
165
- // z.any() generates valid JSON Schema with items: {}
166
+ params: z2.array(z2.unknown()).optional()
167
+ // z.unknown() generates JSON Schema with items: {}
166
168
  });
167
169
  var rawSQLResponseSchema = z2.object({
168
170
  rows: z2.array(z2.record(z2.string(), z2.unknown())),
@@ -735,10 +737,75 @@ var functionUpdateRequestSchema = z13.object({
735
737
 
736
738
  // src/shared/tools.ts
737
739
  import FormData from "form-data";
740
+ var execAsync = promisify(exec);
741
+ var TOOL_VERSION_REQUIREMENTS = {
742
+ "upsert-schedule": "1.1.1",
743
+ // 'get-schedules': '1.1.1',
744
+ // 'get-schedule-logs': '1.1.1',
745
+ "delete-schedule": "1.1.1"
746
+ };
738
747
  function registerInsforgeTools(server, config = {}) {
739
748
  const GLOBAL_API_KEY = config.apiKey || process.env.API_KEY || "";
740
749
  const API_BASE_URL = config.apiBaseUrl || process.env.API_BASE_URL || "http://localhost:7130";
741
750
  const usageTracker = new UsageTracker(API_BASE_URL, GLOBAL_API_KEY);
751
+ let versionCache = null;
752
+ const VERSION_CACHE_TTL = 5 * 60 * 1e3;
753
+ async function getBackendVersion() {
754
+ const now = Date.now();
755
+ if (versionCache && now - versionCache.timestamp < VERSION_CACHE_TTL) {
756
+ return versionCache.version;
757
+ }
758
+ try {
759
+ const response = await fetch2(`${API_BASE_URL}/api/health`, {
760
+ method: "GET",
761
+ headers: {
762
+ "Content-Type": "application/json"
763
+ }
764
+ });
765
+ if (!response.ok) {
766
+ throw new Error(`Health check failed with status ${response.status}`);
767
+ }
768
+ const health = await response.json();
769
+ versionCache = {
770
+ version: health.version,
771
+ timestamp: now
772
+ };
773
+ return health.version;
774
+ } catch (error) {
775
+ const errMsg = error instanceof Error ? error.message : "Unknown error";
776
+ throw new Error(`Failed to fetch backend version: ${errMsg}`);
777
+ }
778
+ }
779
+ function compareVersions(v1, v2) {
780
+ const parts1 = v1.split(".").map(Number);
781
+ const parts2 = v2.split(".").map(Number);
782
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
783
+ const part1 = parts1[i] || 0;
784
+ const part2 = parts2[i] || 0;
785
+ if (part1 > part2) return 1;
786
+ if (part1 < part2) return -1;
787
+ }
788
+ return 0;
789
+ }
790
+ async function checkToolVersion(toolName) {
791
+ const requiredVersion = TOOL_VERSION_REQUIREMENTS[toolName];
792
+ if (!requiredVersion) {
793
+ return;
794
+ }
795
+ try {
796
+ const currentVersion = await getBackendVersion();
797
+ if (compareVersions(currentVersion, requiredVersion) < 0) {
798
+ throw new Error(
799
+ `Tool '${toolName}' requires backend version ${requiredVersion} or higher, but current version is ${currentVersion}. Please upgrade your Insforge backend server.`
800
+ );
801
+ }
802
+ } catch (error) {
803
+ if (error instanceof Error && error.message.includes("requires backend version")) {
804
+ throw error;
805
+ }
806
+ console.warn(`Warning: Could not verify backend version for tool '${toolName}': ${error instanceof Error ? error.message : "Unknown error"}`);
807
+ }
808
+ }
742
809
  async function trackToolUsage(toolName, success = true) {
743
810
  if (GLOBAL_API_KEY) {
744
811
  await usageTracker.trackUsage(toolName, success);
@@ -774,6 +841,7 @@ function registerInsforgeTools(server, config = {}) {
774
841
  if (result && typeof result === "object" && "content" in result) {
775
842
  let content = result.content;
776
843
  content = content.replace(/http:\/\/localhost:7130/g, API_BASE_URL);
844
+ content = content.replace(/https:\/\/your-app\.region\.insforge\.app/g, API_BASE_URL);
777
845
  return content;
778
846
  }
779
847
  throw new Error("Invalid response format from documentation endpoint");
@@ -783,12 +851,16 @@ function registerInsforgeTools(server, config = {}) {
783
851
  }
784
852
  };
785
853
  server.tool(
786
- "get-instructions",
787
- "Instruction Essential backend setup tool. <critical>MANDATORY: You MUST use this tool FIRST before attempting any backend operations. Contains required API endpoints, authentication details, and setup instructions.</critical>",
788
- {},
789
- withUsageTracking("get-instructions", async () => {
854
+ "fetch-docs",
855
+ 'Fetch Insforge documentation. Use "instructions" for essential backend setup (MANDATORY FIRST), or select specific SDK docs for database, auth, storage, functions, or AI integration.',
856
+ {
857
+ docType: z14.enum(["instructions", "db-sdk", "storage-sdk", "functions-sdk", "ai-integration-sdk", "auth-components-react", "auth-components-react-router"]).describe(
858
+ 'Documentation type: "instructions" (essential backend setup - use FIRST), "db-sdk" (database operations), "storage-sdk" (file storage), "functions-sdk" (edge functions), "ai-integration-sdk" (AI features), "auth-components-react" (authentication components for React+Vite applications), "auth-components-react-router" (authentication components for React + React Router applications),'
859
+ )
860
+ },
861
+ withUsageTracking("fetch-docs", async ({ docType }) => {
790
862
  try {
791
- const content = await fetchDocumentation("instructions");
863
+ const content = await fetchDocumentation(docType);
792
864
  return {
793
865
  content: [
794
866
  {
@@ -797,10 +869,62 @@ function registerInsforgeTools(server, config = {}) {
797
869
  }
798
870
  ]
799
871
  };
872
+ } catch (error) {
873
+ if (error instanceof Error && error.message.includes("404")) {
874
+ try {
875
+ const instructionsContent = await fetchDocumentation("instructions");
876
+ return {
877
+ content: [{ type: "text", text: instructionsContent }]
878
+ };
879
+ } catch (fallbackError) {
880
+ const fallbackErrMsg = fallbackError instanceof Error ? fallbackError.message : "Unknown error occurred";
881
+ return {
882
+ content: [{ type: "text", text: `Error fetching documentation: ${fallbackErrMsg}` }]
883
+ };
884
+ }
885
+ }
886
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
887
+ return {
888
+ content: [{ type: "text", text: `Error fetching ${docType} documentation: ${errMsg}` }]
889
+ };
890
+ }
891
+ })
892
+ );
893
+ server.tool(
894
+ "get-anon-key",
895
+ "Generate an anonymous JWT token that never expires. Requires admin API key. Use this for client-side applications that need public access.",
896
+ {
897
+ apiKey: z14.string().optional().describe("API key for authentication (optional if provided via --api_key)")
898
+ },
899
+ withUsageTracking("get-anon-key", async ({ apiKey }) => {
900
+ try {
901
+ const actualApiKey = getApiKey(apiKey);
902
+ const response = await fetch2(`${API_BASE_URL}/api/auth/tokens/anon`, {
903
+ method: "POST",
904
+ headers: {
905
+ "x-api-key": actualApiKey,
906
+ "Content-Type": "application/json"
907
+ }
908
+ });
909
+ const result = await handleApiResponse(response);
910
+ return {
911
+ content: [
912
+ {
913
+ type: "text",
914
+ text: formatSuccessMessage("Anonymous token generated", result)
915
+ }
916
+ ]
917
+ };
800
918
  } catch (error) {
801
919
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
802
920
  return {
803
- content: [{ type: "text", text: `Error: ${errMsg}` }]
921
+ content: [
922
+ {
923
+ type: "text",
924
+ text: `Error generating anonymous token: ${errMsg}`
925
+ }
926
+ ],
927
+ isError: true
804
928
  };
805
929
  }
806
930
  })
@@ -929,9 +1053,73 @@ ${JSON.stringify(metadata, null, 2)}`
929
1053
  }
930
1054
  })
931
1055
  );
1056
+ server.tool(
1057
+ "download-template",
1058
+ "CRITICAL: MANDATORY FIRST STEP for all new InsForge projects. Download pre-configured starter template (React/Next.js/React Router). ALWAYS use this tool BEFORE any other setup when starting a new project.",
1059
+ {
1060
+ frame: z14.enum(["react"]).describe("Framework to use for the template (currently only React is supported)"),
1061
+ projectName: z14.string().optional().describe('Name for the project directory (optional, defaults to "insforge-react")')
1062
+ },
1063
+ withUsageTracking("download-template", async ({ frame, projectName }) => {
1064
+ try {
1065
+ const response = await fetch2(`${API_BASE_URL}/api/auth/tokens/anon`, {
1066
+ method: "POST",
1067
+ headers: {
1068
+ "x-api-key": getApiKey(),
1069
+ "Content-Type": "application/json"
1070
+ }
1071
+ });
1072
+ const result = await handleApiResponse(response);
1073
+ const anonKey = result.accessToken;
1074
+ if (!anonKey) {
1075
+ throw new Error("Failed to retrieve anon key from backend");
1076
+ }
1077
+ const targetDir = projectName || `insforge-${frame}`;
1078
+ const command = `npx create-insforge-app ${targetDir} --frame ${frame} --base-url ${API_BASE_URL} --anon-key ${anonKey}`;
1079
+ const { stdout, stderr } = await execAsync(command, {
1080
+ maxBuffer: 10 * 1024 * 1024
1081
+ // 10MB buffer
1082
+ });
1083
+ const output = stdout || stderr || "";
1084
+ if (output.toLowerCase().includes("error") && !output.includes("successfully")) {
1085
+ throw new Error(`Failed to download template: ${output}`);
1086
+ }
1087
+ return {
1088
+ content: [
1089
+ {
1090
+ type: "text",
1091
+ text: formatSuccessMessage(
1092
+ `React template downloaded to ${targetDir}`,
1093
+ {
1094
+ targetDir,
1095
+ baseUrl: API_BASE_URL,
1096
+ nextSteps: [
1097
+ `cd ${targetDir}`,
1098
+ `npm install`,
1099
+ `npm run dev`
1100
+ ]
1101
+ }
1102
+ )
1103
+ }
1104
+ ]
1105
+ };
1106
+ } catch (error) {
1107
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1108
+ return {
1109
+ content: [
1110
+ {
1111
+ type: "text",
1112
+ text: `Error downloading template: ${errMsg}`
1113
+ }
1114
+ ],
1115
+ isError: true
1116
+ };
1117
+ }
1118
+ })
1119
+ );
932
1120
  server.tool(
933
1121
  "bulk-upsert",
934
- "Bulk insert or update data from CSV or JSON file. Supports upsert operations with a unique key.",
1122
+ "Bulk insert or updallet data from CSV or JSON file. Supports upsert operations with a unique key.",
935
1123
  {
936
1124
  apiKey: z14.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
937
1125
  ...bulkUpsertRequestSchema.shape,
@@ -1313,12 +1501,20 @@ ${JSON.stringify(metadata, null, 2)}`
1313
1501
  const actualApiKey = getApiKey(apiKey);
1314
1502
  const queryParams = new URLSearchParams();
1315
1503
  if (limit) queryParams.append("limit", limit.toString());
1316
- const response = await fetch2(`${API_BASE_URL}/api/logs/analytics/${source}?${queryParams}`, {
1504
+ let response = await fetch2(`${API_BASE_URL}/api/logs/${source}?${queryParams}`, {
1317
1505
  method: "GET",
1318
1506
  headers: {
1319
1507
  "x-api-key": actualApiKey
1320
1508
  }
1321
1509
  });
1510
+ if (response.status === 404) {
1511
+ response = await fetch2(`${API_BASE_URL}/api/logs/analytics/${source}?${queryParams}`, {
1512
+ method: "GET",
1513
+ headers: {
1514
+ "x-api-key": actualApiKey
1515
+ }
1516
+ });
1517
+ }
1322
1518
  const result = await handleApiResponse(response);
1323
1519
  return {
1324
1520
  content: [
@@ -1342,10 +1538,114 @@ ${JSON.stringify(metadata, null, 2)}`
1342
1538
  }
1343
1539
  })
1344
1540
  );
1541
+ server.tool(
1542
+ "upsert-schedule",
1543
+ "Create or update a cron job schedule. If id is provided, updates existing schedule; otherwise creates a new one.",
1544
+ {
1545
+ apiKey: z14.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1546
+ id: z14.string().uuid().optional().describe("The UUID of the schedule to update. If omitted, a new schedule will be created."),
1547
+ name: z14.string().min(3).describe("Schedule name (at least 3 characters)"),
1548
+ cronSchedule: z14.string().describe('Cron schedule format (5 or 6 parts, e.g., "0 */2 * * *" for every 2 hours)'),
1549
+ functionUrl: z14.string().url().describe("The URL to call when the schedule triggers"),
1550
+ httpMethod: z14.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).optional().default("POST").describe("HTTP method to use"),
1551
+ headers: z14.record(z14.string()).optional().describe('HTTP headers. Values starting with "secret:" will be resolved from secrets store.'),
1552
+ body: z14.record(z14.unknown()).optional().describe("JSON body to send with the request")
1553
+ },
1554
+ withUsageTracking("upsert-schedule", async ({ apiKey, id, name, cronSchedule, functionUrl, httpMethod, headers, body }) => {
1555
+ try {
1556
+ await checkToolVersion("upsert-schedule");
1557
+ const actualApiKey = getApiKey(apiKey);
1558
+ const requestBody = {
1559
+ name,
1560
+ cronSchedule,
1561
+ functionUrl,
1562
+ httpMethod: httpMethod || "POST"
1563
+ };
1564
+ if (id) {
1565
+ requestBody.id = id;
1566
+ }
1567
+ if (headers) {
1568
+ requestBody.headers = headers;
1569
+ }
1570
+ if (body) {
1571
+ requestBody.body = body;
1572
+ }
1573
+ const response = await fetch2(`${API_BASE_URL}/api/schedules`, {
1574
+ method: "POST",
1575
+ headers: {
1576
+ "x-api-key": actualApiKey,
1577
+ "Content-Type": "application/json"
1578
+ },
1579
+ body: JSON.stringify(requestBody)
1580
+ });
1581
+ const result = await handleApiResponse(response);
1582
+ const action = id ? "updated" : "created";
1583
+ return {
1584
+ content: [
1585
+ {
1586
+ type: "text",
1587
+ text: formatSuccessMessage(`Schedule '${name}' ${action} successfully`, result)
1588
+ }
1589
+ ]
1590
+ };
1591
+ } catch (error) {
1592
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1593
+ return {
1594
+ content: [
1595
+ {
1596
+ type: "text",
1597
+ text: `Error upserting schedule: ${errMsg}`
1598
+ }
1599
+ ],
1600
+ isError: true
1601
+ };
1602
+ }
1603
+ })
1604
+ );
1605
+ server.tool(
1606
+ "delete-schedule",
1607
+ "Delete a cron job schedule permanently",
1608
+ {
1609
+ apiKey: z14.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1610
+ scheduleId: z14.string().uuid().describe("The UUID of the schedule to delete")
1611
+ },
1612
+ withUsageTracking("delete-schedule", async ({ apiKey, scheduleId }) => {
1613
+ try {
1614
+ await checkToolVersion("delete-schedule");
1615
+ const actualApiKey = getApiKey(apiKey);
1616
+ const response = await fetch2(`${API_BASE_URL}/api/schedules/${scheduleId}`, {
1617
+ method: "DELETE",
1618
+ headers: {
1619
+ "x-api-key": actualApiKey
1620
+ }
1621
+ });
1622
+ const result = await handleApiResponse(response);
1623
+ return {
1624
+ content: [
1625
+ {
1626
+ type: "text",
1627
+ text: formatSuccessMessage(`Schedule ${scheduleId} deleted successfully`, result)
1628
+ }
1629
+ ]
1630
+ };
1631
+ } catch (error) {
1632
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1633
+ return {
1634
+ content: [
1635
+ {
1636
+ type: "text",
1637
+ text: `Error deleting schedule: ${errMsg}`
1638
+ }
1639
+ ],
1640
+ isError: true
1641
+ };
1642
+ }
1643
+ })
1644
+ );
1345
1645
  return {
1346
1646
  apiKey: GLOBAL_API_KEY,
1347
1647
  apiBaseUrl: API_BASE_URL,
1348
- toolCount: 14
1648
+ toolCount: 17
1349
1649
  };
1350
1650
  }
1351
1651
 
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  registerInsforgeTools
4
- } from "./chunk-74IVXADC.js";
4
+ } from "./chunk-OLIGYXLN.js";
5
5
 
6
6
  // src/http/server.ts
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  registerInsforgeTools
4
- } from "./chunk-74IVXADC.js";
4
+ } from "./chunk-OLIGYXLN.js";
5
5
 
6
6
  // src/stdio/index.ts
7
7
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
package/package.json CHANGED
@@ -1,7 +1,8 @@
1
1
  {
2
2
  "name": "@insforge/mcp",
3
- "version": "1.1.7-dev.2",
3
+ "version": "1.1.7-dev.20",
4
4
  "description": "MCP (Model Context Protocol) server for Insforge backend-as-a-service",
5
+ "mcpName": "io.github.InsForge/insforge-mcp",
5
6
  "type": "module",
6
7
  "main": "dist/index.js",
7
8
  "bin": {
@@ -31,10 +32,11 @@
31
32
  },
32
33
  "files": [
33
34
  "dist",
34
- "mcp.json"
35
+ "mcp.json",
36
+ "server.json"
35
37
  ],
36
38
  "dependencies": {
37
- "@insforge/shared-schemas": "^1.1.6",
39
+ "@insforge/shared-schemas": "^1.1.7",
38
40
  "@modelcontextprotocol/sdk": "^1.15.1",
39
41
  "@types/express": "^5.0.3",
40
42
  "commander": "^14.0.0",
package/server.json ADDED
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
3
+ "name": "io.github.InsForge/insforge-mcp",
4
+ "title": "Insforge",
5
+ "description": "MCP server for Insforge BaaS - database, auth, storage, edge functions, and container logs",
6
+ "version": "1.1.5",
7
+ "packages": [
8
+ {
9
+ "registryType": "npm",
10
+ "identifier": "@insforge/mcp",
11
+ "version": "1.1.5",
12
+ "transport": {
13
+ "type": "stdio"
14
+ }
15
+ }
16
+ ]
17
+ }