@mixio-pro/kalaasetu-mcp 1.2.2 → 2.0.2-beta

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,54 +1,159 @@
1
+ import { z } from "zod";
2
+ import {
3
+ loadFalConfig,
4
+ saveFalConfig,
5
+ FAL_BASE_URL,
6
+ DEFAULT_TIMEOUT,
7
+ } from "./config";
8
+ import { safeToolExecute } from "../../utils/tool-wrapper";
9
+
1
10
  /**
2
- * Models module for fal.ai MCP server.
3
- * Provides tools for retrieving information about configured fal.ai models.
11
+ * Extract simplified input schema from FAL OpenAPI response.
12
+ * Returns only the properties object from the input schema.
4
13
  */
14
+ function extractInputSchema(openApiSchema: any): Record<string, any> | null {
15
+ try {
16
+ const schemas = openApiSchema?.components?.schemas;
17
+ if (!schemas) return null;
5
18
 
6
- import { z } from "zod";
7
- import { loadFalConfig, FAL_BASE_URL, DEFAULT_TIMEOUT } from "./config";
8
- import { safeToolExecute } from "../../utils/tool-wrapper";
19
+ // Find the input schema - usually named like "Ltx2ImageToVideoInput" or similar
20
+ const inputSchemaKey = Object.keys(schemas).find(
21
+ (key) =>
22
+ key.toLowerCase().includes("input") &&
23
+ !key.toLowerCase().includes("output")
24
+ );
25
+
26
+ if (!inputSchemaKey) return null;
27
+
28
+ const inputSchema = schemas[inputSchemaKey];
29
+ if (!inputSchema?.properties) return null;
30
+
31
+ // Extract simplified properties
32
+ const simplified: Record<string, any> = {};
33
+ for (const [propName, propDef] of Object.entries(
34
+ inputSchema.properties as Record<string, any>
35
+ )) {
36
+ simplified[propName] = {
37
+ type: propDef.type,
38
+ description: propDef.description,
39
+ ...(propDef.enum && { enum: propDef.enum }),
40
+ ...(propDef.default !== undefined && { default: propDef.default }),
41
+ ...(propDef.examples && { example: propDef.examples[0] }),
42
+ };
43
+ }
44
+
45
+ return simplified;
46
+ } catch (e) {
47
+ return null;
48
+ }
49
+ }
9
50
 
10
51
  /**
11
52
  * Tool to list available generation presets and their intents.
53
+ * Dynamically fetches and caches input schemas from FAL API.
12
54
  */
13
55
  export const falListPresets = {
14
56
  name: "fal_list_presets",
15
57
  description:
16
58
  "The entry point for discovering fal.ai capabilities on this server. " +
17
59
  "Lists all available generation presets, including their high-level 'intent' (e.g., 'Generate cinematic video'), " +
18
- "and the types of input/output they support. Call this first when you need to perform an AI generation task.",
19
- parameters: z.object({}),
20
- timeoutMs: 30000,
21
- execute: async () => {
60
+ "input/output types, and INPUT SCHEMA with parameter details. Call this first when you need to perform an AI generation task.",
61
+ parameters: z.object({
62
+ refresh_schemas: z
63
+ .boolean()
64
+ .optional()
65
+ .describe("If true, re-fetch schemas from FAL API even if cached."),
66
+ }),
67
+ timeoutMs: 60000, // Allow more time for schema fetching
68
+ execute: async (args: { refresh_schemas?: boolean }) => {
22
69
  return safeToolExecute(async () => {
23
70
  const config = loadFalConfig();
71
+ let updated = false;
72
+
73
+ // Fetch schemas for presets that don't have them (or if refresh requested)
74
+ for (const preset of config.presets) {
75
+ const shouldFetch = !preset.input_schema || args.refresh_schemas;
76
+ console.log(
77
+ `[fal_list_presets] ${
78
+ preset.presetName
79
+ }: shouldFetch=${shouldFetch}, hasSchema=${!!preset.input_schema}, refresh=${
80
+ args.refresh_schemas
81
+ }`
82
+ );
83
+
84
+ if (shouldFetch) {
85
+ try {
86
+ const url = `https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=${preset.modelId}`;
87
+ console.log(`[fal_list_presets] Fetching schema from: ${url}`);
88
+ const response = await fetch(url, {
89
+ method: "GET",
90
+ signal: AbortSignal.timeout(10000),
91
+ });
92
+
93
+ if (response.ok) {
94
+ const openApiSchema = await response.json();
95
+ const simplified = extractInputSchema(openApiSchema);
96
+ console.log(
97
+ `[fal_list_presets] Extracted schema for ${preset.presetName}:`,
98
+ simplified ? Object.keys(simplified) : null
99
+ );
100
+
101
+ if (simplified) {
102
+ preset.input_schema = simplified;
103
+ updated = true;
104
+ }
105
+ } else {
106
+ console.log(
107
+ `[fal_list_presets] Fetch failed: ${response.status}`
108
+ );
109
+ }
110
+ } catch (e: any) {
111
+ console.log(
112
+ `[fal_list_presets] Error fetching schema for ${preset.presetName}:`,
113
+ e.message
114
+ );
115
+ }
116
+ }
117
+ }
118
+
119
+ // Save updated config if schemas were fetched
120
+ if (updated) {
121
+ console.log(`[fal_list_presets] Saving updated config...`);
122
+ const saved = saveFalConfig(config);
123
+ console.log(`[fal_list_presets] Config saved: ${saved}`);
124
+ }
125
+
126
+ // Return enriched preset list
24
127
  const summary = config.presets.map((p) => ({
25
128
  presetName: p.presetName,
26
129
  intent: p.intent,
27
130
  inputType: p.inputType,
28
131
  outputType: p.outputType,
29
132
  description: p.description,
133
+ inputSchema: p.input_schema,
30
134
  }));
135
+
31
136
  return JSON.stringify(summary, null, 2);
32
137
  }, "fal_list_presets");
33
138
  },
34
139
  };
35
140
 
36
141
  /**
37
- * Tool to get full details for a specific preset, including default parameters.
142
+ * Tool to get full details for a specific preset, including default parameters
143
+ * and real-time model metadata from fal.ai.
38
144
  */
39
145
  export const falGetPresetDetails = {
40
146
  name: "fal_get_preset_details",
41
147
  description:
42
148
  "Retrieve full details for a specific generation preset. " +
43
- "Use this to see the 'modelId' being used and, most importantly, the 'defaultParams'. " +
44
- "The default parameters shown here can be overridden in the 'parameters' argument of 'fal_generate'. " +
149
+ "This tool fetches both the local preset configuration (like 'defaultParams') " +
150
+ "and the live model metadata (schema, benchmarks, etc.) from fal.ai. " +
151
+ "Use this to understand the full capabilities and constraints of a model. " +
45
152
  "ONLY USE WHEN WORKING WITH FAL MODELS/PRESETS.",
46
153
  parameters: z.object({
47
154
  preset_name: z
48
155
  .string()
49
- .describe(
50
- "The name of the preset to inspect (e.g., 'ltx_image_to_video')."
51
- ),
156
+ .describe("The name of the preset to inspect (e.g., 'cinematic_image')."),
52
157
  }),
53
158
  timeoutMs: 30000,
54
159
  execute: async (args: { preset_name: string }) => {
@@ -60,7 +165,43 @@ export const falGetPresetDetails = {
60
165
  if (!preset) {
61
166
  throw new Error(`Preset '${args.preset_name}' not found.`);
62
167
  }
63
- return JSON.stringify(preset, null, 2);
168
+
169
+ // Fetch live model metadata/schema from fal.ai API
170
+ // Based on: https://fal.ai/api/openapi/queue/openapi.json?endpoint_id={model_id}
171
+ let modelMetadata: any = null;
172
+ let schemaSource = "none";
173
+
174
+ try {
175
+ const url = `https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=${preset.modelId}`;
176
+ const schema = await publicRequest(url);
177
+
178
+ // Extract relevant schema parts if possible, or return full schema
179
+ if (schema) {
180
+ modelMetadata = schema;
181
+ schemaSource = "api";
182
+ }
183
+ } catch (e) {
184
+ // console.error(`Failed to fetch schema for ${preset.modelId}:`, e);
185
+ // Fallback to locally defined schema if available
186
+ }
187
+
188
+ // Fallback: If API failed or returned nothing, use manual schema from config
189
+ if (!modelMetadata && preset.input_schema) {
190
+ modelMetadata = preset.input_schema;
191
+ schemaSource = "local_config";
192
+ }
193
+
194
+ return JSON.stringify(
195
+ {
196
+ preset,
197
+ modelMetadata,
198
+ _meta: {
199
+ schemaSource,
200
+ },
201
+ },
202
+ null,
203
+ 2
204
+ );
64
205
  }, "fal_get_preset_details");
65
206
  },
66
207
  };
@@ -82,22 +223,6 @@ async function publicRequest(url: string): Promise<any> {
82
223
  return response.json();
83
224
  }
84
225
 
85
- /**
86
- * Tool to retrieve the current full FAL configuration.
87
- */
88
- export const falGetConfig = {
89
- name: "fal_get_config",
90
- description: "Retrieve the full FAL configuration JSON file content.",
91
- parameters: z.object({}),
92
- timeoutMs: 30000,
93
- execute: async () => {
94
- return safeToolExecute(async () => {
95
- const config = loadFalConfig();
96
- return JSON.stringify(config, null, 2);
97
- }, "fal_get_config");
98
- },
99
- };
100
-
101
226
  /*
102
227
  // ORIGINAL UNRESTRICTED TOOLS - Commented out for reference
103
228
 
@@ -12,6 +12,10 @@ import { PassThrough } from "stream";
12
12
  import { getStorage } from "../storage";
13
13
  import { generateTimestampedFilename } from "../utils/filename";
14
14
  import { safeToolExecute } from "../utils/tool-wrapper";
15
+ import {
16
+ resolveEnhancer,
17
+ listImageEnhancerPresets,
18
+ } from "../utils/prompt-enhancer-presets";
15
19
 
16
20
  const ai = new GoogleGenAI({
17
21
  apiKey: process.env.GEMINI_API_KEY || "",
@@ -232,6 +236,13 @@ export const geminiTextToImage = {
232
236
  .describe(
233
237
  "Optional: local paths or URLs of images to use as visual references for style or composition."
234
238
  ),
239
+ enhancer_preset: z
240
+ .string()
241
+ .optional()
242
+ .describe(
243
+ "Optional: Name of a prompt enhancer preset to apply (e.g., 'cinematic', 'photorealistic', 'anime'). " +
244
+ "Automatically enhances the prompt with professional style modifiers."
245
+ ),
235
246
  }),
236
247
  timeoutMs: 300000,
237
248
  execute: async (args: {
@@ -239,10 +250,20 @@ export const geminiTextToImage = {
239
250
  aspect_ratio?: string;
240
251
  output_path?: string;
241
252
  reference_images?: string[];
253
+ enhancer_preset?: string;
242
254
  }) => {
243
255
  return safeToolExecute(async () => {
244
256
  try {
245
- const contents: any[] = [args.prompt];
257
+ // Apply prompt enhancement if preset specified
258
+ let enhancedPrompt = args.prompt;
259
+ if (args.enhancer_preset) {
260
+ const enhancer = resolveEnhancer(args.enhancer_preset);
261
+ if (enhancer.hasTransformations()) {
262
+ enhancedPrompt = enhancer.enhance(args.prompt);
263
+ }
264
+ }
265
+
266
+ const contents: any[] = [enhancedPrompt];
246
267
 
247
268
  if (args.reference_images && Array.isArray(args.reference_images)) {
248
269
  for (const refPath of args.reference_images) {
@@ -337,6 +358,13 @@ export const geminiEditImage = {
337
358
  .describe(
338
359
  "Optional: additional images to guide the edit (e.g., to reference a specific character or object style)."
339
360
  ),
361
+ enhancer_preset: z
362
+ .string()
363
+ .optional()
364
+ .describe(
365
+ "Optional: Name of a prompt enhancer preset to apply (e.g., 'cinematic', 'photorealistic'). " +
366
+ "Enhances the edit instructions with professional style modifiers."
367
+ ),
340
368
  }),
341
369
  timeoutMs: 300000,
342
370
  execute: async (args: {
@@ -344,11 +372,21 @@ export const geminiEditImage = {
344
372
  prompt: string;
345
373
  output_path?: string;
346
374
  reference_images?: string[];
375
+ enhancer_preset?: string;
347
376
  }) => {
348
377
  return safeToolExecute(async () => {
349
378
  try {
379
+ // Apply prompt enhancement if preset specified
380
+ let enhancedPrompt = args.prompt;
381
+ if (args.enhancer_preset) {
382
+ const enhancer = resolveEnhancer(args.enhancer_preset);
383
+ if (enhancer.hasTransformations()) {
384
+ enhancedPrompt = enhancer.enhance(args.prompt);
385
+ }
386
+ }
387
+
350
388
  const imagePart = await fileToGenerativePart(args.image_path);
351
- const contents: any[] = [args.prompt, imagePart];
389
+ const contents: any[] = [enhancedPrompt, imagePart];
352
390
 
353
391
  if (args.reference_images) {
354
392
  for (const refPath of args.reference_images) {
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Unified status checking tool for FAL and Vertex AI generation operations.
3
+ * This tool allows resuming/checking status of operations that timed out.
4
+ */
5
+
6
+ import { z } from "zod";
7
+ import { safeToolExecute } from "../utils/tool-wrapper";
8
+ import { getGoogleAccessToken } from "../utils/google-auth";
9
+
10
+ const FAL_KEY = process.env.FAL_KEY;
11
+
12
+ /**
13
+ * Check FAL generation status using the status URL
14
+ */
15
+ async function checkFalStatus(statusUrl: string): Promise<any> {
16
+ if (!FAL_KEY) {
17
+ throw new Error("FAL_KEY environment variable not set");
18
+ }
19
+
20
+ const response = await fetch(statusUrl, {
21
+ method: "GET",
22
+ headers: {
23
+ Authorization: `Key ${FAL_KEY}`,
24
+ "Content-Type": "application/json",
25
+ },
26
+ });
27
+
28
+ if (!response.ok) {
29
+ const errorText = await response.text();
30
+ throw new Error(`FAL API error [${response.status}]: ${errorText}`);
31
+ }
32
+
33
+ const statusResult = (await response.json()) as { status?: string };
34
+
35
+ if (statusResult.status === "COMPLETED") {
36
+ // Fetch the actual result
37
+ const responseUrl = statusUrl.replace(/\/status$/, "");
38
+ const resultResponse = await fetch(responseUrl, {
39
+ method: "GET",
40
+ headers: {
41
+ Authorization: `Key ${FAL_KEY}`,
42
+ "Content-Type": "application/json",
43
+ },
44
+ });
45
+
46
+ if (resultResponse.ok) {
47
+ return await resultResponse.json();
48
+ }
49
+ }
50
+
51
+ return statusResult;
52
+ }
53
+
54
+ /**
55
+ * Check Vertex AI operation status
56
+ */
57
+ async function checkVertexStatus(
58
+ operationName: string,
59
+ projectId: string,
60
+ locationId: string
61
+ ): Promise<any> {
62
+ const accessToken = await getGoogleAccessToken();
63
+
64
+ const operationsUrl = `https://${locationId}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${locationId}/publishers/google/models/veo-3.1-generate-preview/operations/${operationName}`;
65
+
66
+ const response = await fetch(operationsUrl, {
67
+ method: "GET",
68
+ headers: {
69
+ Authorization: `Bearer ${accessToken}`,
70
+ "Content-Type": "application/json",
71
+ },
72
+ });
73
+
74
+ if (!response.ok) {
75
+ const errorText = await response.text();
76
+ throw new Error(`Vertex AI API error [${response.status}]: ${errorText}`);
77
+ }
78
+
79
+ return await response.json();
80
+ }
81
+
82
+ export const getGenerationStatus = {
83
+ name: "get_generation_status",
84
+ description:
85
+ "Check the status or retrieve the result of a generation operation that was started by 'fal_generate' or 'generateVideoi2v'. " +
86
+ "Use this when the original generation tool returned an 'IN_PROGRESS' status with a 'resume_id'. " +
87
+ "Pass the resume_id exactly as it was returned. " +
88
+ "For FAL operations, the resume_id is a full URL. " +
89
+ "For Vertex AI operations, the resume_id is an operation name.",
90
+ parameters: z.object({
91
+ resume_id: z
92
+ .string()
93
+ .describe(
94
+ "The resume_id returned by the original generation tool. " +
95
+ "For FAL: This is a full URL (starts with 'https://queue.fal.run/...'). " +
96
+ "For Vertex AI: This is an operation name."
97
+ ),
98
+ source: z
99
+ .enum(["fal", "vertex", "auto"])
100
+ .optional()
101
+ .default("auto")
102
+ .describe(
103
+ "Source of the operation: 'fal' for FAL AI, 'vertex' for Google Vertex AI, or 'auto' to auto-detect based on resume_id format."
104
+ ),
105
+ project_id: z
106
+ .string()
107
+ .optional()
108
+ .default("mixio-pro")
109
+ .describe("GCP Project ID (only needed for Vertex AI operations)."),
110
+ location_id: z
111
+ .string()
112
+ .optional()
113
+ .default("us-central1")
114
+ .describe("GCP region (only needed for Vertex AI operations)."),
115
+ }),
116
+ timeoutMs: 30000, // 30 seconds for status check
117
+ execute: async (args: {
118
+ resume_id: string;
119
+ source?: "fal" | "vertex" | "auto";
120
+ project_id?: string;
121
+ location_id?: string;
122
+ }) => {
123
+ return safeToolExecute(async () => {
124
+ const {
125
+ resume_id,
126
+ source = "auto",
127
+ project_id = "mixio-pro",
128
+ location_id = "us-central1",
129
+ } = args;
130
+
131
+ // Auto-detect source based on resume_id format
132
+ let detectedSource = source;
133
+ if (source === "auto") {
134
+ if (
135
+ resume_id.startsWith("https://queue.fal.run") ||
136
+ resume_id.startsWith("https://fal.run")
137
+ ) {
138
+ detectedSource = "fal";
139
+ } else {
140
+ detectedSource = "vertex";
141
+ }
142
+ }
143
+
144
+ let result: any;
145
+
146
+ if (detectedSource === "fal") {
147
+ result = await checkFalStatus(resume_id);
148
+ } else {
149
+ result = await checkVertexStatus(resume_id, project_id, location_id);
150
+ }
151
+
152
+ // Normalize the response
153
+ const status =
154
+ result.status || (result.done ? "COMPLETED" : "IN_PROGRESS");
155
+
156
+ return JSON.stringify(
157
+ {
158
+ source: detectedSource,
159
+ status,
160
+ resume_id,
161
+ result,
162
+ message:
163
+ status === "COMPLETED"
164
+ ? "Generation completed! The result is included in the 'result' field."
165
+ : status === "FAILED"
166
+ ? "Generation failed. Check the 'result' field for error details."
167
+ : "Generation is still in progress. Call this tool again with the same resume_id to check later.",
168
+ },
169
+ null,
170
+ 2
171
+ );
172
+ }, "get_generation_status");
173
+ },
174
+ };