UNPKG

@insforge/mcp

Version:

MCP (Model Context Protocol) server for Insforge backend-as-a-service

1,334 lines (1,316 loc) 41 kB
#!/usr/bin/env node // src/index.ts import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z as z12 } from "zod"; import fetch2 from "node-fetch"; import FormData from "form-data"; import { program } from "commander"; import { promises as fs } from "fs"; // src/response-handler.ts async function handleApiResponse(response) { const responseData = await response.json(); if (!response.ok) { const errorData = responseData; let fullMessage = errorData.message || errorData.error || "Unknown error"; if (errorData.nextAction) { fullMessage += `. ${errorData.nextAction}`; } throw new Error(fullMessage); } return responseData; } function formatSuccessMessage(operation, data) { if (data && typeof data === "object" && "message" in data) { return `${data.message} ${JSON.stringify(data, null, 2)}`; } return `${operation} completed successfully: ${JSON.stringify(data, null, 2)}`; } // src/usage-tracker.ts import fetch from "node-fetch"; var UsageTracker = class { apiBaseUrl; apiKey; constructor(apiBaseUrl, apiKey) { this.apiBaseUrl = apiBaseUrl; this.apiKey = apiKey; } async trackUsage(toolName, success = true) { if (!this.apiKey) { return; } try { const payload = { tool_name: toolName, success, timestamp: (/* @__PURE__ */ new Date()).toISOString() }; await fetch(`${this.apiBaseUrl}/api/usage/mcp`, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": this.apiKey }, body: JSON.stringify(payload) }); } catch (error) { console.error("Failed to track usage:", error); } } }; // node_modules/@insforge/shared-schemas/dist/database.schema.js import { z } from "zod"; var ColumnType; (function(ColumnType2) { ColumnType2["STRING"] = "string"; ColumnType2["DATE"] = "date"; ColumnType2["DATETIME"] = "datetime"; ColumnType2["INTEGER"] = "integer"; ColumnType2["FLOAT"] = "float"; ColumnType2["BOOLEAN"] = "boolean"; ColumnType2["UUID"] = "uuid"; ColumnType2["JSON"] = "json"; })(ColumnType || (ColumnType = {})); var onUpdateActionSchema = z.enum(["CASCADE", "RESTRICT", "NO ACTION"]); var onDeleteActionSchema = z.enum([ "CASCADE", "SET NULL", "SET DEFAULT", "RESTRICT", "NO ACTION" ]); var columnTypeSchema = z.enum([ ColumnType.STRING, ColumnType.DATE, ColumnType.DATETIME, ColumnType.INTEGER, ColumnType.FLOAT, ColumnType.BOOLEAN, ColumnType.UUID, ColumnType.JSON ]); var foreignKeySchema = z.object({ referenceTable: z.string().min(1, "Target table cannot be empty"), referenceColumn: z.string().min(1, "Target column cannot be empty"), onDelete: onDeleteActionSchema, onUpdate: onUpdateActionSchema }); var columnSchema = z.object({ columnName: z.string().min(1, "Column name cannot be empty").max(64, "Column name must be less than 64 characters"), type: z.union([columnTypeSchema, z.string()]), defaultValue: z.string().optional(), isPrimaryKey: z.boolean().optional(), isNullable: z.boolean(), isUnique: z.boolean(), foreignKey: foreignKeySchema.optional() }); var tableSchema = z.object({ tableName: z.string().min(1, "Table name cannot be empty").max(64, "Table name must be less than 64 characters"), columns: z.array(columnSchema).min(1, "At least one column is required"), recordCount: z.number().default(0), createdAt: z.string().optional(), updatedAt: z.string().optional() }); // node_modules/@insforge/shared-schemas/dist/database-api.schema.js import { z as z2 } from "zod"; var createTableRequestSchema = tableSchema.pick({ tableName: true, columns: true }).extend({ rlsEnabled: z2.boolean().default(true) }); var createTableResponseSchema = tableSchema.pick({ tableName: true, columns: true }).extend({ message: z2.string(), autoFields: z2.array(z2.string()), nextActions: z2.string() }); var updateTableSchemaRequestSchema = z2.object({ addColumns: z2.array(columnSchema.omit({ foreignKey: true })).optional(), dropColumns: z2.array(z2.string()).optional(), updateColumns: z2.array(z2.object({ columnName: z2.string(), defaultValue: z2.string().optional(), newColumnName: z2.string().min(1, "New column name cannot be empty").max(64, "New column name must be less than 64 characters").optional() })).optional(), addForeignKeys: z2.array(z2.object({ columnName: z2.string().min(1, "Column name is required for adding foreign key"), foreignKey: foreignKeySchema })).optional(), dropForeignKeys: z2.array(z2.string()).optional(), renameTable: z2.object({ newTableName: z2.string().min(1, "New table name cannot be empty").max(64, "New table name must be less than 64 characters") }).optional() }); var updateTableSchemaResponse = z2.object({ message: z2.string(), tableName: z2.string(), operations: z2.array(z2.string()) }); var deleteTableResponse = z2.object({ message: z2.string(), tableName: z2.string(), nextActions: z2.string() }); var rawSQLRequestSchema = z2.object({ query: z2.string().min(1, "Query is required"), params: z2.array(z2.any()).optional() }); var rawSQLResponseSchema = z2.object({ rows: z2.array(z2.any()), rowCount: z2.number().nullable(), fields: z2.array(z2.object({ name: z2.string(), dataTypeID: z2.number() })).optional() }); var exportRequestSchema = z2.object({ tables: z2.array(z2.string()).optional(), format: z2.enum(["sql", "json"]).default("sql"), includeData: z2.boolean().default(true), includeFunctions: z2.boolean().default(false), includeSequences: z2.boolean().default(false), includeViews: z2.boolean().default(false), rowLimit: z2.number().int().positive().max(1e4).default(1e3) }); var exportJsonDataSchema = z2.object({ timestamp: z2.string(), tables: z2.record(z2.string(), z2.object({ schema: z2.array(z2.object({ columnName: z2.string(), dataType: z2.string(), characterMaximumLength: z2.number().nullable(), isNullable: z2.string(), columnDefault: z2.string().nullable() })), indexes: z2.array(z2.object({ indexname: z2.string(), indexdef: z2.string(), isUnique: z2.boolean().nullable(), isPrimary: z2.boolean().nullable() })), foreignKeys: z2.array(z2.object({ constraintName: z2.string(), columnName: z2.string(), foreignTableName: z2.string(), foreignColumnName: z2.string(), deleteRule: z2.string().nullable(), updateRule: z2.string().nullable() })), rlsEnabled: z2.boolean().optional(), policies: z2.array(z2.object({ policyname: z2.string(), cmd: z2.string(), roles: z2.array(z2.string()), qual: z2.string().nullable(), withCheck: z2.string().nullable() })), triggers: z2.array(z2.object({ triggerName: z2.string(), actionTiming: z2.string(), eventManipulation: z2.string(), actionOrientation: z2.string(), actionCondition: z2.string().nullable(), actionStatement: z2.string(), newTable: z2.string().nullable(), oldTable: z2.string().nullable() })), rows: z2.array(z2.any()).optional() })), functions: z2.array(z2.object({ functionName: z2.string(), functionDef: z2.string(), kind: z2.string() })), sequences: z2.array(z2.object({ sequenceName: z2.string(), startValue: z2.string(), increment: z2.string(), minValue: z2.string().nullable(), maxValue: z2.string().nullable(), cycle: z2.string() })), views: z2.array(z2.object({ viewName: z2.string(), definition: z2.string() })) }); var exportResponseSchema = z2.object({ format: z2.enum(["sql", "json"]), data: z2.union([z2.string(), exportJsonDataSchema]), timestamp: z2.string() }); var importRequestSchema = z2.object({ truncate: z2.union([ z2.boolean(), z2.string().transform((val) => { if (val === "true") return true; if (val === "false") return false; throw new Error("Invalid boolean string"); }) ]).default(false) }); var importResponseSchema = z2.object({ success: z2.boolean(), message: z2.string(), filename: z2.string(), tables: z2.array(z2.string()), rowsImported: z2.number(), fileSize: z2.number() }); var bulkUpsertRequestSchema = z2.object({ table: z2.string().min(1, "Table name is required"), upsertKey: z2.string().optional() // Note: File handling is done at the API layer via multipart/form-data }); var bulkUpsertResponseSchema = z2.object({ success: z2.boolean(), message: z2.string(), table: z2.string(), rowsAffected: z2.number(), totalRecords: z2.number(), filename: z2.string() }); // node_modules/@insforge/shared-schemas/dist/storage.schema.js import { z as z3 } from "zod"; var storageFileSchema = z3.object({ key: z3.string(), bucket: z3.string(), size: z3.number(), mimeType: z3.string().optional(), uploadedAt: z3.string(), url: z3.string() }); var storageBucketSchema = z3.object({ name: z3.string(), public: z3.boolean(), createdAt: z3.string() }); // node_modules/@insforge/shared-schemas/dist/storage-api.schema.js import { z as z4 } from "zod"; var createBucketRequestSchema = z4.object({ bucketName: z4.string().min(1, "Bucket name cannot be empty"), isPublic: z4.boolean().default(true) }); var updateBucketRequestSchema = z4.object({ isPublic: z4.boolean() }); var listObjectsResponseSchema = z4.object({ objects: z4.array(storageFileSchema), pagination: z4.object({ offset: z4.number(), limit: z4.number(), total: z4.number() }) }); var uploadStrategyRequestSchema = z4.object({ filename: z4.string().min(1, "Filename cannot be empty"), contentType: z4.string().optional(), size: z4.number().optional() }); var uploadStrategyResponseSchema = z4.object({ method: z4.enum(["presigned", "direct"]), uploadUrl: z4.string(), fields: z4.record(z4.string()).optional(), key: z4.string(), confirmRequired: z4.boolean(), confirmUrl: z4.string().optional(), expiresAt: z4.date().optional() }); var downloadStrategyRequestSchema = z4.object({ expiresIn: z4.number().optional().default(3600) }); var downloadStrategyResponseSchema = z4.object({ method: z4.enum(["presigned", "direct"]), url: z4.string(), expiresAt: z4.date().optional(), headers: z4.record(z4.string()).optional() }); var confirmUploadRequestSchema = z4.object({ size: z4.number(), contentType: z4.string().optional(), etag: z4.string().optional() }); // node_modules/@insforge/shared-schemas/dist/auth.schema.js import { z as z5 } from "zod"; var userIdSchema = z5.string().uuid("Invalid user ID format"); var emailSchema = z5.string().email("Invalid email format").toLowerCase().trim(); var passwordSchema = z5.string().min(6, "Password must be at least 6 characters").max(32, "Password must be less than 32 characters"); var nameSchema = z5.string().min(1, "Name is required").max(100, "Name must be less than 100 characters").trim(); var roleSchema = z5.enum(["authenticated", "project_admin"]); var userSchema = z5.object({ id: userIdSchema, email: emailSchema, name: nameSchema, emailVerified: z5.boolean(), identities: z5.array(z5.object({ provider: z5.string() })).optional(), providerType: z5.string().optional(), createdAt: z5.string(), // PostgreSQL timestamp updatedAt: z5.string() // PostgreSQL timestamp }); var oAuthProvidersSchema = z5.enum(["google", "github"]); var oAuthStateSchema = z5.object({ provider: oAuthProvidersSchema, redirectUri: z5.string().url().optional() }); var oAuthConfigSchema = z5.object({ provider: z5.string(), clientId: z5.string().optional(), scopes: z5.array(z5.string()).optional(), redirectUri: z5.string().optional(), useSharedKey: z5.boolean() }); var tokenPayloadSchema = z5.object({ sub: userIdSchema, // Subject (user ID) email: emailSchema, role: roleSchema, iat: z5.number().optional(), // Issued at exp: z5.number().optional() // Expiration }); // node_modules/@insforge/shared-schemas/dist/auth-api.schema.js import { z as z6 } from "zod"; var paginationSchema = z6.object({ limit: z6.string().optional(), offset: z6.string().optional() }); var createUserRequestSchema = z6.object({ email: emailSchema, password: passwordSchema, name: nameSchema.optional() }); var createSessionRequestSchema = z6.object({ email: emailSchema, password: passwordSchema }); var exchangeAdminSessionRequestSchema = z6.object({ code: z6.string() }); var listUsersRequestSchema = paginationSchema.extend({ search: z6.string().optional() }).optional(); var deleteUsersRequestSchema = z6.object({ userIds: z6.array(userIdSchema).min(1, "At least one user ID is required") }); var createUserResponseSchema = z6.object({ user: userSchema, accessToken: z6.string() }); var getCurrentSessionResponseSchema = z6.object({ user: z6.object({ id: userIdSchema, email: emailSchema, role: roleSchema }) }); var listUsersResponseSchema = z6.object({ data: z6.array(userSchema), pagination: z6.object({ offset: z6.number(), limit: z6.number(), total: z6.number() }) }); var deleteUsersResponseSchema = z6.object({ message: z6.string(), deletedCount: z6.number().int().nonnegative() }); var getOauthUrlResponseSchema = z6.object({ authUrl: z6.string().url() }); var createOAuthConfigRequestSchema = oAuthConfigSchema.extend({ clientSecret: z6.string().optional() }); var updateOAuthConfigRequestSchema = oAuthConfigSchema.extend({ clientSecret: z6.string().optional() }).omit({ provider: true }); var listOAuthConfigsResponseSchema = z6.object({ data: z6.array(oAuthConfigSchema), count: z6.number() }); var authErrorResponseSchema = z6.object({ error: z6.string(), message: z6.string(), statusCode: z6.number().int(), nextActions: z6.string().optional() }); // node_modules/@insforge/shared-schemas/dist/metadata.schema.js import { z as z7 } from "zod"; var authMetadataSchema = z7.object({ oauths: z7.array(oAuthConfigSchema) }); var databaseMetadataSchema = z7.object({ tables: z7.array(tableSchema), totalSize: z7.number() }); var bucketMetadataSchema = storageBucketSchema.extend({ objectCount: z7.number().optional() }); var storageMetadataSchema = z7.object({ buckets: z7.array(bucketMetadataSchema), totalSize: z7.number() }); var edgeFunctionMetadataSchema = z7.object({ slug: z7.string(), name: z7.string(), description: z7.string().nullable(), status: z7.string() }); var aiMetadataSchema = z7.object({ models: z7.array(z7.object({ inputModality: z7.array(z7.string()), outputModality: z7.array(z7.string()), modelId: z7.string() })) }); var appMetaDataSchema = z7.object({ auth: authMetadataSchema, database: databaseMetadataSchema, storage: storageMetadataSchema, aiIntegration: aiMetadataSchema.optional(), functions: z7.array(edgeFunctionMetadataSchema), version: z7.string().optional() }); // node_modules/@insforge/shared-schemas/dist/ai.schema.js import { z as z8 } from "zod"; var modalitySchema = z8.enum(["text", "image"]); var aiConfigurationSchema = z8.object({ id: z8.string().uuid(), inputModality: z8.array(modalitySchema).min(1), outputModality: z8.array(modalitySchema).min(1), provider: z8.string(), modelId: z8.string(), systemPrompt: z8.string().optional() }); var aiConfigurationWithUsageSchema = aiConfigurationSchema.extend({ usageStats: z8.object({ totalInputTokens: z8.number(), totalOutputTokens: z8.number(), totalTokens: z8.number(), totalImageCount: z8.number(), totalRequests: z8.number() }).optional() }); var aiUsageDataSchema = z8.object({ configId: z8.string().uuid(), inputTokens: z8.number().int().optional(), outputTokens: z8.number().int().optional(), imageCount: z8.number().int().optional(), imageResolution: z8.string().optional() }); var aiUsageRecordSchema = aiUsageDataSchema.extend({ id: z8.string().uuid(), createdAt: z8.date() }); var aiUsageSummarySchema = z8.object({ totalInputTokens: z8.number(), totalOutputTokens: z8.number(), totalTokens: z8.number(), totalImageCount: z8.number(), totalRequests: z8.number() }); // node_modules/@insforge/shared-schemas/dist/ai-api.schema.js import { z as z9 } from "zod"; var chatMessageSchema = z9.object({ role: z9.enum(["user", "assistant", "system"]), content: z9.string(), images: z9.array(z9.object({ url: z9.string() })).optional() }); var chatCompletionRequestSchema = z9.object({ model: z9.string(), messages: z9.array(chatMessageSchema), temperature: z9.number().min(0).max(2).optional(), maxTokens: z9.number().positive().optional(), topP: z9.number().min(0).max(1).optional(), stream: z9.boolean().optional() }); var chatCompletionResponseSchema = z9.object({ text: z9.string(), metadata: z9.object({ model: z9.string(), usage: z9.object({ promptTokens: z9.number().optional(), completionTokens: z9.number().optional(), totalTokens: z9.number().optional() }).optional() }).optional() }); var imageGenerationRequestSchema = z9.object({ model: z9.string(), prompt: z9.string(), images: z9.array(z9.object({ url: z9.string() })).optional() }); var imageGenerationResponseSchema = z9.object({ text: z9.string().optional(), images: z9.array(z9.object({ type: z9.literal("imageUrl"), imageUrl: z9.string() })), metadata: z9.object({ model: z9.string(), usage: z9.object({ promptTokens: z9.number().optional(), completionTokens: z9.number().optional(), totalTokens: z9.number().optional() }).optional() }).optional() }); var openRouterModelSchema = z9.object({ id: z9.string(), name: z9.string(), created: z9.number(), description: z9.string().optional(), architecture: z9.object({ inputModalities: z9.array(z9.string()), outputModalities: z9.array(z9.string()), tokenizer: z9.string(), instructType: z9.string() }).optional(), topProvider: z9.object({ isModerated: z9.boolean(), contextLength: z9.number(), maxCompletionTokens: z9.number() }).optional(), pricing: z9.object({ prompt: z9.string(), completion: z9.string(), image: z9.string().optional(), request: z9.string().optional(), webSearch: z9.string().optional(), internalReasoning: z9.string().optional(), inputCacheRead: z9.string().optional(), inputCacheWrite: z9.string().optional() }) }); var listModelsResponseSchema = z9.object({ text: z9.array(z9.object({ provider: z9.string(), configured: z9.boolean(), models: z9.array(openRouterModelSchema) })), image: z9.array(z9.object({ provider: z9.string(), configured: z9.boolean(), models: z9.array(openRouterModelSchema) })) }); var createAIConfigurationRequestSchema = aiConfigurationSchema.omit({ id: true }); var updateAIConfigurationRequestSchema = z9.object({ systemPrompt: z9.string().nullable() }); var listAIUsageResponseSchema = z9.object({ records: z9.array(aiUsageRecordSchema), total: z9.number() }); var getAIUsageRequestSchema = z9.object({ startDate: z9.string().datetime().optional(), endDate: z9.string().datetime().optional(), limit: z9.string().regex(/^\d+$/).default("50"), offset: z9.string().regex(/^\d+$/).default("0") }); var getAIUsageSummaryRequestSchema = z9.object({ configId: z9.string().uuid().optional(), startDate: z9.string().datetime().optional(), endDate: z9.string().datetime().optional() }); // node_modules/@insforge/shared-schemas/dist/logs.schema.js import { z as z10 } from "zod"; var auditLogSchema = z10.object({ id: z10.string(), actor: z10.string(), action: z10.string(), module: z10.string(), details: z10.record(z10.unknown()).nullable(), ipAddress: z10.string().nullable(), createdAt: z10.string(), updatedAt: z10.string() }); // node_modules/@insforge/shared-schemas/dist/logs-api.schema.js import { z as z11 } from "zod"; var getAuditLogsRequestSchema = z11.object({ limit: z11.number().default(100), offset: z11.number().default(0), actor: z11.string().optional(), action: z11.string().optional(), module: z11.string().optional(), startDate: z11.string().optional(), endDate: z11.string().optional() }); var getAuditLogsResponseSchema = z11.object({ data: z11.array(auditLogSchema), pagination: z11.object({ limit: z11.number(), offset: z11.number(), total: z11.number() }) }); var getAuditLogStatsRequestSchema = z11.object({ days: z11.number().default(7) }); var getAuditLogStatsResponseSchema = z11.object({ totalLogs: z11.number(), uniqueActors: z11.number(), uniqueModules: z11.number(), actionsByModule: z11.record(z11.number()), recentActivity: z11.array(auditLogSchema) }); var clearAuditLogsRequestSchema = z11.object({ daysToKeep: z11.number().default(90) }); var clearAuditLogsResponseSchema = z11.object({ message: z11.string(), deleted: z11.number() }); // src/index.ts program.option("--api_key <value>", "API Key"); program.parse(process.argv); var options = program.opts(); var { api_key } = options; var GLOBAL_API_KEY = api_key || process.env.API_KEY || ""; var server = new McpServer({ name: "insforge-mcp", version: "1.0.0" }); var API_BASE_URL = process.env.API_BASE_URL || "http://localhost:7130"; var usageTracker = new UsageTracker(API_BASE_URL, GLOBAL_API_KEY); async function trackToolUsage(toolName, success = true) { if (GLOBAL_API_KEY) { await usageTracker.trackUsage(toolName, success); } } function withUsageTracking(toolName, handler) { return async (...args) => { try { const result = await handler(...args); await trackToolUsage(toolName, true); return result; } catch (error) { await trackToolUsage(toolName, false); throw error; } }; } var getApiKey = (toolApiKey) => { if (GLOBAL_API_KEY) { return GLOBAL_API_KEY; } if (toolApiKey) { return toolApiKey; } throw new Error( "API key is required. Either pass --api_key as command line argument or provide api_key in tool calls." ); }; var fetchDocumentation = async (docType) => { try { const response = await fetch2(`${API_BASE_URL}/api/docs/${docType}`, { method: "GET", headers: { "Content-Type": "application/json" } }); const result = await handleApiResponse(response); if (result && typeof result === "object" && "content" in result) { let content = result.content; content = content.replace(/http:\/\/localhost:7130/g, API_BASE_URL); return content; } throw new Error("Invalid response format from documentation endpoint"); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; throw new Error(`Unable to retrieve ${docType} documentation: ${errMsg}`); } }; var fetchInsforgeInstructionsContext = async () => { try { return await fetchDocumentation("instructions"); } catch (error) { console.error("Failed to fetch insforge-instructions.md:", error); return null; } }; var addBackgroundContext = async (response) => { const context = await fetchInsforgeInstructionsContext(); if (context && response.content && Array.isArray(response.content)) { response.content.push({ type: "text", text: ` --- \u{1F527} INSFORGE DEVELOPMENT RULES (Auto-loaded): ${context}` }); } return response; }; server.tool( "get-instructions", "Instruction Essential backend setup tool. <critical>MANDATORY: You MUST use this tool FIRST before attempting any backend operations. Contains required API endpoints, authentication details, and setup instructions.</critical>", {}, withUsageTracking("get-instructions", async () => { try { const content = await fetchDocumentation("instructions"); const response = { content: [ { type: "text", text: content } ] }; return await addBackgroundContext(response); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; const errorResponse = { content: [{ type: "text", text: `Error: ${errMsg}` }] }; return await addBackgroundContext(errorResponse); } }) ); server.tool( "get-api-key", "Retrieves the API key for the Insforge OSS backend. This is used to authenticate all requests to the backend.", {}, async () => { try { return await addBackgroundContext({ content: [{ type: "text", text: `API key: ${getApiKey()}` }] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [{ type: "text", text: `Error: ${errMsg}` }] }); } } ); server.tool( "get-table-schema", "Returns the schema of a specific table", { apiKey: z12.string().optional().describe("API key for authentication (optional if provided via --api_key)"), tableName: z12.string().describe("Name of the table") }, withUsageTracking("get-table-schema", async ({ apiKey, tableName }) => { try { const actualApiKey = getApiKey(apiKey); const response = await fetch2(`${API_BASE_URL}/api/metadata/${tableName}`, { method: "GET", headers: { "x-api-key": actualApiKey } }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage("Schema retrieved", result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error getting table schema: ${errMsg}` } ], isError: true }); } }) ); server.tool( "get-backend-metadata", "Index all backend metadata", { apiKey: z12.string().optional().describe("API key for authentication (optional if provided via --api_key)") }, withUsageTracking("get-backend-metadata", async ({ apiKey }) => { try { const actualApiKey = getApiKey(apiKey); const response = await fetch2(`${API_BASE_URL}/api/metadata?mcp=true`, { method: "GET", headers: { "x-api-key": actualApiKey } }); const metadata = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: `Backend metadata: ${JSON.stringify(metadata, null, 2)}` } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error retrieving backend metadata: ${errMsg}` } ], isError: true }); } }) ); server.tool( "run-raw-sql", "Execute raw SQL query with optional parameters. Admin access required. Use with caution as it can modify data directly.", { apiKey: z12.string().optional().describe("API key for authentication (optional if provided via --api_key)"), ...rawSQLRequestSchema.shape }, withUsageTracking("run-raw-sql", async ({ apiKey, query, params }) => { try { const actualApiKey = getApiKey(apiKey); const requestBody = { query, params: params || [] }; const response = await fetch2(`${API_BASE_URL}/api/database/advance/rawsql`, { method: "POST", headers: { "x-api-key": actualApiKey, "Content-Type": "application/json" }, body: JSON.stringify(requestBody) }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage("SQL query executed", result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error executing SQL query: ${errMsg}` } ], isError: true }); } }) ); server.tool( "bulk-upsert", "Bulk insert or update data from CSV or JSON file. Supports upsert operations with a unique key.", { apiKey: z12.string().optional().describe("API key for authentication (optional if provided via --api_key)"), ...bulkUpsertRequestSchema.shape, filePath: z12.string().describe("Path to CSV or JSON file containing data to import") }, withUsageTracking("bulk-upsert", async ({ apiKey, table, filePath, upsertKey }) => { try { const actualApiKey = getApiKey(apiKey); const fileBuffer = await fs.readFile(filePath); const fileName = filePath.split("/").pop() || "data.csv"; const formData = new FormData(); formData.append("file", fileBuffer, fileName); formData.append("table", table); if (upsertKey) { formData.append("upsertKey", upsertKey); } const response = await fetch2(`${API_BASE_URL}/api/database/advance/bulk-upsert`, { method: "POST", headers: { "x-api-key": actualApiKey, ...formData.getHeaders() }, body: formData }); const result = await handleApiResponse(response); const message = result.success ? `Successfully processed ${result.rowsAffected} of ${result.totalRecords} records into table "${result.table}"` : result.message || "Bulk upsert operation completed"; return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage("Bulk upsert completed", { message, table: result.table, rowsAffected: result.rowsAffected, totalRecords: result.totalRecords, errors: result.errors }) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error performing bulk upsert: ${errMsg}` } ], isError: true }); } }) ); server.tool( "create-bucket", "Create new storage bucket", { apiKey: z12.string().optional().describe("API key for authentication (optional if provided via --api_key)"), ...createBucketRequestSchema.shape }, withUsageTracking("create-bucket", async ({ apiKey, bucketName, isPublic }) => { try { const actualApiKey = getApiKey(apiKey); const response = await fetch2(`${API_BASE_URL}/api/storage/buckets`, { method: "POST", headers: { "x-api-key": actualApiKey, "Content-Type": "application/json" }, body: JSON.stringify({ bucketName, isPublic }) }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage("Bucket created", result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error creating bucket: ${errMsg}` } ], isError: true }); } }) ); server.tool( "list-buckets", "Lists all storage buckets", {}, withUsageTracking("list-buckets", async () => { try { const response = await fetch2(`${API_BASE_URL}/api/storage/buckets`, { method: "GET", headers: { "x-api-key": getApiKey() // Still need API key for protected endpoint } }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage("Buckets retrieved", result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error listing buckets: ${errMsg}` } ], isError: true }); } }) ); server.tool( "delete-bucket", "Deletes a storage bucket", { apiKey: z12.string().optional().describe("API key for authentication (optional if provided via --api_key)"), bucketName: z12.string().describe("Name of the bucket to delete") }, withUsageTracking("delete-bucket", async ({ apiKey, bucketName }) => { try { const actualApiKey = getApiKey(apiKey); const response = await fetch2(`${API_BASE_URL}/api/storage/buckets/${bucketName}`, { method: "DELETE", headers: { "x-api-key": actualApiKey } }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage("Bucket deleted", result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error deleting bucket: ${errMsg}` } ], isError: true }); } }) ); server.tool( "create-function", "Create a new edge function that runs in Deno runtime. The code must be written to a file first for version control", { slug: z12.string().regex(/^[a-zA-Z0-9_-]+$/, "Slug must be alphanumeric with hyphens or underscores only").describe( 'URL-friendly identifier (alphanumeric, hyphens, underscores only). Example: "my-calculator"' ), name: z12.string().describe('Function display name. Example: "Calculator Function"'), codeFile: z12.string().describe( "Path to JavaScript file containing the function code. Must export: module.exports = async function(request) { return new Response(...) }" ), description: z12.string().optional().describe("Description of what the function does"), active: z12.boolean().optional().describe("Set to true to deploy immediately, false for draft mode") }, withUsageTracking("create-function", async (args) => { try { let code; try { code = await fs.readFile(args.codeFile, "utf-8"); } catch (fileError) { throw new Error( `Failed to read code file '${args.codeFile}': ${fileError instanceof Error ? fileError.message : "Unknown error"}` ); } const response = await fetch2(`${API_BASE_URL}/api/functions`, { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": getApiKey() }, body: JSON.stringify({ slug: args.slug, name: args.name, code, description: args.description || "", status: args.active ? "active" : "draft" }) }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage( `Edge function '${args.slug}' created successfully from ${args.codeFile}`, result ) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error creating function: ${errMsg}` } ], isError: true }); } }) ); server.tool( "get-function", "Get details of a specific edge function including its code", { slug: z12.string().describe("The slug identifier of the function") }, withUsageTracking("get-function", async (args) => { try { const response = await fetch2(`${API_BASE_URL}/api/functions/${args.slug}`, { method: "GET", headers: { "x-api-key": getApiKey() } }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' details`, result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error getting function: ${errMsg}` } ], isError: true }); } }) ); server.tool( "update-function", "Update an existing edge function code or metadata", { slug: z12.string().describe("The slug identifier of the function to update"), name: z12.string().optional().describe("New display name"), codeFile: z12.string().optional().describe( "Path to JavaScript file containing the new function code. Must export: module.exports = async function(request) { return new Response(...) }" ), description: z12.string().optional().describe("New description"), status: z12.string().optional().describe('Function status: "draft" (not deployed), "active" (deployed), or "error"') }, withUsageTracking("update-function", async (args) => { try { const updateData = {}; if (args.name) { updateData.name = args.name; } if (args.codeFile) { try { updateData.code = await fs.readFile(args.codeFile, "utf-8"); } catch (fileError) { throw new Error( `Failed to read code file '${args.codeFile}': ${fileError instanceof Error ? fileError.message : "Unknown error"}` ); } } if (args.description !== void 0) { updateData.description = args.description; } if (args.status) { updateData.status = args.status; } const response = await fetch2(`${API_BASE_URL}/api/functions/${args.slug}`, { method: "PUT", headers: { "Content-Type": "application/json", "x-api-key": getApiKey() }, body: JSON.stringify(updateData) }); const result = await handleApiResponse(response); const fileInfo = args.codeFile ? ` from ${args.codeFile}` : ""; return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage( `Edge function '${args.slug}' updated successfully${fileInfo}`, result ) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error updating function: ${errMsg}` } ], isError: true }); } }) ); server.tool( "delete-function", "Delete an edge function permanently", { slug: z12.string().describe("The slug identifier of the function to delete") }, withUsageTracking("delete-function", async (args) => { try { const response = await fetch2(`${API_BASE_URL}/api/functions/${args.slug}`, { method: "DELETE", headers: { "x-api-key": getApiKey() } }); const result = await handleApiResponse(response); return await addBackgroundContext({ content: [ { type: "text", text: formatSuccessMessage(`Edge function '${args.slug}' deleted successfully`, result) } ] }); } catch (error) { const errMsg = error instanceof Error ? error.message : "Unknown error occurred"; return await addBackgroundContext({ content: [ { type: "text", text: `Error deleting function: ${errMsg}` } ], isError: true }); } }) ); async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Insforge MCP server started"); } main().catch(console.error);