@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.
- package/README.md +21 -0
- package/dist/{chunk-3S2HFIGS.js → chunk-DZ5W3BSP.js} +1057 -669
- package/dist/http-server.js +2087 -130
- package/dist/index.js +3 -2
- package/package.json +34 -7
- package/server.json +29 -6
|
@@ -1,29 +1,37 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/shared/tools.ts
|
|
4
|
-
import
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
//
|
|
1300
|
-
import
|
|
1301
|
-
var
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
//
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
//
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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:
|
|
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:
|
|
1584
|
-
tableName:
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
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
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
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
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
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
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
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
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
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
|
|
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
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
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:
|
|
1884
|
+
apiKey: z26.string().optional().describe("API key for authentication (optional if provided via --api_key)"),
|
|
1784
1885
|
...bulkUpsertRequestSchema.shape,
|
|
1785
|
-
filePath:
|
|
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
|
|
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
|
|
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
|
|
1807
|
-
|
|
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
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
1917
|
-
|
|
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
|
|
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
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
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
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
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
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
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
|
-
|
|
2073
|
-
|
|
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
|
-
|
|
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
|
|
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:
|
|
2155
|
-
source:
|
|
2156
|
-
limit:
|
|
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
|
|
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
|
|
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
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
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
|
|
2223
|
-
if (!
|
|
2446
|
+
const isAbsolutePath = sourceDirectory.startsWith("/") || /^[a-zA-Z]:[/\\]/.test(sourceDirectory);
|
|
2447
|
+
if (!isAbsolutePath) {
|
|
2224
2448
|
return {
|
|
2225
|
-
content: [
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
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
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
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
|
-
|
|
2279
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
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: `
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
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
|
-
|
|
2341
|
-
|
|
2728
|
+
backendVersion,
|
|
2729
|
+
toolCount
|
|
2342
2730
|
};
|
|
2343
2731
|
}
|
|
2344
2732
|
|