@insforge/mcp 1.2.6 → 1.2.8

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.
@@ -1,29 +1,37 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // src/shared/tools.ts
4
- import { z as z23 } from "zod";
5
- import fetch2 from "node-fetch";
6
- import { promises as fs } from "fs";
7
- import { exec } from "child_process";
8
- import { promisify } from "util";
9
- import { tmpdir } from "os";
10
- import archiver from "archiver";
3
+ // src/shared/tools/index.ts
4
+ import fetch7 from "node-fetch";
11
5
 
12
6
  // src/shared/response-handler.ts
7
+ function isErrorResponse(value) {
8
+ if (typeof value !== "object" || value === null) return false;
9
+ const v = value;
10
+ const hasMessage = typeof v.message === "string";
11
+ const hasError = typeof v.error === "string";
12
+ const hasStatusCode = typeof v.statusCode === "number";
13
+ const hasNextAction = !("nextAction" in v) || typeof v.nextAction === "string";
14
+ return (hasMessage || hasError || hasStatusCode) && hasNextAction;
15
+ }
13
16
  async function handleApiResponse(response) {
14
17
  const responseData = await response.json();
15
18
  if (!response.ok) {
16
- const errorData = responseData;
17
- let fullMessage = errorData.message || errorData.error || "Unknown error";
18
- if (errorData.nextAction) {
19
- fullMessage += `. ${errorData.nextAction}`;
19
+ if (isErrorResponse(responseData)) {
20
+ let fullMessage = responseData.message ?? responseData.error ?? "Unknown error";
21
+ if (typeof responseData.nextAction === "string" && responseData.nextAction.length > 0) {
22
+ fullMessage += `. ${responseData.nextAction}`;
23
+ }
24
+ throw new Error(fullMessage);
20
25
  }
21
- throw new Error(fullMessage);
26
+ throw new Error("Unknown error");
22
27
  }
23
28
  return responseData;
24
29
  }
30
+ function hasMessageProperty(value) {
31
+ return typeof value === "object" && value !== null && "message" in value && typeof value.message === "string";
32
+ }
25
33
  function formatSuccessMessage(operation, data) {
26
- if (data && typeof data === "object" && "message" in data) {
34
+ if (hasMessageProperty(data)) {
27
35
  return `${data.message}
28
36
  ${JSON.stringify(data, null, 2)}`;
29
37
  }
@@ -64,6 +72,10 @@ var UsageTracker = class {
64
72
  }
65
73
  };
66
74
 
75
+ // src/shared/tools/docs.ts
76
+ import { z as z25 } from "zod";
77
+ import fetch2 from "node-fetch";
78
+
67
79
  // node_modules/@insforge/shared-schemas/dist/database.schema.js
68
80
  import { z } from "zod";
69
81
  var ColumnType;
@@ -518,10 +530,14 @@ var paginationSchema = z8.object({
518
530
  limit: z8.string().optional(),
519
531
  offset: z8.string().optional()
520
532
  });
533
+ var authOptionsSchema = z8.object({
534
+ emailRedirectTo: z8.string().url().optional()
535
+ }).optional();
521
536
  var createUserRequestSchema = z8.object({
522
537
  email: emailSchema,
523
538
  password: passwordSchema,
524
- name: nameSchema.optional()
539
+ name: nameSchema.optional(),
540
+ options: authOptionsSchema
525
541
  });
526
542
  var createSessionRequestSchema = z8.object({
527
543
  email: emailSchema,
@@ -540,7 +556,8 @@ var updateProfileRequestSchema = z8.object({
540
556
  profile: z8.record(z8.unknown())
541
557
  });
542
558
  var sendVerificationEmailRequestSchema = z8.object({
543
- email: emailSchema
559
+ email: emailSchema,
560
+ options: authOptionsSchema
544
561
  });
545
562
  var verifyEmailRequestSchema = z8.object({
546
563
  email: emailSchema.optional(),
@@ -564,24 +581,33 @@ var createUserResponseSchema = z8.object({
564
581
  accessToken: z8.string().nullable(),
565
582
  requireEmailVerification: z8.boolean().optional(),
566
583
  redirectTo: z8.string().url().optional(),
567
- csrfToken: z8.string().nullable().optional()
584
+ csrfToken: z8.string().nullable().optional(),
585
+ refreshToken: z8.string().optional()
586
+ // For mobile/desktop clients (no cookies)
568
587
  });
569
588
  var createSessionResponseSchema = z8.object({
570
589
  user: userSchema,
571
590
  accessToken: z8.string(),
572
591
  redirectTo: z8.string().url().optional(),
573
- csrfToken: z8.string().nullable().optional()
592
+ csrfToken: z8.string().nullable().optional(),
593
+ refreshToken: z8.string().optional()
594
+ // For mobile/desktop clients (no cookies)
574
595
  });
575
596
  var verifyEmailResponseSchema = z8.object({
576
597
  user: userSchema,
577
598
  accessToken: z8.string(),
578
599
  redirectTo: z8.string().url().optional(),
579
- csrfToken: z8.string().nullable().optional()
600
+ csrfToken: z8.string().nullable().optional(),
601
+ refreshToken: z8.string().optional()
602
+ // For mobile/desktop clients (no cookies)
580
603
  });
581
604
  var refreshSessionResponseSchema = z8.object({
582
605
  accessToken: z8.string(),
583
606
  user: userSchema,
584
- csrfToken: z8.string()
607
+ csrfToken: z8.string().optional(),
608
+ // For web clients (cookie-based)
609
+ refreshToken: z8.string().optional()
610
+ // For mobile/desktop clients (no cookies)
585
611
  });
586
612
  var exchangeResetPasswordTokenResponseSchema = z8.object({
587
613
  token: z8.string(),
@@ -627,6 +653,18 @@ var updateOAuthConfigRequestSchema = oAuthConfigSchema.omit({
627
653
  }).extend({
628
654
  clientSecret: z8.string().optional()
629
655
  }).partial();
656
+ var pkceRegex = /^[A-Za-z0-9._~-]+$/;
657
+ var oAuthInitRequestSchema = z8.object({
658
+ // eslint-disable-next-line @typescript-eslint/naming-convention
659
+ redirect_uri: z8.string().url().optional(),
660
+ // eslint-disable-next-line @typescript-eslint/naming-convention
661
+ code_challenge: z8.string().min(43, "Code challenge must be at least 43 characters").max(128, "Code challenge must be at most 128 characters").regex(pkceRegex, "Code challenge must be base64url encoded")
662
+ });
663
+ var oAuthCodeExchangeRequestSchema = z8.object({
664
+ code: z8.string().min(1, "Exchange code is required"),
665
+ // eslint-disable-next-line @typescript-eslint/naming-convention
666
+ code_verifier: z8.string().min(43, "Code verifier must be at least 43 characters").max(128, "Code verifier must be at most 128 characters").regex(pkceRegex, "Code verifier must be base64url encoded")
667
+ });
630
668
  var listOAuthConfigsResponseSchema = z8.object({
631
669
  data: z8.array(oAuthConfigSchema),
632
670
  count: z8.number()
@@ -919,7 +957,24 @@ var audioContentSchema = z13.object({
919
957
  format: z13.enum(["wav", "mp3", "aiff", "aac", "ogg", "flac", "m4a"])
920
958
  })
921
959
  });
922
- var contentSchema = z13.union([textContentSchema, imageContentSchema, audioContentSchema]);
960
+ var fileContentSchema = z13.object({
961
+ type: z13.literal("file"),
962
+ file: z13.object({
963
+ // Filename with extension (e.g., "document.pdf")
964
+ filename: z13.string(),
965
+ // File data can be:
966
+ // - Public URL: "https://example.com/document.pdf"
967
+ // - Base64 data URL: "data:application/pdf;base64,..."
968
+ // eslint-disable-next-line @typescript-eslint/naming-convention
969
+ file_data: z13.string()
970
+ })
971
+ });
972
+ var contentSchema = z13.union([
973
+ textContentSchema,
974
+ imageContentSchema,
975
+ audioContentSchema,
976
+ fileContentSchema
977
+ ]);
923
978
  var chatMessageSchema = z13.object({
924
979
  role: z13.enum(["user", "assistant", "system"]),
925
980
  // New format: content can be string or array of content parts (OpenAI-compatible)
@@ -927,16 +982,72 @@ var chatMessageSchema = z13.object({
927
982
  // Legacy format: separate images field (deprecated but supported for backward compatibility)
928
983
  images: z13.array(z13.object({ url: z13.string() })).optional()
929
984
  });
985
+ var webSearchPluginSchema = z13.object({
986
+ enabled: z13.boolean(),
987
+ // Engine selection:
988
+ // - "native": Always use provider's built-in web search (OpenAI, Anthropic, Perplexity, xAI)
989
+ // - "exa": Use Exa's search API
990
+ // - undefined: Auto-select (native if available, otherwise Exa)
991
+ engine: z13.enum(["native", "exa"]).optional(),
992
+ // Maximum number of search results (1-10, default: 5)
993
+ maxResults: z13.number().min(1).max(10).optional(),
994
+ // Custom prompt for attaching search results to the message
995
+ searchPrompt: z13.string().optional()
996
+ });
997
+ var fileParserPluginSchema = z13.object({
998
+ enabled: z13.boolean(),
999
+ pdf: z13.object({
1000
+ // PDF processing engine:
1001
+ // - "pdf-text": Best for well-structured PDFs with clear text content (Free)
1002
+ // - "mistral-ocr": Best for scanned documents or PDFs with images ($2 per 1,000 pages)
1003
+ // - "native": Only available for models that support file input natively (charged as input tokens)
1004
+ // If not specified, defaults to native if available, otherwise mistral-ocr
1005
+ engine: z13.enum(["pdf-text", "mistral-ocr", "native"]).optional()
1006
+ }).optional()
1007
+ });
930
1008
  var chatCompletionRequestSchema = z13.object({
931
1009
  model: z13.string(),
932
1010
  messages: z13.array(chatMessageSchema),
933
1011
  temperature: z13.number().min(0).max(2).optional(),
934
1012
  maxTokens: z13.number().positive().optional(),
935
1013
  topP: z13.number().min(0).max(1).optional(),
936
- stream: z13.boolean().optional()
1014
+ stream: z13.boolean().optional(),
1015
+ // Web Search: Incorporate relevant web search results into the response
1016
+ // Results are returned in the annotations field
1017
+ webSearch: webSearchPluginSchema.optional(),
1018
+ // File Parser: Configure PDF processing for file content in messages
1019
+ // When files are included in messages, this controls how PDFs are parsed
1020
+ fileParser: fileParserPluginSchema.optional(),
1021
+ // Thinking/Reasoning mode: Enable extended reasoning capabilities
1022
+ // Appends ":thinking" to the model ID for chain-of-thought reasoning
1023
+ thinking: z13.boolean().optional()
1024
+ });
1025
+ var urlCitationAnnotationSchema = z13.object({
1026
+ type: z13.literal("url_citation"),
1027
+ urlCitation: z13.object({
1028
+ url: z13.string(),
1029
+ title: z13.string().optional(),
1030
+ content: z13.string().optional(),
1031
+ // Character indices in the response text where this citation applies
1032
+ startIndex: z13.number().optional(),
1033
+ endIndex: z13.number().optional()
1034
+ })
1035
+ });
1036
+ var fileAnnotationSchema = z13.object({
1037
+ type: z13.literal("file"),
1038
+ file: z13.object({
1039
+ filename: z13.string(),
1040
+ // Parsed content from the PDF (used for caching)
1041
+ parsedContent: z13.string().optional(),
1042
+ // Additional metadata from the parser
1043
+ metadata: z13.record(z13.unknown()).optional()
1044
+ })
937
1045
  });
1046
+ var annotationSchema = z13.union([urlCitationAnnotationSchema, fileAnnotationSchema]);
938
1047
  var chatCompletionResponseSchema = z13.object({
939
1048
  text: z13.string(),
1049
+ // Annotations from web search or file parsing (can be URL citations or file annotations)
1050
+ annotations: z13.array(annotationSchema).optional(),
940
1051
  metadata: z13.object({
941
1052
  model: z13.string(),
942
1053
  usage: z13.object({
@@ -946,6 +1057,30 @@ var chatCompletionResponseSchema = z13.object({
946
1057
  }).optional()
947
1058
  }).optional()
948
1059
  });
1060
+ var embeddingsRequestSchema = z13.object({
1061
+ model: z13.string(),
1062
+ input: z13.union([z13.string(), z13.array(z13.string())]),
1063
+ // eslint-disable-next-line @typescript-eslint/naming-convention
1064
+ encoding_format: z13.enum(["float", "base64"]).optional(),
1065
+ dimensions: z13.number().int().min(0).optional()
1066
+ });
1067
+ var embeddingObjectSchema = z13.object({
1068
+ object: z13.literal("embedding"),
1069
+ // Embedding can be number[] (float format) or string (base64 format)
1070
+ embedding: z13.union([z13.array(z13.number()), z13.string()]),
1071
+ index: z13.number()
1072
+ });
1073
+ var embeddingsResponseSchema = z13.object({
1074
+ object: z13.literal("list"),
1075
+ data: z13.array(embeddingObjectSchema),
1076
+ metadata: z13.object({
1077
+ model: z13.string(),
1078
+ usage: z13.object({
1079
+ promptTokens: z13.number().optional(),
1080
+ totalTokens: z13.number().optional()
1081
+ }).optional()
1082
+ }).optional()
1083
+ });
949
1084
  var imageGenerationRequestSchema = z13.object({
950
1085
  model: z13.string(),
951
1086
  prompt: z13.string(),
@@ -974,7 +1109,10 @@ var aiModelSchema = z13.object({
974
1109
  outputModality: z13.array(modalitySchema).min(1),
975
1110
  provider: z13.string(),
976
1111
  modelId: z13.string(),
977
- priceLevel: z13.number().min(0).max(3).optional()
1112
+ inputPrice: z13.number().min(0).optional(),
1113
+ // Price per million tokens in USD
1114
+ outputPrice: z13.number().min(0).optional()
1115
+ // Price per million tokens in USD
978
1116
  });
979
1117
  var createAIConfigurationRequestSchema = aiConfigurationSchema.omit({
980
1118
  id: true
@@ -1296,109 +1434,91 @@ var listDeploymentsResponseSchema = z22.object({
1296
1434
  })
1297
1435
  });
1298
1436
 
1299
- // src/shared/tools.ts
1300
- import FormData from "form-data";
1301
- var execAsync = promisify(exec);
1302
- var TOOL_VERSION_REQUIREMENTS = {
1303
- // Schedule tools - require backend v1.1.1+
1304
- // 'upsert-schedule': { minVersion: '1.1.1' },
1305
- // 'delete-schedule': { minVersion: '1.1.1' },
1306
- // 'get-schedules': { minVersion: '1.1.1' },
1307
- // 'get-schedule-logs': { minVersion: '1.1.1' },
1308
- "create-deployment": { minVersion: "1.4.7" },
1309
- "fetch-sdk-docs": { minVersion: "1.5.1" }
1310
- // Example of a deprecated tool (uncomment when needed):
1311
- // 'legacy-tool': { minVersion: '1.0.0', maxVersion: '1.5.0' },
1312
- };
1313
- function compareVersions(v1, v2) {
1314
- const clean1 = v1.replace(/^v/, "").split("-")[0];
1315
- const clean2 = v2.replace(/^v/, "").split("-")[0];
1316
- const parts1 = clean1.split(".").map(Number);
1317
- const parts2 = clean2.split(".").map(Number);
1318
- for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
1319
- const part1 = parts1[i] || 0;
1320
- const part2 = parts2[i] || 0;
1321
- if (part1 > part2) return 1;
1322
- if (part1 < part2) return -1;
1323
- }
1324
- return 0;
1325
- }
1326
- function shouldRegisterTool(toolName, backendVersion) {
1327
- const requirement = TOOL_VERSION_REQUIREMENTS[toolName];
1328
- if (!requirement) {
1329
- return true;
1330
- }
1331
- const { minVersion, maxVersion } = requirement;
1332
- if (minVersion && compareVersions(backendVersion, minVersion) < 0) {
1333
- return false;
1334
- }
1335
- if (maxVersion && compareVersions(backendVersion, maxVersion) > 0) {
1336
- return false;
1337
- }
1338
- return true;
1339
- }
1340
- async function fetchBackendVersion(apiBaseUrl) {
1341
- const response = await fetch2(`${apiBaseUrl}/api/health`, {
1342
- method: "GET",
1343
- headers: {
1344
- "Content-Type": "application/json"
1345
- }
1346
- });
1347
- if (!response.ok) {
1348
- throw new Error(`Health check failed with status ${response.status}`);
1349
- }
1350
- const health = await response.json();
1351
- return health.version;
1352
- }
1353
- async function registerInsforgeTools(server, config = {}) {
1354
- const GLOBAL_API_KEY = config.apiKey || process.env.API_KEY || "";
1355
- const API_BASE_URL = config.apiBaseUrl || process.env.API_BASE_URL || "http://localhost:7130";
1356
- const usageTracker = new UsageTracker(API_BASE_URL, GLOBAL_API_KEY);
1357
- const backendVersion = await fetchBackendVersion(API_BASE_URL);
1358
- console.error(`Backend version: ${backendVersion}`);
1359
- let toolCount = 0;
1360
- const registerTool = (toolName, ...args) => {
1361
- if (shouldRegisterTool(toolName, backendVersion)) {
1362
- server.tool(toolName, ...args);
1363
- toolCount++;
1364
- return true;
1365
- } else {
1366
- const req = TOOL_VERSION_REQUIREMENTS[toolName];
1367
- const reason = req?.minVersion && compareVersions(backendVersion, req.minVersion) < 0 ? `requires backend >= ${req.minVersion}` : `deprecated after backend ${req?.maxVersion}`;
1368
- console.error(`Skipping tool '${toolName}': ${reason} (current: ${backendVersion})`);
1369
- return false;
1370
- }
1371
- };
1372
- async function trackToolUsage(toolName, success = true) {
1373
- if (GLOBAL_API_KEY) {
1374
- await usageTracker.trackUsage(toolName, success);
1375
- }
1376
- }
1377
- function withUsageTracking(toolName, handler) {
1378
- return async (...args) => {
1379
- try {
1380
- const result = await handler(...args);
1381
- await trackToolUsage(toolName, true);
1382
- return result;
1383
- } catch (error) {
1384
- await trackToolUsage(toolName, false);
1385
- throw error;
1386
- }
1387
- };
1388
- }
1389
- const getApiKey = (_toolApiKey) => {
1390
- if (!GLOBAL_API_KEY) {
1391
- throw new Error("API key is required. Pass --api_key when starting the MCP server.");
1392
- }
1393
- return GLOBAL_API_KEY;
1394
- };
1437
+ // node_modules/@insforge/shared-schemas/dist/schedules.schema.js
1438
+ import { z as z23 } from "zod";
1439
+ var scheduleSchema = z23.object({
1440
+ id: z23.string().uuid(),
1441
+ name: z23.string(),
1442
+ cronSchedule: z23.string(),
1443
+ functionUrl: z23.string().url(),
1444
+ httpMethod: z23.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
1445
+ // Optional HTTP headers to include when invoking the scheduled function
1446
+ headers: z23.record(z23.string()).nullable(),
1447
+ // Body payload for the scheduled invocation. Can be a JSON object or a raw string.
1448
+ body: z23.union([z23.string(), z23.record(z23.unknown())]).nullable(),
1449
+ // cron_job_id is a BIGINT in postgres, which node-pg returns as a string.
1450
+ cronJobId: z23.string().nullable(),
1451
+ lastExecutedAt: z23.string().datetime().nullable(),
1452
+ // Whether the cron job is currently active (has a scheduled cron job)
1453
+ isActive: z23.boolean().default(true),
1454
+ // Next scheduled run time in ISO format (nullable if cron expression invalid)
1455
+ nextRun: z23.string().datetime().nullable(),
1456
+ createdAt: z23.string().datetime(),
1457
+ updatedAt: z23.string().datetime()
1458
+ });
1459
+ var scheduleLogSchema = z23.object({
1460
+ id: z23.string().uuid(),
1461
+ scheduleId: z23.string().uuid(),
1462
+ executedAt: z23.string().datetime(),
1463
+ statusCode: z23.number().int(),
1464
+ success: z23.boolean(),
1465
+ durationMs: z23.number().int(),
1466
+ message: z23.string().nullable()
1467
+ });
1468
+
1469
+ // node_modules/@insforge/shared-schemas/dist/schedules-api.schema.js
1470
+ import { z as z24 } from "zod";
1471
+ var cronScheduleSchema = z24.string().refine((value) => {
1472
+ const parts = value.split(" ");
1473
+ return parts.length === 5 || parts.length === 6;
1474
+ }, { message: 'Invalid cron schedule format. Use 5 or 6 parts (e.g., "* * * * *").' });
1475
+ var createScheduleRequestSchema = z24.object({
1476
+ name: z24.string().min(3, "Schedule name must be at least 3 characters long"),
1477
+ cronSchedule: cronScheduleSchema,
1478
+ functionUrl: z24.string().url("The function URL must be a valid URL."),
1479
+ httpMethod: z24.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]),
1480
+ headers: z24.record(z24.string()).optional().describe("Header values can reference secrets using ${{secrets.KEY_NAME}} syntax."),
1481
+ body: z24.record(z24.unknown()).optional().describe("The JSON body to send with the request.")
1482
+ });
1483
+ var updateScheduleRequestSchema = z24.object({
1484
+ name: z24.string().min(3, "Schedule name must be at least 3 characters long").optional(),
1485
+ cronSchedule: cronScheduleSchema.optional(),
1486
+ functionUrl: z24.string().url("The function URL must be a valid URL.").optional(),
1487
+ httpMethod: z24.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]).optional(),
1488
+ headers: z24.record(z24.string()).optional().describe("Header values can reference secrets using ${{secrets.KEY_NAME}} syntax."),
1489
+ body: z24.record(z24.unknown()).optional().describe("The JSON body to send with the request."),
1490
+ isActive: z24.boolean().optional().describe("Enable or disable the schedule.")
1491
+ });
1492
+ var listSchedulesResponseSchema = z24.array(scheduleSchema);
1493
+ var executionLogResponseSchema = scheduleLogSchema;
1494
+ var listExecutionLogsResponseSchema = z24.object({
1495
+ logs: z24.array(executionLogResponseSchema),
1496
+ totalCount: z24.number().int().nonnegative(),
1497
+ limit: z24.number().int().positive(),
1498
+ offset: z24.number().int().nonnegative()
1499
+ });
1500
+ var createScheduleResponseSchema = z24.object({
1501
+ id: z24.string().uuid(),
1502
+ cronJobId: z24.string(),
1503
+ message: z24.string()
1504
+ });
1505
+ var updateScheduleResponseSchema = z24.object({
1506
+ id: z24.string().uuid(),
1507
+ cronJobId: z24.string().optional(),
1508
+ message: z24.string()
1509
+ });
1510
+ var deleteScheduleResponseSchema = z24.object({
1511
+ message: z24.string()
1512
+ });
1513
+
1514
+ // src/shared/tools/docs.ts
1515
+ function registerDocsTools(ctx) {
1516
+ const { API_BASE_URL, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
1395
1517
  const fetchDocumentation = async (docType) => {
1396
1518
  try {
1397
1519
  const response = await fetch2(`${API_BASE_URL}/api/docs/${docType}`, {
1398
1520
  method: "GET",
1399
- headers: {
1400
- "Content-Type": "application/json"
1401
- }
1521
+ headers: { "Content-Type": "application/json" }
1402
1522
  });
1403
1523
  if (response.status === 404) {
1404
1524
  throw new Error("Documentation not found. This feature may not be supported in your project version. Please contact the Insforge team for assistance.");
@@ -1414,16 +1534,14 @@ async function registerInsforgeTools(server, config = {}) {
1414
1534
  throw new Error("Invalid response format from documentation endpoint");
1415
1535
  } catch (error) {
1416
1536
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1417
- throw new Error(`Unable to retrieve ${docType} documentation: ${errMsg}`);
1537
+ throw new Error(`Unable to retrieve ${docType} documentation: ${errMsg}`, { cause: error });
1418
1538
  }
1419
1539
  };
1420
1540
  const fetchSDKDocumentation = async (feature, language) => {
1421
1541
  try {
1422
1542
  const response = await fetch2(`${API_BASE_URL}/api/docs/${feature}/${language}`, {
1423
1543
  method: "GET",
1424
- headers: {
1425
- "Content-Type": "application/json"
1426
- }
1544
+ headers: { "Content-Type": "application/json" }
1427
1545
  });
1428
1546
  if (response.status === 404) {
1429
1547
  throw new Error("Documentation not found. This feature may not be supported in your project version. Please contact the Insforge team for assistance.");
@@ -1439,50 +1557,18 @@ async function registerInsforgeTools(server, config = {}) {
1439
1557
  throw new Error("Invalid response format from documentation endpoint");
1440
1558
  } catch (error) {
1441
1559
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1442
- throw new Error(`Unable to retrieve ${feature}-${language} documentation: ${errMsg}`);
1443
- }
1444
- };
1445
- const fetchInsforgeInstructionsContext = async () => {
1446
- try {
1447
- return await fetchDocumentation("instructions");
1448
- } catch (error) {
1449
- console.error("Failed to fetch insforge-instructions.md:", error);
1450
- return null;
1451
- }
1452
- };
1453
- const addBackgroundContext = async (response) => {
1454
- const isLegacyVersion = compareVersions(backendVersion, "1.1.7") < 0;
1455
- if (isLegacyVersion) {
1456
- const context = await fetchInsforgeInstructionsContext();
1457
- if (context && response.content && Array.isArray(response.content)) {
1458
- response.content.push({
1459
- type: "text",
1460
- text: `
1461
-
1462
- ---
1463
- \u{1F527} INSFORGE DEVELOPMENT RULES (Auto-loaded):
1464
- ${context}`
1465
- });
1466
- }
1560
+ throw new Error(`Unable to retrieve ${feature}-${language} documentation: ${errMsg}`, { cause: error });
1467
1561
  }
1468
- return response;
1469
1562
  };
1470
1563
  registerTool(
1471
1564
  "fetch-docs",
1472
1565
  'Fetch Insforge documentation. Use "instructions" for essential backend setup (MANDATORY FIRST), or select specific SDK docs for database, auth, storage, functions, or AI integration.',
1473
- {
1474
- docType: docTypeSchema
1475
- },
1566
+ { docType: docTypeSchema },
1476
1567
  withUsageTracking("fetch-docs", async ({ docType }) => {
1477
1568
  try {
1478
1569
  const content = await fetchDocumentation(docType);
1479
1570
  return await addBackgroundContext({
1480
- content: [
1481
- {
1482
- type: "text",
1483
- text: content
1484
- }
1485
- ]
1571
+ content: [{ type: "text", text: content }]
1486
1572
  });
1487
1573
  } catch (error) {
1488
1574
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
@@ -1495,7 +1581,8 @@ ${context}`
1495
1581
  };
1496
1582
  }
1497
1583
  return {
1498
- content: [{ type: "text", text: `Error fetching ${docType} documentation: ${errMsg}` }]
1584
+ content: [{ type: "text", text: `Error fetching ${docType} documentation: ${errMsg}` }],
1585
+ isError: true
1499
1586
  };
1500
1587
  }
1501
1588
  })
@@ -1506,20 +1593,12 @@ ${context}`
1506
1593
 
1507
1594
  Supported features: ${sdkFeatureSchema.options.join(", ")}
1508
1595
  Supported languages: ${sdkLanguageSchema.options.join(", ")}`,
1509
- {
1510
- sdkFeature: sdkFeatureSchema,
1511
- sdkLanguage: sdkLanguageSchema
1512
- },
1596
+ { sdkFeature: sdkFeatureSchema, sdkLanguage: sdkLanguageSchema },
1513
1597
  withUsageTracking("fetch-sdk-docs", async ({ sdkFeature, sdkLanguage }) => {
1514
1598
  try {
1515
1599
  const content = await fetchSDKDocumentation(sdkFeature, sdkLanguage);
1516
1600
  return await addBackgroundContext({
1517
- content: [
1518
- {
1519
- type: "text",
1520
- text: content
1521
- }
1522
- ]
1601
+ content: [{ type: "text", text: content }]
1523
1602
  });
1524
1603
  } catch (error) {
1525
1604
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
@@ -1532,7 +1611,8 @@ Supported languages: ${sdkLanguageSchema.options.join(", ")}`,
1532
1611
  };
1533
1612
  }
1534
1613
  return {
1535
- content: [{ type: "text", text: `Error fetching ${sdkFeature}-${sdkLanguage} documentation: ${errMsg}` }]
1614
+ content: [{ type: "text", text: `Error fetching ${sdkFeature}-${sdkLanguage} documentation: ${errMsg}` }],
1615
+ isError: true
1536
1616
  };
1537
1617
  }
1538
1618
  })
@@ -1541,7 +1621,7 @@ Supported languages: ${sdkLanguageSchema.options.join(", ")}`,
1541
1621
  "get-anon-key",
1542
1622
  "Generate an anonymous JWT token that never expires. Requires admin API key. Use this for client-side applications that need public access.",
1543
1623
  {
1544
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)")
1624
+ apiKey: z25.string().optional().describe("API key for authentication (optional if provided via --api_key)")
1545
1625
  },
1546
1626
  withUsageTracking("get-anon-key", async ({ apiKey }) => {
1547
1627
  try {
@@ -1555,61 +1635,74 @@ Supported languages: ${sdkLanguageSchema.options.join(", ")}`,
1555
1635
  });
1556
1636
  const result = await handleApiResponse(response);
1557
1637
  return await addBackgroundContext({
1558
- content: [
1559
- {
1560
- type: "text",
1561
- text: formatSuccessMessage("Anonymous token generated", result)
1562
- }
1563
- ]
1638
+ content: [{ type: "text", text: formatSuccessMessage("Anonymous token generated", result) }]
1564
1639
  });
1565
1640
  } catch (error) {
1566
1641
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1567
1642
  return {
1568
- content: [
1569
- {
1570
- type: "text",
1571
- text: `Error generating anonymous token: ${errMsg}`
1572
- }
1573
- ],
1643
+ content: [{ type: "text", text: `Error generating anonymous token: ${errMsg}` }],
1574
1644
  isError: true
1575
1645
  };
1576
1646
  }
1577
1647
  })
1578
1648
  );
1649
+ }
1650
+
1651
+ // src/shared/tools/database.ts
1652
+ import { z as z26 } from "zod";
1653
+ import fetch3 from "node-fetch";
1654
+ import { promises as fs } from "fs";
1655
+ import { execFile } from "child_process";
1656
+ import { promisify } from "util";
1657
+ import { tmpdir } from "os";
1658
+ import { join, basename } from "path";
1659
+ import FormData from "form-data";
1660
+
1661
+ // src/shared/tools/utils.ts
1662
+ var shellEsc = (s) => `'${s.replace(/'/g, "'\\''")}'`;
1663
+
1664
+ // src/shared/tools/database.ts
1665
+ var execFileAsync = promisify(execFile);
1666
+ function isAnonTokenResponse(obj) {
1667
+ return typeof obj === "object" && obj !== null && "accessToken" in obj && typeof obj.accessToken === "string" && obj.accessToken.length > 0;
1668
+ }
1669
+ function isBulkUpsertApiResult(obj) {
1670
+ if (typeof obj !== "object" || obj === null || !("success" in obj) || !("rowsAffected" in obj) || !("totalRecords" in obj) || !("table" in obj)) {
1671
+ return false;
1672
+ }
1673
+ const value = obj;
1674
+ if (typeof value.success !== "boolean" || typeof value.rowsAffected !== "number" || typeof value.totalRecords !== "number" || typeof value.table !== "string" || value.table.length === 0) {
1675
+ return false;
1676
+ }
1677
+ if (value.message !== void 0 && typeof value.message !== "string") {
1678
+ return false;
1679
+ }
1680
+ return true;
1681
+ }
1682
+ function registerDatabaseTools(ctx) {
1683
+ const { API_BASE_URL, isRemote, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
1579
1684
  registerTool(
1580
1685
  "get-table-schema",
1581
1686
  "Returns the detailed schema(including RLS, indexes, constraints, etc.) of a specific table",
1582
1687
  {
1583
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1584
- tableName: z23.string().describe("Name of the table")
1688
+ apiKey: z26.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1689
+ tableName: z26.string().describe("Name of the table")
1585
1690
  },
1586
1691
  withUsageTracking("get-table-schema", async ({ apiKey, tableName }) => {
1587
1692
  try {
1588
1693
  const actualApiKey = getApiKey(apiKey);
1589
- const response = await fetch2(`${API_BASE_URL}/api/metadata/${tableName}`, {
1694
+ const response = await fetch3(`${API_BASE_URL}/api/metadata/${encodeURIComponent(tableName)}`, {
1590
1695
  method: "GET",
1591
- headers: {
1592
- "x-api-key": actualApiKey
1593
- }
1696
+ headers: { "x-api-key": actualApiKey }
1594
1697
  });
1595
1698
  const result = await handleApiResponse(response);
1596
1699
  return await addBackgroundContext({
1597
- content: [
1598
- {
1599
- type: "text",
1600
- text: formatSuccessMessage("Schema retrieved", result)
1601
- }
1602
- ]
1700
+ content: [{ type: "text", text: formatSuccessMessage("Schema retrieved", result) }]
1603
1701
  });
1604
1702
  } catch (error) {
1605
1703
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1606
1704
  return {
1607
- content: [
1608
- {
1609
- type: "text",
1610
- text: `Error getting table schema: ${errMsg}`
1611
- }
1612
- ],
1705
+ content: [{ type: "text", text: `Error getting table schema: ${errMsg}` }],
1613
1706
  isError: true
1614
1707
  };
1615
1708
  }
@@ -1619,37 +1712,25 @@ Supported languages: ${sdkLanguageSchema.options.join(", ")}`,
1619
1712
  "get-backend-metadata",
1620
1713
  "Index all backend metadata",
1621
1714
  {
1622
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)")
1715
+ apiKey: z26.string().optional().describe("API key for authentication (optional if provided via --api_key)")
1623
1716
  },
1624
1717
  withUsageTracking("get-backend-metadata", async ({ apiKey }) => {
1625
1718
  try {
1626
1719
  const actualApiKey = getApiKey(apiKey);
1627
- const response = await fetch2(`${API_BASE_URL}/api/metadata?mcp=true`, {
1720
+ const response = await fetch3(`${API_BASE_URL}/api/metadata?mcp=true`, {
1628
1721
  method: "GET",
1629
- headers: {
1630
- "x-api-key": actualApiKey
1631
- }
1722
+ headers: { "x-api-key": actualApiKey }
1632
1723
  });
1633
1724
  const metadata = await handleApiResponse(response);
1634
1725
  return await addBackgroundContext({
1635
- content: [
1636
- {
1637
- type: "text",
1638
- text: `Backend metadata:
1726
+ content: [{ type: "text", text: `Backend metadata:
1639
1727
 
1640
- ${JSON.stringify(metadata, null, 2)}`
1641
- }
1642
- ]
1728
+ ${JSON.stringify(metadata, null, 2)}` }]
1643
1729
  });
1644
1730
  } catch (error) {
1645
1731
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1646
1732
  return {
1647
- content: [
1648
- {
1649
- type: "text",
1650
- text: `Error retrieving backend metadata: ${errMsg}`
1651
- }
1652
- ],
1733
+ content: [{ type: "text", text: `Error retrieving backend metadata: ${errMsg}` }],
1653
1734
  isError: true
1654
1735
  };
1655
1736
  }
@@ -1659,17 +1740,14 @@ ${JSON.stringify(metadata, null, 2)}`
1659
1740
  "run-raw-sql",
1660
1741
  "Execute raw SQL query with optional parameters. Admin access required. Use with caution as it can modify data directly.",
1661
1742
  {
1662
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1743
+ apiKey: z26.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1663
1744
  ...rawSQLRequestSchema.shape
1664
1745
  },
1665
1746
  withUsageTracking("run-raw-sql", async ({ apiKey, query, params }) => {
1666
1747
  try {
1667
1748
  const actualApiKey = getApiKey(apiKey);
1668
- const requestBody = {
1669
- query,
1670
- params: params || []
1671
- };
1672
- const response = await fetch2(`${API_BASE_URL}/api/database/advance/rawsql`, {
1749
+ const requestBody = { query, params: params || [] };
1750
+ const response = await fetch3(`${API_BASE_URL}/api/database/advance/rawsql`, {
1673
1751
  method: "POST",
1674
1752
  headers: {
1675
1753
  "x-api-key": actualApiKey,
@@ -1679,75 +1757,103 @@ ${JSON.stringify(metadata, null, 2)}`
1679
1757
  });
1680
1758
  const result = await handleApiResponse(response);
1681
1759
  return await addBackgroundContext({
1682
- content: [
1683
- {
1684
- type: "text",
1685
- text: formatSuccessMessage("SQL query executed", result)
1686
- }
1687
- ]
1760
+ content: [{ type: "text", text: formatSuccessMessage("SQL query executed", result) }]
1688
1761
  });
1689
1762
  } catch (error) {
1690
1763
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1691
1764
  return {
1692
- content: [
1693
- {
1694
- type: "text",
1695
- text: `Error executing SQL query: ${errMsg}`
1696
- }
1697
- ],
1765
+ content: [{ type: "text", text: `Error executing SQL query: ${errMsg}` }],
1698
1766
  isError: true
1699
1767
  };
1700
1768
  }
1701
1769
  })
1702
1770
  );
1703
- registerTool(
1704
- "download-template",
1705
- "CRITICAL: MANDATORY FIRST STEP for all new InsForge projects. Download pre-configured starter template to a temporary directory. After download, you MUST copy files to current directory using the provided command.",
1706
- {
1707
- frame: z23.enum(["react", "nextjs"]).describe("Framework to use for the template (support React and Next.js)"),
1708
- projectName: z23.string().optional().describe('Name for the project directory (optional, defaults to "insforge-react")')
1709
- },
1710
- withUsageTracking("download-template", async ({ frame, projectName }) => {
1711
- try {
1712
- const response = await fetch2(`${API_BASE_URL}/api/auth/tokens/anon`, {
1713
- method: "POST",
1714
- headers: {
1715
- "x-api-key": getApiKey(),
1716
- "Content-Type": "application/json"
1771
+ if (isRemote) {
1772
+ registerTool(
1773
+ "download-template",
1774
+ "CRITICAL: MANDATORY FIRST STEP for all new InsForge projects. Fetches configuration and returns a command for you to run locally to scaffold a starter template.",
1775
+ {
1776
+ frame: z26.enum(["react", "nextjs"]).describe("Framework to use for the template (support React and Next.js)"),
1777
+ projectName: z26.string().optional().describe('Name for the project directory (optional, defaults to "insforge-{frame}")')
1778
+ },
1779
+ withUsageTracking("download-template", async ({ frame, projectName }) => {
1780
+ try {
1781
+ const response = await fetch3(`${API_BASE_URL}/api/auth/tokens/anon`, {
1782
+ method: "POST",
1783
+ headers: {
1784
+ "x-api-key": getApiKey(),
1785
+ "Content-Type": "application/json"
1786
+ }
1787
+ });
1788
+ const anonResult = await handleApiResponse(response);
1789
+ if (!isAnonTokenResponse(anonResult)) {
1790
+ throw new Error("Failed to retrieve anon key from backend");
1717
1791
  }
1718
- });
1719
- const result = await handleApiResponse(response);
1720
- const anonKey = result.accessToken;
1721
- if (!anonKey) {
1722
- throw new Error("Failed to retrieve anon key from backend");
1792
+ const anonKey = anonResult.accessToken;
1793
+ const rawDir = projectName || `insforge-${frame}`;
1794
+ if (!rawDir || rawDir === "." || rawDir === ".." || /[/\\]/.test(rawDir) || !/^[\w.-]+$/.test(rawDir)) {
1795
+ throw new Error("projectName must be a single directory name using only letters, numbers, hyphens, underscores, and dots");
1796
+ }
1797
+ const targetDir = rawDir;
1798
+ const instructions = `Template configuration ready. Please run the following command in your project's parent directory:
1799
+
1800
+ \`\`\`bash
1801
+ npx create-insforge-app ${shellEsc(targetDir)} --frame ${frame} --base-url ${shellEsc(API_BASE_URL)} --anon-key ${shellEsc(anonKey)}
1802
+ \`\`\`
1803
+
1804
+ After the command completes, \`cd ${shellEsc(targetDir)}\` and start developing.`;
1805
+ return {
1806
+ content: [{ type: "text", text: instructions }]
1807
+ };
1808
+ } catch (error) {
1809
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1810
+ return {
1811
+ content: [{ type: "text", text: `Error preparing template: ${errMsg}` }],
1812
+ isError: true
1813
+ };
1723
1814
  }
1724
- const tempDir = tmpdir();
1725
- const targetDir = projectName || `insforge-${frame}`;
1726
- const templatePath = `${tempDir}/${targetDir}`;
1727
- console.error(`[download-template] Target path: ${templatePath}`);
1815
+ })
1816
+ );
1817
+ } else {
1818
+ registerTool(
1819
+ "download-template",
1820
+ "CRITICAL: MANDATORY FIRST STEP for all new InsForge projects. Download pre-configured starter template to a temporary directory. After download, you MUST copy files to current directory using the provided command.",
1821
+ {
1822
+ frame: z26.enum(["react", "nextjs"]).describe("Framework to use for the template (support React and Next.js)"),
1823
+ projectName: z26.string().optional().describe('Name for the project directory (optional, defaults to "insforge-{frame}")')
1824
+ },
1825
+ withUsageTracking("download-template", async ({ frame, projectName }) => {
1728
1826
  try {
1729
- const stats = await fs.stat(templatePath);
1730
- if (stats.isDirectory()) {
1731
- console.error(`[download-template] Removing existing template at ${templatePath}`);
1732
- await fs.rm(templatePath, { recursive: true, force: true });
1827
+ const response = await fetch3(`${API_BASE_URL}/api/auth/tokens/anon`, {
1828
+ method: "POST",
1829
+ headers: {
1830
+ "x-api-key": getApiKey(),
1831
+ "Content-Type": "application/json"
1832
+ }
1833
+ });
1834
+ const anonResult = await handleApiResponse(response);
1835
+ if (!isAnonTokenResponse(anonResult)) {
1836
+ throw new Error("Failed to retrieve anon key from backend");
1733
1837
  }
1734
- } catch {
1735
- }
1736
- const command = `npx create-insforge-app ${targetDir} --frame ${frame} --base-url ${API_BASE_URL} --anon-key ${anonKey} --skip-install`;
1737
- const { stdout, stderr } = await execAsync(command, {
1738
- maxBuffer: 10 * 1024 * 1024,
1739
- // 10MB buffer
1740
- cwd: tempDir
1741
- });
1742
- const output = stdout || stderr || "";
1743
- if (output.toLowerCase().includes("error") && !output.includes("successfully")) {
1744
- throw new Error(`Failed to download template: ${output}`);
1745
- }
1746
- return await addBackgroundContext({
1747
- content: [
1748
- {
1838
+ const anonKey = anonResult.accessToken;
1839
+ const rawDir = projectName || `insforge-${frame}`;
1840
+ if (!rawDir || rawDir === "." || rawDir === ".." || /[/\\]/.test(rawDir) || !/^[\w.-]+$/.test(rawDir)) {
1841
+ throw new Error("projectName must be a single directory name using only letters, numbers, hyphens, underscores, and dots");
1842
+ }
1843
+ const targetDir = rawDir;
1844
+ const workspaceBase = await fs.mkdtemp(join(tmpdir(), "insforge-template-"));
1845
+ const templatePath = join(workspaceBase, targetDir);
1846
+ console.error(`[download-template] Target path: ${templatePath}`);
1847
+ await execFileAsync(
1848
+ "npx",
1849
+ ["create-insforge-app", targetDir, "--frame", frame, "--base-url", API_BASE_URL, "--anon-key", anonKey, "--skip-install"],
1850
+ { maxBuffer: 10 * 1024 * 1024, cwd: workspaceBase }
1851
+ );
1852
+ const frameName = frame === "nextjs" ? "Next.js" : "React";
1853
+ return await addBackgroundContext({
1854
+ content: [{
1749
1855
  type: "text",
1750
- text: `\u2705 React template downloaded successfully
1856
+ text: `\u2705 ${frameName} template downloaded successfully
1751
1857
 
1752
1858
  \u{1F4C1} Template Location: ${templatePath}
1753
1859
 
@@ -1759,43 +1865,38 @@ You MUST copy ALL files (INCLUDING HIDDEN FILES like .env, .gitignore, etc.) fro
1759
1865
  Copy all files from: ${templatePath}
1760
1866
  To: Your current project directory
1761
1867
  `
1762
- }
1763
- ]
1764
- });
1765
- } catch (error) {
1766
- const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1767
- return {
1768
- content: [
1769
- {
1770
- type: "text",
1771
- text: `Error downloading template: ${errMsg}`
1772
- }
1773
- ],
1774
- isError: true
1775
- };
1776
- }
1777
- })
1778
- );
1868
+ }]
1869
+ });
1870
+ } catch (error) {
1871
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1872
+ return {
1873
+ content: [{ type: "text", text: `Error downloading template: ${errMsg}` }],
1874
+ isError: true
1875
+ };
1876
+ }
1877
+ })
1878
+ );
1879
+ }
1779
1880
  registerTool(
1780
1881
  "bulk-upsert",
1781
1882
  "Bulk insert or update data from CSV or JSON file. Supports upsert operations with a unique key.",
1782
1883
  {
1783
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1884
+ apiKey: z26.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1784
1885
  ...bulkUpsertRequestSchema.shape,
1785
- filePath: z23.string().describe("Path to CSV or JSON file containing data to import")
1886
+ filePath: z26.string().describe("Path to CSV or JSON file containing data to import")
1786
1887
  },
1787
1888
  withUsageTracking("bulk-upsert", async ({ apiKey, table, filePath, upsertKey }) => {
1788
1889
  try {
1789
1890
  const actualApiKey = getApiKey(apiKey);
1790
1891
  const fileBuffer = await fs.readFile(filePath);
1791
- const fileName = filePath.split("/").pop() || "data.csv";
1892
+ const fileName = basename(filePath) || "data.csv";
1792
1893
  const formData = new FormData();
1793
1894
  formData.append("file", fileBuffer, fileName);
1794
1895
  formData.append("table", table);
1795
1896
  if (upsertKey) {
1796
1897
  formData.append("upsertKey", upsertKey);
1797
1898
  }
1798
- const response = await fetch2(`${API_BASE_URL}/api/database/advance/bulk-upsert`, {
1899
+ const response = await fetch3(`${API_BASE_URL}/api/database/advance/bulk-upsert`, {
1799
1900
  method: "POST",
1800
1901
  headers: {
1801
1902
  "x-api-key": actualApiKey,
@@ -1803,47 +1904,50 @@ To: Your current project directory
1803
1904
  },
1804
1905
  body: formData
1805
1906
  });
1806
- const result = await handleApiResponse(response);
1807
- const message = result.success ? `Successfully processed ${result.rowsAffected} of ${result.totalRecords} records into table "${result.table}"` : result.message || "Bulk upsert operation completed";
1907
+ const rawResult = await handleApiResponse(response);
1908
+ if (!isBulkUpsertApiResult(rawResult)) {
1909
+ throw new Error("Unexpected response format from bulk-upsert endpoint");
1910
+ }
1911
+ const message = rawResult.success ? `Successfully processed ${rawResult.rowsAffected} of ${rawResult.totalRecords} records into table "${rawResult.table}"` : rawResult.message || "Bulk upsert operation completed";
1808
1912
  return await addBackgroundContext({
1809
- content: [
1810
- {
1811
- type: "text",
1812
- text: formatSuccessMessage("Bulk upsert completed", {
1813
- message,
1814
- table: result.table,
1815
- rowsAffected: result.rowsAffected,
1816
- totalRecords: result.totalRecords,
1817
- errors: result.errors
1818
- })
1819
- }
1820
- ]
1913
+ content: [{
1914
+ type: "text",
1915
+ text: formatSuccessMessage("Bulk upsert completed", {
1916
+ message,
1917
+ table: rawResult.table,
1918
+ rowsAffected: rawResult.rowsAffected,
1919
+ totalRecords: rawResult.totalRecords,
1920
+ errors: rawResult.errors
1921
+ })
1922
+ }]
1821
1923
  });
1822
1924
  } catch (error) {
1823
1925
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1824
1926
  return {
1825
- content: [
1826
- {
1827
- type: "text",
1828
- text: `Error performing bulk upsert: ${errMsg}`
1829
- }
1830
- ],
1927
+ content: [{ type: "text", text: `Error performing bulk upsert: ${errMsg}` }],
1831
1928
  isError: true
1832
1929
  };
1833
1930
  }
1834
1931
  })
1835
1932
  );
1933
+ }
1934
+
1935
+ // src/shared/tools/storage.ts
1936
+ import { z as z27 } from "zod";
1937
+ import fetch4 from "node-fetch";
1938
+ function registerStorageTools(ctx) {
1939
+ const { API_BASE_URL, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
1836
1940
  registerTool(
1837
1941
  "create-bucket",
1838
1942
  "Create new storage bucket",
1839
1943
  {
1840
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1944
+ apiKey: z27.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1841
1945
  ...createBucketRequestSchema.shape
1842
1946
  },
1843
1947
  withUsageTracking("create-bucket", async ({ apiKey, bucketName, isPublic }) => {
1844
1948
  try {
1845
1949
  const actualApiKey = getApiKey(apiKey);
1846
- const response = await fetch2(`${API_BASE_URL}/api/storage/buckets`, {
1950
+ const response = await fetch4(`${API_BASE_URL}/api/storage/buckets`, {
1847
1951
  method: "POST",
1848
1952
  headers: {
1849
1953
  "x-api-key": actualApiKey,
@@ -1853,22 +1957,12 @@ To: Your current project directory
1853
1957
  });
1854
1958
  const result = await handleApiResponse(response);
1855
1959
  return await addBackgroundContext({
1856
- content: [
1857
- {
1858
- type: "text",
1859
- text: formatSuccessMessage("Bucket created", result)
1860
- }
1861
- ]
1960
+ content: [{ type: "text", text: formatSuccessMessage("Bucket created", result) }]
1862
1961
  });
1863
1962
  } catch (error) {
1864
1963
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1865
1964
  return {
1866
- content: [
1867
- {
1868
- type: "text",
1869
- text: `Error creating bucket: ${errMsg}`
1870
- }
1871
- ],
1965
+ content: [{ type: "text", text: `Error creating bucket: ${errMsg}` }],
1872
1966
  isError: true
1873
1967
  };
1874
1968
  }
@@ -1877,33 +1971,23 @@ To: Your current project directory
1877
1971
  registerTool(
1878
1972
  "list-buckets",
1879
1973
  "Lists all storage buckets",
1880
- {},
1881
- withUsageTracking("list-buckets", async () => {
1974
+ {
1975
+ apiKey: z27.string().optional().describe("API key for authentication (optional if provided via --api_key)")
1976
+ },
1977
+ withUsageTracking("list-buckets", async ({ apiKey }) => {
1882
1978
  try {
1883
- const response = await fetch2(`${API_BASE_URL}/api/storage/buckets`, {
1979
+ const response = await fetch4(`${API_BASE_URL}/api/storage/buckets`, {
1884
1980
  method: "GET",
1885
- headers: {
1886
- "x-api-key": getApiKey()
1887
- }
1981
+ headers: { "x-api-key": getApiKey(apiKey) }
1888
1982
  });
1889
1983
  const result = await handleApiResponse(response);
1890
1984
  return await addBackgroundContext({
1891
- content: [
1892
- {
1893
- type: "text",
1894
- text: formatSuccessMessage("Buckets retrieved", result)
1895
- }
1896
- ]
1985
+ content: [{ type: "text", text: formatSuccessMessage("Buckets retrieved", result) }]
1897
1986
  });
1898
1987
  } catch (error) {
1899
1988
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1900
1989
  return {
1901
- content: [
1902
- {
1903
- type: "text",
1904
- text: `Error listing buckets: ${errMsg}`
1905
- }
1906
- ],
1990
+ content: [{ type: "text", text: `Error listing buckets: ${errMsg}` }],
1907
1991
  isError: true
1908
1992
  };
1909
1993
  }
@@ -1913,432 +1997,736 @@ To: Your current project directory
1913
1997
  "delete-bucket",
1914
1998
  "Deletes a storage bucket",
1915
1999
  {
1916
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
1917
- bucketName: z23.string().describe("Name of the bucket to delete")
2000
+ apiKey: z27.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2001
+ // Reuse the same bucket name validation as create-bucket
2002
+ bucketName: createBucketRequestSchema.shape.bucketName
1918
2003
  },
1919
2004
  withUsageTracking("delete-bucket", async ({ apiKey, bucketName }) => {
1920
2005
  try {
1921
2006
  const actualApiKey = getApiKey(apiKey);
1922
- const response = await fetch2(`${API_BASE_URL}/api/storage/buckets/${bucketName}`, {
2007
+ const response = await fetch4(`${API_BASE_URL}/api/storage/buckets/${encodeURIComponent(bucketName)}`, {
1923
2008
  method: "DELETE",
1924
- headers: {
1925
- "x-api-key": actualApiKey
1926
- }
2009
+ headers: { "x-api-key": actualApiKey }
1927
2010
  });
1928
2011
  const result = await handleApiResponse(response);
1929
2012
  return await addBackgroundContext({
1930
- content: [
1931
- {
1932
- type: "text",
1933
- text: formatSuccessMessage("Bucket deleted", result)
1934
- }
1935
- ]
2013
+ content: [{ type: "text", text: formatSuccessMessage("Bucket deleted", result) }]
1936
2014
  });
1937
2015
  } catch (error) {
1938
2016
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1939
2017
  return {
1940
- content: [
1941
- {
1942
- type: "text",
1943
- text: `Error deleting bucket: ${errMsg}`
1944
- }
1945
- ],
2018
+ content: [{ type: "text", text: `Error deleting bucket: ${errMsg}` }],
1946
2019
  isError: true
1947
2020
  };
1948
2021
  }
1949
2022
  })
1950
2023
  );
1951
- registerTool(
1952
- "create-function",
1953
- "Create a new edge function that runs in Deno runtime. The code must be written to a file first for version control",
1954
- {
1955
- ...uploadFunctionRequestSchema.omit({ code: true }).shape,
1956
- codeFile: z23.string().describe(
1957
- "Path to JavaScript file containing the function code. Must export: module.exports = async function(request) { return new Response(...) }"
1958
- )
1959
- },
1960
- withUsageTracking("create-function", async (args) => {
1961
- try {
1962
- let code;
2024
+ }
2025
+
2026
+ // src/shared/tools/functions.ts
2027
+ import { z as z28 } from "zod";
2028
+ import fetch5 from "node-fetch";
2029
+ import { promises as fs2 } from "fs";
2030
+ function registerFunctionTools(ctx) {
2031
+ const { API_BASE_URL, isRemote, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
2032
+ if (isRemote) {
2033
+ registerTool(
2034
+ "create-function",
2035
+ "Create a new edge function that runs in Deno runtime",
2036
+ {
2037
+ apiKey: z28.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2038
+ ...uploadFunctionRequestSchema.omit({ code: true }).shape,
2039
+ code: z28.string().describe(
2040
+ "The function code as a string. Must export: module.exports = async function(request) { return new Response(...) }"
2041
+ )
2042
+ },
2043
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2044
+ withUsageTracking("create-function", async (args) => {
1963
2045
  try {
1964
- code = await fs.readFile(args.codeFile, "utf-8");
1965
- } catch (fileError) {
1966
- throw new Error(
1967
- `Failed to read code file '${args.codeFile}': ${fileError instanceof Error ? fileError.message : "Unknown error"}`
1968
- );
2046
+ const response = await fetch5(`${API_BASE_URL}/api/functions`, {
2047
+ method: "POST",
2048
+ headers: {
2049
+ "Content-Type": "application/json",
2050
+ "x-api-key": getApiKey(args.apiKey)
2051
+ },
2052
+ body: JSON.stringify({
2053
+ slug: args.slug,
2054
+ name: args.name,
2055
+ code: args.code,
2056
+ description: args.description,
2057
+ status: args.status
2058
+ })
2059
+ });
2060
+ const result = await handleApiResponse(response);
2061
+ return await addBackgroundContext({
2062
+ content: [{ type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' created successfully`, result) }]
2063
+ });
2064
+ } catch (error) {
2065
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2066
+ return {
2067
+ content: [{ type: "text", text: `Error creating function: ${errMsg}` }],
2068
+ isError: true
2069
+ };
1969
2070
  }
1970
- const response = await fetch2(`${API_BASE_URL}/api/functions`, {
1971
- method: "POST",
1972
- headers: {
1973
- "Content-Type": "application/json",
1974
- "x-api-key": getApiKey()
1975
- },
1976
- body: JSON.stringify({
1977
- slug: args.slug,
1978
- name: args.name,
1979
- code,
1980
- description: args.description,
1981
- status: args.status
1982
- })
1983
- });
1984
- const result = await handleApiResponse(response);
1985
- return await addBackgroundContext({
1986
- content: [
1987
- {
1988
- type: "text",
1989
- text: formatSuccessMessage(
1990
- `Edge function '${args.slug}' created successfully from ${args.codeFile}`,
1991
- result
1992
- )
1993
- }
1994
- ]
1995
- });
1996
- } catch (error) {
1997
- const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
1998
- return {
1999
- content: [
2000
- {
2001
- type: "text",
2002
- text: `Error creating function: ${errMsg}`
2003
- }
2004
- ],
2005
- isError: true
2006
- };
2007
- }
2008
- })
2009
- );
2071
+ })
2072
+ );
2073
+ } else {
2074
+ registerTool(
2075
+ "create-function",
2076
+ "Create a new edge function that runs in Deno runtime. The code must be written to a file first for version control",
2077
+ {
2078
+ apiKey: z28.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2079
+ ...uploadFunctionRequestSchema.omit({ code: true }).shape,
2080
+ codeFile: z28.string().describe(
2081
+ "Path to JavaScript file containing the function code. Must export: module.exports = async function(request) { return new Response(...) }"
2082
+ )
2083
+ },
2084
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2085
+ withUsageTracking("create-function", async (args) => {
2086
+ try {
2087
+ let code;
2088
+ try {
2089
+ code = await fs2.readFile(args.codeFile, "utf-8");
2090
+ } catch (fileError) {
2091
+ throw new Error(
2092
+ `Failed to read code file '${args.codeFile}': ${fileError instanceof Error ? fileError.message : "Unknown error"}`,
2093
+ { cause: fileError }
2094
+ );
2095
+ }
2096
+ const response = await fetch5(`${API_BASE_URL}/api/functions`, {
2097
+ method: "POST",
2098
+ headers: {
2099
+ "Content-Type": "application/json",
2100
+ "x-api-key": getApiKey(args.apiKey)
2101
+ },
2102
+ body: JSON.stringify({
2103
+ slug: args.slug,
2104
+ name: args.name,
2105
+ code,
2106
+ description: args.description,
2107
+ status: args.status
2108
+ })
2109
+ });
2110
+ const result = await handleApiResponse(response);
2111
+ return await addBackgroundContext({
2112
+ content: [{ type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' created successfully from ${args.codeFile}`, result) }]
2113
+ });
2114
+ } catch (error) {
2115
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2116
+ return {
2117
+ content: [{ type: "text", text: `Error creating function: ${errMsg}` }],
2118
+ isError: true
2119
+ };
2120
+ }
2121
+ })
2122
+ );
2123
+ }
2010
2124
  registerTool(
2011
2125
  "get-function",
2012
2126
  "Get details of a specific edge function including its code",
2013
2127
  {
2014
- slug: z23.string().describe("The slug identifier of the function")
2128
+ apiKey: z28.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2129
+ slug: functionSchema.shape.slug.describe("The slug identifier of the function")
2015
2130
  },
2131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2016
2132
  withUsageTracking("get-function", async (args) => {
2017
2133
  try {
2018
- const response = await fetch2(`${API_BASE_URL}/api/functions/${args.slug}`, {
2134
+ const response = await fetch5(`${API_BASE_URL}/api/functions/${encodeURIComponent(args.slug)}`, {
2019
2135
  method: "GET",
2020
- headers: {
2021
- "x-api-key": getApiKey()
2022
- }
2136
+ headers: { "x-api-key": getApiKey(args.apiKey) }
2023
2137
  });
2024
2138
  const result = await handleApiResponse(response);
2025
2139
  return await addBackgroundContext({
2026
- content: [
2027
- {
2028
- type: "text",
2029
- text: formatSuccessMessage(`Edge function '${args.slug}' details`, result)
2030
- }
2031
- ]
2140
+ content: [{ type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' details`, result) }]
2032
2141
  });
2033
2142
  } catch (error) {
2034
2143
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2035
2144
  return {
2036
- content: [
2037
- {
2038
- type: "text",
2039
- text: `Error getting function: ${errMsg}`
2040
- }
2041
- ],
2145
+ content: [{ type: "text", text: `Error getting function: ${errMsg}` }],
2042
2146
  isError: true
2043
2147
  };
2044
2148
  }
2045
2149
  })
2046
2150
  );
2047
- registerTool(
2048
- "update-function",
2049
- "Update an existing edge function code or metadata",
2050
- {
2051
- slug: z23.string().describe("The slug identifier of the function to update"),
2052
- ...updateFunctionRequestSchema.omit({ code: true }).shape,
2053
- codeFile: z23.string().optional().describe(
2054
- "Path to JavaScript file containing the new function code. Must export: module.exports = async function(request) { return new Response(...) }"
2055
- )
2056
- },
2057
- withUsageTracking("update-function", async (args) => {
2058
- try {
2059
- const updateData = {};
2060
- if (args.name) {
2061
- updateData.name = args.name;
2151
+ if (isRemote) {
2152
+ registerTool(
2153
+ "update-function",
2154
+ "Update an existing edge function code or metadata",
2155
+ {
2156
+ apiKey: z28.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2157
+ slug: functionSchema.shape.slug.describe("The slug identifier of the function to update"),
2158
+ ...updateFunctionRequestSchema.omit({ code: true }).shape,
2159
+ code: z28.string().optional().describe(
2160
+ "The new function code as a string. Must export: module.exports = async function(request) { return new Response(...) }"
2161
+ )
2162
+ },
2163
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2164
+ withUsageTracking("update-function", async (args) => {
2165
+ try {
2166
+ const updateData = {};
2167
+ if (args.name) updateData.name = args.name;
2168
+ if (args.code) updateData.code = args.code;
2169
+ if (args.description !== void 0) updateData.description = args.description;
2170
+ if (args.status) updateData.status = args.status;
2171
+ const response = await fetch5(`${API_BASE_URL}/api/functions/${encodeURIComponent(args.slug)}`, {
2172
+ method: "PUT",
2173
+ headers: {
2174
+ "Content-Type": "application/json",
2175
+ "x-api-key": getApiKey(args.apiKey)
2176
+ },
2177
+ body: JSON.stringify(updateData)
2178
+ });
2179
+ const result = await handleApiResponse(response);
2180
+ return await addBackgroundContext({
2181
+ content: [{ type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' updated successfully`, result) }]
2182
+ });
2183
+ } catch (error) {
2184
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2185
+ return {
2186
+ content: [{ type: "text", text: `Error updating function: ${errMsg}` }],
2187
+ isError: true
2188
+ };
2062
2189
  }
2063
- if (args.codeFile) {
2064
- try {
2065
- updateData.code = await fs.readFile(args.codeFile, "utf-8");
2066
- } catch (fileError) {
2067
- throw new Error(
2068
- `Failed to read code file '${args.codeFile}': ${fileError instanceof Error ? fileError.message : "Unknown error"}`
2069
- );
2190
+ })
2191
+ );
2192
+ } else {
2193
+ registerTool(
2194
+ "update-function",
2195
+ "Update an existing edge function code or metadata",
2196
+ {
2197
+ apiKey: z28.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2198
+ slug: functionSchema.shape.slug.describe("The slug identifier of the function to update"),
2199
+ ...updateFunctionRequestSchema.omit({ code: true }).shape,
2200
+ codeFile: z28.string().optional().describe(
2201
+ "Path to JavaScript file containing the new function code. Must export: module.exports = async function(request) { return new Response(...) }"
2202
+ )
2203
+ },
2204
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2205
+ withUsageTracking("update-function", async (args) => {
2206
+ try {
2207
+ const updateData = {};
2208
+ if (args.name) updateData.name = args.name;
2209
+ if (args.codeFile) {
2210
+ try {
2211
+ updateData.code = await fs2.readFile(args.codeFile, "utf-8");
2212
+ } catch (fileError) {
2213
+ throw new Error(
2214
+ `Failed to read code file '${args.codeFile}': ${fileError instanceof Error ? fileError.message : "Unknown error"}`,
2215
+ { cause: fileError }
2216
+ );
2217
+ }
2070
2218
  }
2219
+ if (args.description !== void 0) updateData.description = args.description;
2220
+ if (args.status) updateData.status = args.status;
2221
+ const response = await fetch5(`${API_BASE_URL}/api/functions/${encodeURIComponent(args.slug)}`, {
2222
+ method: "PUT",
2223
+ headers: {
2224
+ "Content-Type": "application/json",
2225
+ "x-api-key": getApiKey(args.apiKey)
2226
+ },
2227
+ body: JSON.stringify(updateData)
2228
+ });
2229
+ const result = await handleApiResponse(response);
2230
+ const fileInfo = args.codeFile ? ` from ${args.codeFile}` : "";
2231
+ return await addBackgroundContext({
2232
+ content: [{ type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' updated successfully${fileInfo}`, result) }]
2233
+ });
2234
+ } catch (error) {
2235
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2236
+ return {
2237
+ content: [{ type: "text", text: `Error updating function: ${errMsg}` }],
2238
+ isError: true
2239
+ };
2071
2240
  }
2072
- if (args.description !== void 0) {
2073
- updateData.description = args.description;
2074
- }
2075
- if (args.status) {
2076
- updateData.status = args.status;
2077
- }
2078
- const response = await fetch2(`${API_BASE_URL}/api/functions/${args.slug}`, {
2079
- method: "PUT",
2080
- headers: {
2081
- "Content-Type": "application/json",
2082
- "x-api-key": getApiKey()
2083
- },
2084
- body: JSON.stringify(updateData)
2085
- });
2086
- const result = await handleApiResponse(response);
2087
- const fileInfo = args.codeFile ? ` from ${args.codeFile}` : "";
2088
- return await addBackgroundContext({
2089
- content: [
2090
- {
2091
- type: "text",
2092
- text: formatSuccessMessage(
2093
- `Edge function '${args.slug}' updated successfully${fileInfo}`,
2094
- result
2095
- )
2096
- }
2097
- ]
2098
- });
2099
- } catch (error) {
2100
- const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2101
- return {
2102
- content: [
2103
- {
2104
- type: "text",
2105
- text: `Error updating function: ${errMsg}`
2106
- }
2107
- ],
2108
- isError: true
2109
- };
2110
- }
2111
- })
2112
- );
2241
+ })
2242
+ );
2243
+ }
2113
2244
  registerTool(
2114
2245
  "delete-function",
2115
2246
  "Delete an edge function permanently",
2116
2247
  {
2117
- slug: z23.string().describe("The slug identifier of the function to delete")
2248
+ apiKey: z28.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2249
+ slug: functionSchema.shape.slug.describe("The slug identifier of the function to delete")
2118
2250
  },
2251
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
2119
2252
  withUsageTracking("delete-function", async (args) => {
2120
2253
  try {
2121
- const response = await fetch2(`${API_BASE_URL}/api/functions/${args.slug}`, {
2254
+ const response = await fetch5(`${API_BASE_URL}/api/functions/${encodeURIComponent(args.slug)}`, {
2122
2255
  method: "DELETE",
2123
- headers: {
2124
- "x-api-key": getApiKey()
2125
- }
2256
+ headers: { "x-api-key": getApiKey(args.apiKey) }
2126
2257
  });
2127
2258
  const result = await handleApiResponse(response);
2128
2259
  return await addBackgroundContext({
2129
- content: [
2130
- {
2131
- type: "text",
2132
- text: formatSuccessMessage(`Edge function '${args.slug}' deleted successfully`, result)
2133
- }
2134
- ]
2260
+ content: [{ type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' deleted successfully`, result) }]
2135
2261
  });
2136
2262
  } catch (error) {
2137
2263
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2138
2264
  return {
2139
- content: [
2140
- {
2141
- type: "text",
2142
- text: `Error deleting function: ${errMsg}`
2143
- }
2144
- ],
2265
+ content: [{ type: "text", text: `Error deleting function: ${errMsg}` }],
2145
2266
  isError: true
2146
2267
  };
2147
2268
  }
2148
2269
  })
2149
2270
  );
2271
+ }
2272
+
2273
+ // src/shared/tools/deployment.ts
2274
+ import { z as z29 } from "zod";
2275
+ import fetch6 from "node-fetch";
2276
+ import { promises as fs3, createWriteStream, createReadStream } from "fs";
2277
+ import { tmpdir as tmpdir2 } from "os";
2278
+ import { join as join2 } from "path";
2279
+ import archiver from "archiver";
2280
+ import FormData2 from "form-data";
2281
+ function isCreateDeploymentResponse(obj) {
2282
+ if (typeof obj !== "object" || obj === null) {
2283
+ return false;
2284
+ }
2285
+ const value = obj;
2286
+ const idOk = "id" in value && (typeof value.id === "string" || typeof value.id === "number");
2287
+ const urlOk = "uploadUrl" in value && typeof value.uploadUrl === "string" && value.uploadUrl.length > 0;
2288
+ const fieldsOk = "uploadFields" in value && typeof value.uploadFields === "object" && value.uploadFields !== null;
2289
+ return idOk && urlOk && fieldsOk;
2290
+ }
2291
+ function registerDeploymentTools(ctx) {
2292
+ const { API_BASE_URL, isRemote, registerTool, withUsageTracking, getApiKey, addBackgroundContext } = ctx;
2150
2293
  registerTool(
2151
2294
  "get-container-logs",
2152
2295
  "Get latest logs from a specific container/service. Use this to help debug problems with your app.",
2153
2296
  {
2154
- apiKey: z23.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2155
- source: z23.enum(["insforge.logs", "postgREST.logs", "postgres.logs", "function.logs"]).describe("Log source to retrieve"),
2156
- limit: z23.number().optional().default(20).describe("Number of logs to return (default: 20)")
2297
+ apiKey: z29.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
2298
+ source: z29.enum(["insforge.logs", "postgREST.logs", "postgres.logs", "function.logs"]).describe("Log source to retrieve"),
2299
+ limit: z29.number().optional().default(20).describe("Number of logs to return (default: 20)")
2157
2300
  },
2158
2301
  withUsageTracking("get-container-logs", async ({ apiKey, source, limit }) => {
2159
2302
  try {
2160
2303
  const actualApiKey = getApiKey(apiKey);
2161
2304
  const queryParams = new URLSearchParams();
2162
2305
  if (limit) queryParams.append("limit", limit.toString());
2163
- let response = await fetch2(`${API_BASE_URL}/api/logs/${source}?${queryParams}`, {
2306
+ let response = await fetch6(`${API_BASE_URL}/api/logs/${source}?${queryParams}`, {
2164
2307
  method: "GET",
2165
- headers: {
2166
- "x-api-key": actualApiKey
2167
- }
2308
+ headers: { "x-api-key": actualApiKey }
2168
2309
  });
2169
2310
  if (response.status === 404) {
2170
- response = await fetch2(`${API_BASE_URL}/api/logs/analytics/${source}?${queryParams}`, {
2311
+ response = await fetch6(`${API_BASE_URL}/api/logs/analytics/${source}?${queryParams}`, {
2171
2312
  method: "GET",
2172
- headers: {
2173
- "x-api-key": actualApiKey
2174
- }
2313
+ headers: { "x-api-key": actualApiKey }
2175
2314
  });
2176
2315
  }
2177
2316
  const result = await handleApiResponse(response);
2178
2317
  return await addBackgroundContext({
2179
- content: [
2180
- {
2181
- type: "text",
2182
- text: formatSuccessMessage(`Latest logs from ${source}`, result)
2183
- }
2184
- ]
2318
+ content: [{ type: "text", text: formatSuccessMessage(`Latest logs from ${source}`, result) }]
2185
2319
  });
2186
2320
  } catch (error) {
2187
2321
  const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2188
2322
  return {
2189
- content: [
2190
- {
2191
- type: "text",
2192
- text: `Error retrieving container logs: ${errMsg}`
2193
- }
2194
- ],
2323
+ content: [{ type: "text", text: `Error retrieving container logs: ${errMsg}` }],
2195
2324
  isError: true
2196
2325
  };
2197
2326
  }
2198
2327
  })
2199
2328
  );
2200
- registerTool(
2201
- "create-deployment",
2202
- "Deploy source code from a directory. This tool zips files, uploads to cloud storage, and triggers deployment with optional environment variables and project settings.",
2203
- {
2204
- sourceDirectory: z23.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 "."'),
2205
- ...startDeploymentRequestSchema.shape
2206
- },
2207
- withUsageTracking("create-deployment", async ({ sourceDirectory, projectSettings, envVars, meta }) => {
2208
- try {
2209
- const isAbsolutePath = sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
2210
- if (!isAbsolutePath) {
2211
- return {
2212
- content: [
2213
- {
2329
+ if (isRemote) {
2330
+ registerTool(
2331
+ "create-deployment",
2332
+ "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.",
2333
+ {
2334
+ 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 "."')
2335
+ },
2336
+ withUsageTracking("create-deployment", async ({ sourceDirectory }) => {
2337
+ try {
2338
+ const isAbsolutePath = sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
2339
+ if (!isAbsolutePath) {
2340
+ return {
2341
+ content: [{
2214
2342
  type: "text",
2215
2343
  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).`
2216
- }
2217
- ],
2344
+ }],
2345
+ isError: true
2346
+ };
2347
+ }
2348
+ const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
2349
+ method: "POST",
2350
+ headers: {
2351
+ "x-api-key": getApiKey(),
2352
+ "Content-Type": "application/json"
2353
+ }
2354
+ });
2355
+ const createResult = await handleApiResponse(createResponse);
2356
+ if (!isCreateDeploymentResponse(createResult)) {
2357
+ throw new Error("Unexpected response format from deployments endpoint");
2358
+ }
2359
+ const { id: deploymentId, uploadUrl, uploadFields } = createResult;
2360
+ const esc = shellEsc;
2361
+ const curlFields = Object.entries(uploadFields).map(([key, value]) => `-F ${esc(`${key}=${value}`)}`).join(" \\\n ");
2362
+ const escapedDir = esc(sourceDirectory);
2363
+ const tmpZip = `/tmp/insforge-deploy-${deploymentId}.zip`;
2364
+ const instructions = `Deployment prepared successfully. Deployment ID: ${deploymentId}
2365
+
2366
+ Please execute the following commands locally, then call the \`start-deployment\` tool:
2367
+
2368
+ ## Step 1: Zip the source directory
2369
+ \`\`\`bash
2370
+ cd ${escapedDir} && zip -r ${tmpZip} . -x "node_modules/*" ".git/*" ".next/*" ".env" ".env.local" "dist/*" "build/*" ".DS_Store" "*.log"
2371
+ \`\`\`
2372
+
2373
+ ## Step 2: Upload the zip file
2374
+ \`\`\`bash
2375
+ curl -X POST ${esc(uploadUrl)} ${curlFields} -F 'file=@${tmpZip};type=application/zip'
2376
+ \`\`\`
2377
+
2378
+ ## Step 3: Clean up
2379
+ \`\`\`bash
2380
+ rm /tmp/insforge-deploy-${deploymentId}.zip
2381
+ \`\`\`
2382
+
2383
+ ## Step 4: Trigger the build
2384
+ Call the \`start-deployment\` tool with deploymentId: "${deploymentId}"
2385
+
2386
+ Run each step in order. If any step fails, do not proceed to the next step.`;
2387
+ return {
2388
+ content: [{ type: "text", text: instructions }]
2389
+ };
2390
+ } catch (error) {
2391
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2392
+ return {
2393
+ content: [{ type: "text", text: `Error preparing deployment: ${errMsg}` }],
2394
+ isError: true
2395
+ };
2396
+ }
2397
+ })
2398
+ );
2399
+ registerTool(
2400
+ "start-deployment",
2401
+ "Trigger a deployment build after uploading source code. Use this after executing the upload commands from create-deployment.",
2402
+ {
2403
+ deploymentId: z29.string().describe("The deployment ID returned by create-deployment"),
2404
+ ...startDeploymentRequestSchema.shape
2405
+ },
2406
+ withUsageTracking("start-deployment", async ({ deploymentId, projectSettings, envVars, meta }) => {
2407
+ try {
2408
+ const startBody = {};
2409
+ if (projectSettings) startBody.projectSettings = projectSettings;
2410
+ if (envVars) startBody.envVars = envVars;
2411
+ if (meta) startBody.meta = meta;
2412
+ const startResponse = await fetch6(`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`, {
2413
+ method: "POST",
2414
+ headers: {
2415
+ "x-api-key": getApiKey(),
2416
+ "Content-Type": "application/json"
2417
+ },
2418
+ body: JSON.stringify(startBody)
2419
+ });
2420
+ const startResult = await handleApiResponse(startResponse);
2421
+ return await addBackgroundContext({
2422
+ content: [{
2423
+ type: "text",
2424
+ text: formatSuccessMessage("Deployment started", startResult) + "\n\nNote: You can check deployment status by querying the system.deployments table."
2425
+ }]
2426
+ });
2427
+ } catch (error) {
2428
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2429
+ return {
2430
+ content: [{ type: "text", text: `Error starting deployment: ${errMsg}` }],
2218
2431
  isError: true
2219
2432
  };
2220
2433
  }
2434
+ })
2435
+ );
2436
+ } else {
2437
+ registerTool(
2438
+ "create-deployment",
2439
+ "Deploy source code from a directory. This tool zips files, uploads to cloud storage, and triggers deployment with optional environment variables and project settings.",
2440
+ {
2441
+ 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 "."'),
2442
+ ...startDeploymentRequestSchema.shape
2443
+ },
2444
+ withUsageTracking("create-deployment", async ({ sourceDirectory, projectSettings, envVars, meta }) => {
2221
2445
  try {
2222
- const stats = await fs.stat(sourceDirectory);
2223
- if (!stats.isDirectory()) {
2446
+ const isAbsolutePath = sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
2447
+ if (!isAbsolutePath) {
2224
2448
  return {
2225
- content: [
2226
- {
2227
- type: "text",
2228
- text: `Error: "${sourceDirectory}" is not a directory. Please provide a path to a directory containing the source code.`
2229
- }
2230
- ],
2449
+ content: [{
2450
+ type: "text",
2451
+ 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).`
2452
+ }],
2231
2453
  isError: true
2232
2454
  };
2233
2455
  }
2234
- } catch (statError) {
2235
- return {
2236
- content: [
2237
- {
2456
+ try {
2457
+ const stats = await fs3.stat(sourceDirectory);
2458
+ if (!stats.isDirectory()) {
2459
+ return {
2460
+ content: [{
2461
+ type: "text",
2462
+ text: `Error: "${sourceDirectory}" is not a directory. Please provide a path to a directory containing the source code.`
2463
+ }],
2464
+ isError: true
2465
+ };
2466
+ }
2467
+ } catch {
2468
+ return {
2469
+ content: [{
2238
2470
  type: "text",
2239
2471
  text: `Error: Directory "${sourceDirectory}" does not exist or is not accessible. Please verify the path is correct.`
2240
- }
2241
- ],
2242
- isError: true
2243
- };
2244
- }
2245
- const resolvedSourceDir = sourceDirectory;
2246
- const createResponse = await fetch2(`${API_BASE_URL}/api/deployments`, {
2247
- method: "POST",
2248
- headers: {
2249
- "x-api-key": getApiKey(),
2250
- "Content-Type": "application/json"
2472
+ }],
2473
+ isError: true
2474
+ };
2251
2475
  }
2252
- });
2253
- const createResult = await handleApiResponse(createResponse);
2254
- const { id: deploymentId, uploadUrl, uploadFields } = createResult;
2255
- const zipBuffer = await new Promise((resolve, reject) => {
2256
- const archive = archiver("zip", { zlib: { level: 9 } });
2257
- const chunks = [];
2258
- archive.on("data", (chunk) => chunks.push(chunk));
2259
- archive.on("end", () => resolve(Buffer.concat(chunks)));
2260
- archive.on("error", (err) => reject(err));
2261
- const excludePatterns = [
2262
- "node_modules",
2263
- ".git",
2264
- ".next",
2265
- ".env",
2266
- ".env.local",
2267
- "dist",
2268
- "build",
2269
- ".DS_Store"
2270
- ];
2271
- archive.directory(resolvedSourceDir, false, (entry) => {
2272
- const normalizedName = entry.name.replace(/\\/g, "/");
2273
- for (const pattern of excludePatterns) {
2274
- if (normalizedName.startsWith(pattern + "/") || normalizedName === pattern || normalizedName.endsWith("/" + pattern) || normalizedName.includes("/" + pattern + "/")) {
2275
- return false;
2276
- }
2476
+ const resolvedSourceDir = sourceDirectory;
2477
+ const createResponse = await fetch6(`${API_BASE_URL}/api/deployments`, {
2478
+ method: "POST",
2479
+ headers: {
2480
+ "x-api-key": getApiKey(),
2481
+ "Content-Type": "application/json"
2277
2482
  }
2278
- if (normalizedName.endsWith(".log")) {
2279
- return false;
2483
+ });
2484
+ const createResult = await handleApiResponse(createResponse);
2485
+ if (!isCreateDeploymentResponse(createResult)) {
2486
+ throw new Error("Unexpected response format from deployments endpoint");
2487
+ }
2488
+ const { id: deploymentId, uploadUrl, uploadFields } = createResult;
2489
+ const tmpZipPath = join2(tmpdir2(), `insforge-deploy-${deploymentId}.zip`);
2490
+ try {
2491
+ await new Promise((resolve, reject) => {
2492
+ const archive = archiver("zip", { zlib: { level: 9 } });
2493
+ const output = createWriteStream(tmpZipPath);
2494
+ output.on("close", resolve);
2495
+ output.on("error", reject);
2496
+ archive.on("error", reject);
2497
+ const excludePatterns = ["node_modules", ".git", ".next", ".env", ".env.local", "dist", "build", ".DS_Store"];
2498
+ archive.directory(resolvedSourceDir, false, (entry) => {
2499
+ const normalizedName = entry.name.replace(/\\/g, "/");
2500
+ for (const pattern of excludePatterns) {
2501
+ if (normalizedName.startsWith(pattern + "/") || normalizedName === pattern || normalizedName.endsWith("/" + pattern) || normalizedName.includes("/" + pattern + "/")) {
2502
+ return false;
2503
+ }
2504
+ }
2505
+ if (normalizedName.endsWith(".log")) return false;
2506
+ return entry;
2507
+ });
2508
+ archive.pipe(output);
2509
+ archive.finalize();
2510
+ });
2511
+ const { size: zipSize } = await fs3.stat(tmpZipPath);
2512
+ const uploadFormData = new FormData2();
2513
+ for (const [key, value] of Object.entries(uploadFields)) {
2514
+ uploadFormData.append(key, value);
2280
2515
  }
2281
- return entry;
2516
+ uploadFormData.append("file", createReadStream(tmpZipPath), {
2517
+ filename: "deployment.zip",
2518
+ contentType: "application/zip",
2519
+ knownLength: zipSize
2520
+ });
2521
+ const uploadResponse = await fetch6(uploadUrl, {
2522
+ method: "POST",
2523
+ body: uploadFormData,
2524
+ headers: uploadFormData.getHeaders()
2525
+ });
2526
+ if (!uploadResponse.ok) {
2527
+ const uploadError = await uploadResponse.text();
2528
+ throw new Error(`Failed to upload zip file: ${uploadError}`);
2529
+ }
2530
+ } finally {
2531
+ await fs3.rm(tmpZipPath, { force: true }).catch(() => void 0);
2532
+ }
2533
+ const startBody = {};
2534
+ if (projectSettings) startBody.projectSettings = projectSettings;
2535
+ if (envVars) startBody.envVars = envVars;
2536
+ if (meta) startBody.meta = meta;
2537
+ const startResponse = await fetch6(`${API_BASE_URL}/api/deployments/${encodeURIComponent(deploymentId)}/start`, {
2538
+ method: "POST",
2539
+ headers: {
2540
+ "x-api-key": getApiKey(),
2541
+ "Content-Type": "application/json"
2542
+ },
2543
+ body: JSON.stringify(startBody)
2282
2544
  });
2283
- archive.finalize();
2284
- });
2285
- const uploadFormData = new FormData();
2286
- for (const [key, value] of Object.entries(uploadFields)) {
2287
- uploadFormData.append(key, value);
2288
- }
2289
- uploadFormData.append("file", zipBuffer, {
2290
- filename: "deployment.zip",
2291
- contentType: "application/zip"
2292
- });
2293
- const uploadResponse = await fetch2(uploadUrl, {
2294
- method: "POST",
2295
- body: uploadFormData,
2296
- headers: uploadFormData.getHeaders()
2297
- });
2298
- if (!uploadResponse.ok) {
2299
- const uploadError = await uploadResponse.text();
2300
- throw new Error(`Failed to upload zip file: ${uploadError}`);
2301
- }
2302
- const startBody = {};
2303
- if (projectSettings) startBody.projectSettings = projectSettings;
2304
- if (envVars) startBody.envVars = envVars;
2305
- if (meta) startBody.meta = meta;
2306
- const startResponse = await fetch2(`${API_BASE_URL}/api/deployments/${deploymentId}/start`, {
2307
- method: "POST",
2308
- headers: {
2309
- "x-api-key": getApiKey(),
2310
- "Content-Type": "application/json"
2311
- },
2312
- body: JSON.stringify(startBody)
2313
- });
2314
- const startResult = await handleApiResponse(startResponse);
2315
- return await addBackgroundContext({
2316
- content: [
2317
- {
2545
+ const startResult = await handleApiResponse(startResponse);
2546
+ return await addBackgroundContext({
2547
+ content: [{
2318
2548
  type: "text",
2319
2549
  text: formatSuccessMessage("Deployment started", startResult) + "\n\nNote: You can check deployment status by querying the system.deployments table."
2320
- }
2321
- ]
2322
- });
2550
+ }]
2551
+ });
2552
+ } catch (error) {
2553
+ const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2554
+ return {
2555
+ content: [{ type: "text", text: `Error creating deployment: ${errMsg}` }],
2556
+ isError: true
2557
+ };
2558
+ }
2559
+ })
2560
+ );
2561
+ }
2562
+ }
2563
+
2564
+ // src/shared/tools/index.ts
2565
+ var TOOL_VERSION_REQUIREMENTS = {
2566
+ // Schedule tools - require backend v1.1.1+
2567
+ // 'upsert-schedule': { minVersion: '1.1.1' },
2568
+ // 'delete-schedule': { minVersion: '1.1.1' },
2569
+ // 'get-schedules': { minVersion: '1.1.1' },
2570
+ // 'get-schedule-logs': { minVersion: '1.1.1' },
2571
+ "create-deployment": { minVersion: "1.4.7" },
2572
+ "fetch-sdk-docs": { minVersion: "1.5.1" }
2573
+ // Example of a deprecated tool (uncomment when needed):
2574
+ // 'legacy-tool': { minVersion: '1.0.0', maxVersion: '1.5.0' },
2575
+ };
2576
+ var LOCAL_ONLY_TOOLS = /* @__PURE__ */ new Set([
2577
+ "bulk-upsert"
2578
+ // Requires reading local data file (filePath is required)
2579
+ ]);
2580
+ function compareVersions(v1, v2) {
2581
+ const clean1 = v1.replace(/^v/, "").split("-")[0];
2582
+ const clean2 = v2.replace(/^v/, "").split("-")[0];
2583
+ const parts1 = clean1.split(".").map(Number);
2584
+ const parts2 = clean2.split(".").map(Number);
2585
+ for (let i = 0; i < Math.max(parts1.length, parts2.length); i++) {
2586
+ const part1 = parts1[i] || 0;
2587
+ const part2 = parts2[i] || 0;
2588
+ if (part1 > part2) return 1;
2589
+ if (part1 < part2) return -1;
2590
+ }
2591
+ return 0;
2592
+ }
2593
+ function shouldRegisterTool(toolName, backendVersion) {
2594
+ const requirement = TOOL_VERSION_REQUIREMENTS[toolName];
2595
+ if (!requirement) return true;
2596
+ const { minVersion, maxVersion } = requirement;
2597
+ if (minVersion && compareVersions(backendVersion, minVersion) < 0) return false;
2598
+ if (maxVersion && compareVersions(backendVersion, maxVersion) > 0) return false;
2599
+ return true;
2600
+ }
2601
+ async function fetchBackendVersion(apiBaseUrl) {
2602
+ const controller = new AbortController();
2603
+ const timeoutId = setTimeout(() => controller.abort(), 1e4);
2604
+ try {
2605
+ const response = await fetch7(`${apiBaseUrl}/api/health`, {
2606
+ method: "GET",
2607
+ headers: { "Content-Type": "application/json" },
2608
+ signal: controller.signal
2609
+ });
2610
+ if (!response.ok) {
2611
+ throw new Error(`Health check failed with status ${response.status}`);
2612
+ }
2613
+ const health = await response.json();
2614
+ if (!health.version || typeof health.version !== "string") {
2615
+ throw new Error("Health check returned invalid version field");
2616
+ }
2617
+ return health.version;
2618
+ } catch (error) {
2619
+ if (error instanceof Error && error.name === "AbortError") {
2620
+ throw new Error(`Health check timed out after 10s \u2014 is the backend running at ${apiBaseUrl}?`, { cause: error });
2621
+ }
2622
+ throw error;
2623
+ } finally {
2624
+ clearTimeout(timeoutId);
2625
+ }
2626
+ }
2627
+ async function registerInsforgeTools(server, config = {}) {
2628
+ const GLOBAL_API_KEY = config.apiKey || process.env.API_KEY || "";
2629
+ const API_BASE_URL = config.apiBaseUrl || process.env.API_BASE_URL || "http://localhost:7130";
2630
+ const isRemote = config.mode === "remote";
2631
+ const usageTracker = new UsageTracker(API_BASE_URL, GLOBAL_API_KEY);
2632
+ let backendVersion;
2633
+ try {
2634
+ backendVersion = await fetchBackendVersion(API_BASE_URL);
2635
+ console.error(`Backend version: ${backendVersion}`);
2636
+ } catch (error) {
2637
+ const msg = error instanceof Error ? error.message : String(error);
2638
+ console.error(`Failed to fetch backend version: ${msg}`);
2639
+ throw new Error(`Cannot initialize tools: backend at ${API_BASE_URL} is unreachable. ${msg}`, { cause: error });
2640
+ }
2641
+ let toolCount = 0;
2642
+ const registerTool = (toolName, ...args) => {
2643
+ if (isRemote && LOCAL_ONLY_TOOLS.has(toolName)) {
2644
+ console.error(`Skipping tool '${toolName}': requires local filesystem (remote mode)`);
2645
+ return false;
2646
+ }
2647
+ if (shouldRegisterTool(toolName, backendVersion)) {
2648
+ server.tool(toolName, ...args);
2649
+ toolCount++;
2650
+ return true;
2651
+ } else {
2652
+ const req = TOOL_VERSION_REQUIREMENTS[toolName];
2653
+ const reason = req?.minVersion && compareVersions(backendVersion, req.minVersion) < 0 ? `requires backend >= ${req.minVersion}` : `deprecated after backend ${req?.maxVersion}`;
2654
+ console.error(`Skipping tool '${toolName}': ${reason} (current: ${backendVersion})`);
2655
+ return false;
2656
+ }
2657
+ };
2658
+ async function trackToolUsage(toolName, success = true) {
2659
+ if (GLOBAL_API_KEY) {
2660
+ await usageTracker.trackUsage(toolName, success).catch((err) => {
2661
+ console.error(`Failed to track usage for '${toolName}':`, err);
2662
+ });
2663
+ }
2664
+ }
2665
+ function withUsageTracking(toolName, handler) {
2666
+ return async (...args) => {
2667
+ try {
2668
+ const result = await handler(...args);
2669
+ const isStructuredError = result !== null && typeof result === "object" && "isError" in result && result["isError"] === true;
2670
+ void trackToolUsage(toolName, !isStructuredError);
2671
+ return result;
2323
2672
  } catch (error) {
2324
- const errMsg = error instanceof Error ? error.message : "Unknown error occurred";
2325
- return {
2326
- content: [
2327
- {
2673
+ void trackToolUsage(toolName, false);
2674
+ throw error;
2675
+ }
2676
+ };
2677
+ }
2678
+ const getApiKey = (toolApiKey) => {
2679
+ const apiKey = toolApiKey?.trim() || GLOBAL_API_KEY;
2680
+ if (!apiKey) {
2681
+ throw new Error("API key is required. Pass --api_key when starting the MCP server.");
2682
+ }
2683
+ return apiKey;
2684
+ };
2685
+ const addBackgroundContext = async (response) => {
2686
+ const isLegacyVersion = compareVersions(backendVersion, "1.1.7") < 0;
2687
+ if (isLegacyVersion) {
2688
+ try {
2689
+ const docResponse = await fetch7(`${API_BASE_URL}/api/docs/instructions`, {
2690
+ method: "GET",
2691
+ headers: { "Content-Type": "application/json" }
2692
+ });
2693
+ if (docResponse.ok) {
2694
+ const result = await handleApiResponse(docResponse);
2695
+ if (result && typeof result === "object" && "content" in result) {
2696
+ response.content.push({
2328
2697
  type: "text",
2329
- text: `Error creating deployment: ${errMsg}`
2330
- }
2331
- ],
2332
- isError: true
2333
- };
2698
+ text: `
2699
+
2700
+ ---
2701
+ \u{1F527} INSFORGE DEVELOPMENT RULES (Auto-loaded):
2702
+ ${result.content}`
2703
+ });
2704
+ }
2705
+ }
2706
+ } catch (error) {
2707
+ console.error("Failed to fetch insforge-instructions.md:", error);
2334
2708
  }
2335
- })
2336
- );
2709
+ }
2710
+ return response;
2711
+ };
2712
+ const ctx = {
2713
+ API_BASE_URL,
2714
+ isRemote,
2715
+ registerTool,
2716
+ withUsageTracking,
2717
+ getApiKey,
2718
+ addBackgroundContext
2719
+ };
2720
+ registerDocsTools(ctx);
2721
+ registerDatabaseTools(ctx);
2722
+ registerStorageTools(ctx);
2723
+ registerFunctionTools(ctx);
2724
+ registerDeploymentTools(ctx);
2337
2725
  return {
2338
2726
  apiKey: GLOBAL_API_KEY,
2339
2727
  apiBaseUrl: API_BASE_URL,
2340
- toolCount,
2341
- backendVersion
2728
+ backendVersion,
2729
+ toolCount
2342
2730
  };
2343
2731
  }
2344
2732