@mixio-pro/kalaasetu-mcp 1.1.4 → 1.2.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mixio-pro/kalaasetu-mcp",
3
- "version": "1.1.4",
3
+ "version": "1.2.1",
4
4
  "description": "A powerful Model Context Protocol server providing AI tools for content generation and analysis",
5
5
  "type": "module",
6
6
  "module": "src/index.ts",
package/src/index.ts CHANGED
@@ -4,10 +4,10 @@ import pkg from "../package.json";
4
4
  import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
5
5
  import { imageToVideo } from "./tools/image-to-video";
6
6
  import {
7
- falListModels,
8
- falSearchModels,
9
- falGetSchema,
10
- falGenerateContent,
7
+ falListPresets,
8
+ falGetPresetDetails,
9
+ falGetConfig,
10
+ falGenerate,
11
11
  falGetResult,
12
12
  falGetStatus,
13
13
  falCancelRequest,
@@ -41,14 +41,14 @@ server.addTool(imageToVideo);
41
41
  // server.addTool(perplexityVideos);
42
42
 
43
43
  // Fal AI Tools
44
- // server.addTool(falListModels);
45
- // server.addTool(falSearchModels);
46
- // server.addTool(falGetSchema);
47
- // server.addTool(falGenerateContent);
48
- // server.addTool(falGetResult);
49
- // server.addTool(falGetStatus);
50
- // server.addTool(falCancelRequest);
51
- // server.addTool(falUploadFile);
44
+ server.addTool(falGetConfig);
45
+ server.addTool(falListPresets);
46
+ server.addTool(falGetPresetDetails);
47
+ server.addTool(falGenerate);
48
+ server.addTool(falGetResult);
49
+ server.addTool(falGetStatus);
50
+ server.addTool(falCancelRequest);
51
+ server.addTool(falUploadFile);
52
52
 
53
53
  server.start({
54
54
  transportType: "stdio",
@@ -1,7 +1,8 @@
1
1
  /**
2
- * Configuration module for the fal.ai MCP server.
3
2
  * Provides centralized configuration settings for API endpoints, timeouts, and server metadata.
4
3
  */
4
+ import * as fs from "fs";
5
+ import * as path from "path";
5
6
 
6
7
  // API URLs
7
8
  export const FAL_BASE_URL = "https://fal.ai/api";
@@ -32,3 +33,96 @@ export function getApiKey(): string {
32
33
  }
33
34
  return apiKey;
34
35
  }
36
+
37
+ /**
38
+ * Interface for a FAL preset configuration.
39
+ */
40
+ export interface FalPresetConfig {
41
+ presetName: string;
42
+ intent: string;
43
+ modelId: string;
44
+ description?: string;
45
+ defaultParams?: Record<string, any>;
46
+ inputType?: "text" | "image" | "video" | "audio" | "custom";
47
+ outputType?: "image" | "video" | "audio" | "text" | "custom";
48
+ }
49
+
50
+ /**
51
+ * Interface for the FAL configuration file.
52
+ */
53
+ export interface FalConfig {
54
+ presets: FalPresetConfig[];
55
+ }
56
+
57
+ /**
58
+ * Default fallback presets in case no config file is provided.
59
+ */
60
+ export const DEFAULT_PRESETS: FalPresetConfig[] = [
61
+ {
62
+ presetName: "cinematic_image",
63
+ intent: "Generate high-quality cinematic images from text prompts",
64
+ modelId: "fal-ai/flux/pro/v1.1",
65
+ description: "FLUX.1 [pro] v1.1 for state-of-the-art image generation",
66
+ defaultParams: {
67
+ image_size: "landscape_4_3",
68
+ },
69
+ inputType: "text",
70
+ outputType: "image",
71
+ },
72
+ {
73
+ presetName: "cinematic_video",
74
+ intent: "Generate realistic cinematic video from text or image",
75
+ modelId: "fal-ai/luma-dream-machine",
76
+ description: "Luma Dream Machine for high-quality video generation",
77
+ inputType: "custom",
78
+ outputType: "video",
79
+ },
80
+ {
81
+ presetName: "creative_image",
82
+ intent: "Generate creative or artistic images with high flexibility",
83
+ modelId: "fal-ai/stable-diffusion-v35-large",
84
+ description: "Stable Diffusion 3.5 Large for diverse image styles",
85
+ inputType: "text",
86
+ outputType: "image",
87
+ },
88
+ ];
89
+
90
+ /**
91
+ * Load the FAL configuration from a JSON file.
92
+ * Defaults to DEFAULT_PRESETS if no path is provided or if the file cannot be read.
93
+ */
94
+ export function loadFalConfig(): FalConfig {
95
+ const configPath = process.env.FAL_CONFIG_JSON_PATH;
96
+
97
+ if (!configPath) {
98
+ console.error(
99
+ "FAL_CONFIG_JSON_PATH not set. Using internal default presets."
100
+ );
101
+ return { presets: DEFAULT_PRESETS };
102
+ }
103
+
104
+ try {
105
+ const absolutePath = path.isAbsolute(configPath)
106
+ ? configPath
107
+ : path.join(process.cwd(), configPath);
108
+
109
+ if (!fs.existsSync(absolutePath)) {
110
+ console.error(
111
+ `FAL config file not found at ${absolutePath}. Using default presets.`
112
+ );
113
+ return { presets: DEFAULT_PRESETS };
114
+ }
115
+
116
+ const fileContent = fs.readFileSync(absolutePath, "utf-8");
117
+ const config = JSON.parse(fileContent) as FalConfig;
118
+
119
+ if (!config.presets || !Array.isArray(config.presets)) {
120
+ throw new Error("Invalid FAL config: 'presets' must be an array.");
121
+ }
122
+
123
+ return config;
124
+ } catch (error: any) {
125
+ console.error(`Error loading FAL config: ${error.message}`);
126
+ return { presets: DEFAULT_PRESETS };
127
+ }
128
+ }
@@ -10,6 +10,8 @@ import {
10
10
  FAL_DIRECT_URL,
11
11
  AUTHENTICATED_TIMEOUT,
12
12
  getApiKey,
13
+ loadFalConfig,
14
+ type FalPresetConfig,
13
15
  } from "./config";
14
16
 
15
17
  /**
@@ -57,17 +59,20 @@ function sanitizeParameters(
57
59
  }
58
60
 
59
61
  /**
60
- * Generate content using a fal.ai model.
62
+ * Unified generation tool using presets defined in configuration.
61
63
  */
62
- export const falGenerateContent = {
63
- name: "fal_generate_content",
64
+ export const falGenerate = {
65
+ name: "fal_generate",
64
66
  description:
65
- "Generate content using any fal.ai model. Supports both direct execution and queued execution for long-running tasks.",
67
+ "Generate content using a named preset and optional parameters. Use fal_list_presets to discover available intents and preset names.",
66
68
  parameters: z.object({
67
- model: z.string().describe('The model ID to use (e.g., "fal-ai/flux/dev")'),
69
+ preset_name: z
70
+ .string()
71
+ .describe("The name of the preset to use (e.g., 'cinematic_image')"),
68
72
  parameters: z
69
73
  .record(z.string(), z.any())
70
- .describe("Model-specific parameters as a dictionary"),
74
+ .optional()
75
+ .describe("Optional model-specific parameters to override defaults"),
71
76
  queue: z
72
77
  .boolean()
73
78
  .optional()
@@ -76,25 +81,43 @@ export const falGenerateContent = {
76
81
  "Whether to use the queuing system for long-running tasks. Default: false"
77
82
  ),
78
83
  }),
84
+ timeoutMs: 300000,
79
85
  execute: async (args: {
80
- model: string;
81
- parameters: Record<string, any>;
86
+ preset_name: string;
87
+ parameters?: Record<string, any>;
82
88
  queue?: boolean;
83
89
  }) => {
84
90
  return safeToolExecute(async () => {
85
- const sanitizedParams = sanitizeParameters(args.parameters);
91
+ const config = loadFalConfig();
92
+ const preset = config.presets.find(
93
+ (p) => p.presetName === args.preset_name
94
+ );
95
+
96
+ if (!preset) {
97
+ throw new Error(
98
+ `Preset '${args.preset_name}' not found. Use fal_list_presets to see available options.`
99
+ );
100
+ }
101
+
102
+ // Merge defaults from config with runtime overrides
103
+ const mergedParams = {
104
+ ...(preset.defaultParams || {}),
105
+ ...(args.parameters || {}),
106
+ };
107
+
108
+ const sanitizedParams = sanitizeParameters(mergedParams);
86
109
 
87
110
  const baseUrl = args.queue ? FAL_QUEUE_URL : FAL_DIRECT_URL;
88
- const url = `${baseUrl}/${args.model}`;
111
+ const url = `${baseUrl}/${preset.modelId}`;
89
112
 
90
- console.error(`[fal_generate] Submitting request to ${url}...`);
113
+ console.error(
114
+ `[fal_generate] Using preset '${args.preset_name}' on model ${preset.modelId}...`
115
+ );
91
116
 
92
117
  const result = await authenticatedRequest(url, "POST", sanitizedParams);
93
118
 
94
- console.error(`[fal_generate] Request completed successfully`);
95
-
96
119
  return JSON.stringify(result);
97
- }, "fal_generate_content");
120
+ }, "fal_generate");
98
121
  },
99
122
  };
100
123
 
@@ -107,6 +130,7 @@ export const falGetResult = {
107
130
  parameters: z.object({
108
131
  url: z.string().describe("The response_url from a queued request"),
109
132
  }),
133
+ timeoutMs: 300000,
110
134
  execute: async (args: { url: string }) => {
111
135
  const result = await authenticatedRequest(args.url, "GET");
112
136
  return JSON.stringify(result);
@@ -122,6 +146,7 @@ export const falGetStatus = {
122
146
  parameters: z.object({
123
147
  url: z.string().describe("The status_url from a queued request"),
124
148
  }),
149
+ timeoutMs: 300000,
125
150
  execute: async (args: { url: string }) => {
126
151
  const result = await authenticatedRequest(args.url, "GET");
127
152
  return JSON.stringify(result);
@@ -137,6 +162,7 @@ export const falCancelRequest = {
137
162
  parameters: z.object({
138
163
  url: z.string().describe("The cancel_url from a queued request"),
139
164
  }),
165
+ timeoutMs: 300000,
140
166
  execute: async (args: { url: string }) => {
141
167
  return safeToolExecute(async () => {
142
168
  const result = await authenticatedRequest(args.url, "PUT");
@@ -3,9 +3,9 @@
3
3
  * Export all fal.ai tools for registration with the MCP server.
4
4
  */
5
5
 
6
- export { falListModels, falSearchModels, falGetSchema } from "./models";
6
+ export { falListPresets, falGetPresetDetails, falGetConfig } from "./models";
7
7
  export {
8
- falGenerateContent,
8
+ falGenerate,
9
9
  falGetResult,
10
10
  falGetStatus,
11
11
  falCancelRequest,
@@ -1,13 +1,63 @@
1
1
  /**
2
2
  * Models module for fal.ai MCP server.
3
- * Provides tools for listing, searching, and retrieving schemas for fal.ai models.
3
+ * Provides tools for retrieving information about configured fal.ai models.
4
4
  */
5
5
 
6
6
  import { z } from "zod";
7
- import { FAL_BASE_URL, DEFAULT_TIMEOUT } from "./config";
7
+ import { loadFalConfig, FAL_BASE_URL, DEFAULT_TIMEOUT } from "./config";
8
+ import { safeToolExecute } from "../../utils/tool-wrapper";
8
9
 
9
10
  /**
10
- * Make a non-authenticated request to fal.ai API.
11
+ * Tool to list available generation presets and their intents.
12
+ */
13
+ export const falListPresets = {
14
+ name: "fal_list_presets",
15
+ description:
16
+ "List all available generation presets, including their intents, input types, and output types. Use this to find the right preset for a task.",
17
+ parameters: z.object({}),
18
+ timeoutMs: 30000,
19
+ execute: async () => {
20
+ return safeToolExecute(async () => {
21
+ const config = loadFalConfig();
22
+ const summary = config.presets.map((p) => ({
23
+ presetName: p.presetName,
24
+ intent: p.intent,
25
+ inputType: p.inputType,
26
+ outputType: p.outputType,
27
+ description: p.description,
28
+ }));
29
+ return JSON.stringify(summary, null, 2);
30
+ }, "fal_list_presets");
31
+ },
32
+ };
33
+
34
+ /**
35
+ * Tool to get full details for a specific preset, including default parameters.
36
+ */
37
+ export const falGetPresetDetails = {
38
+ name: "fal_get_preset_details",
39
+ description:
40
+ "Get full details for a specific generation preset, including its model ID and default parameters.",
41
+ parameters: z.object({
42
+ preset_name: z.string().describe("The name of the preset to inspect"),
43
+ }),
44
+ timeoutMs: 30000,
45
+ execute: async (args: { preset_name: string }) => {
46
+ return safeToolExecute(async () => {
47
+ const config = loadFalConfig();
48
+ const preset = config.presets.find(
49
+ (p) => p.presetName === args.preset_name
50
+ );
51
+ if (!preset) {
52
+ throw new Error(`Preset '${args.preset_name}' not found.`);
53
+ }
54
+ return JSON.stringify(preset, null, 2);
55
+ }, "fal_get_preset_details");
56
+ },
57
+ };
58
+
59
+ /**
60
+ * Helper for making public API requests to fal.ai
11
61
  */
12
62
  async function publicRequest(url: string): Promise<any> {
13
63
  const response = await fetch(url, {
@@ -24,8 +74,24 @@ async function publicRequest(url: string): Promise<any> {
24
74
  }
25
75
 
26
76
  /**
27
- * List available models on fal.ai with optional pagination.
77
+ * Tool to retrieve the current full FAL configuration.
28
78
  */
79
+ export const falGetConfig = {
80
+ name: "fal_get_config",
81
+ description: "Retrieve the full FAL configuration JSON file content.",
82
+ parameters: z.object({}),
83
+ timeoutMs: 30000,
84
+ execute: async () => {
85
+ return safeToolExecute(async () => {
86
+ const config = loadFalConfig();
87
+ return JSON.stringify(config, null, 2);
88
+ }, "fal_get_config");
89
+ },
90
+ };
91
+
92
+ /*
93
+ // ORIGINAL UNRESTRICTED TOOLS - Commented out for reference
94
+
29
95
  export const falListModels = {
30
96
  name: "fal_list_models",
31
97
  description:
@@ -40,6 +106,7 @@ export const falListModels = {
40
106
  .optional()
41
107
  .describe("The total number of models to retrieve per page"),
42
108
  }),
109
+ timeoutMs: 300000,
43
110
  execute: async (args: { page?: number; total?: number }) => {
44
111
  let url = `${FAL_BASE_URL}/models`;
45
112
 
@@ -56,15 +123,13 @@ export const falListModels = {
56
123
  },
57
124
  };
58
125
 
59
- /**
60
- * Search for models on fal.ai based on keywords.
61
- */
62
126
  export const falSearchModels = {
63
127
  name: "fal_search_models",
64
128
  description: "Search for models on fal.ai based on keywords.",
65
129
  parameters: z.object({
66
130
  keywords: z.string().describe("The search terms to find models"),
67
131
  }),
132
+ timeoutMs: 300000,
68
133
  execute: async (args: { keywords: string }) => {
69
134
  const url = `${FAL_BASE_URL}/models?keywords=${encodeURIComponent(
70
135
  args.keywords
@@ -73,23 +138,4 @@ export const falSearchModels = {
73
138
  return JSON.stringify(result);
74
139
  },
75
140
  };
76
-
77
- /**
78
- * Get the OpenAPI schema for a specific model.
79
- */
80
- export const falGetSchema = {
81
- name: "fal_get_schema",
82
- description: "Get the OpenAPI schema for a specific fal.ai model.",
83
- parameters: z.object({
84
- model_id: z
85
- .string()
86
- .describe('The ID of the model (e.g., "fal-ai/flux/dev")'),
87
- }),
88
- execute: async (args: { model_id: string }) => {
89
- const url = `${FAL_BASE_URL}/openapi/queue/openapi.json?endpoint_id=${encodeURIComponent(
90
- args.model_id
91
- )}`;
92
- const result = await publicRequest(url);
93
- return JSON.stringify(result);
94
- },
95
- };
141
+ */
@@ -42,6 +42,7 @@ export const falUploadFile = {
42
42
  parameters: z.object({
43
43
  path: z.string().describe("The absolute path to the file to upload"),
44
44
  }),
45
+ timeoutMs: 300000,
45
46
  execute: async (args: { path: string }) => {
46
47
  return safeToolExecute(async () => {
47
48
  // Validate file exists
@@ -220,6 +220,7 @@ export const geminiTextToImage = {
220
220
  .optional()
221
221
  .describe("Optional reference image file paths to guide generation"),
222
222
  }),
223
+ timeoutMs: 300000,
223
224
  execute: async (args: {
224
225
  prompt: string;
225
226
  aspect_ratio?: string;
@@ -309,6 +310,7 @@ export const geminiEditImage = {
309
310
  .optional()
310
311
  .describe("Additional image paths for reference"),
311
312
  }),
313
+ timeoutMs: 300000,
312
314
  execute: async (args: {
313
315
  image_path: string;
314
316
  prompt: string;
@@ -383,6 +385,7 @@ export const geminiAnalyzeImages = {
383
385
  .describe("Array of image file paths to analyze"),
384
386
  prompt: z.string().describe("Text prompt or question about the images"),
385
387
  }),
388
+ timeoutMs: 300000,
386
389
  execute: async (args: { image_paths: string[]; prompt: string }) => {
387
390
  return safeToolExecute(async () => {
388
391
  try {
@@ -459,6 +462,7 @@ export const geminiSingleSpeakerTts = {
459
462
  "Output WAV file path (optional, defaults to timestamp-based filename)"
460
463
  ),
461
464
  }),
465
+ timeoutMs: 300000,
462
466
  execute: async (args: {
463
467
  text: string;
464
468
  voice_name: string;
@@ -547,6 +551,7 @@ export const geminiAnalyzeVideos = {
547
551
  "Media resolution: 'default' or 'low' (low resolution uses ~100 tokens/sec vs 300 tokens/sec)"
548
552
  ),
549
553
  }),
554
+ timeoutMs: 300000,
550
555
  execute: async (args: {
551
556
  video_inputs: string[];
552
557
  prompt: string;
@@ -20,6 +20,7 @@ export const perplexityImages = {
20
20
  "A list of allowed image formats. E.g., ['jpg', 'png', 'gif']."
21
21
  ),
22
22
  }),
23
+ timeoutMs: 300000,
23
24
  execute: async (args: {
24
25
  query: string;
25
26
  image_domain_filter?: string[];
@@ -133,6 +134,7 @@ export const perplexityVideos = {
133
134
  "A list of domains to limit the search to (e.g., ['youtube.com']). Use a '-' prefix to exclude a domain."
134
135
  ),
135
136
  }),
137
+ timeoutMs: 300000,
136
138
  execute: async (args: { query: string; search_domain_filter?: string[] }) => {
137
139
  return safeToolExecute(async () => {
138
140
  const apiKey = process.env.PERPLEXITY_API_KEY;
@@ -20,6 +20,7 @@ export const analyzeYoutubeVideo = {
20
20
  .string()
21
21
  .describe("Analysis prompt or question about the YouTube video content"),
22
22
  }),
23
+ timeoutMs: 300000,
23
24
  execute: async (args: { youtube_url: string; prompt: string }) => {
24
25
  return safeToolExecute(async () => {
25
26
  try {