@mixio-pro/kalaasetu-mcp 1.1.2 → 1.1.4

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.2",
3
+ "version": "1.1.4",
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
@@ -1,21 +1,22 @@
1
1
  #!/usr/bin/env bun
2
2
  import { FastMCP } from "fastmcp";
3
- import {
4
- geminiTextToImage,
5
- geminiEditImage,
6
- geminiAnalyzeImages,
7
- geminiSingleSpeakerTts,
8
- geminiAnalyzeVideos,
9
- } from "./tools/gemini";
10
- import { analyzeYoutubeVideo } from "./tools/youtube";
3
+ import pkg from "../package.json";
4
+ import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
11
5
  import { imageToVideo } from "./tools/image-to-video";
12
- import { infinitalk } from "./tools/infinitalk";
13
- import { hunyuanAvatar } from "./tools/hunyuan-avatar";
14
- import { perplexityImages, perplexityVideos } from "./tools/perplexity";
6
+ import {
7
+ falListModels,
8
+ falSearchModels,
9
+ falGetSchema,
10
+ falGenerateContent,
11
+ falGetResult,
12
+ falGetStatus,
13
+ falCancelRequest,
14
+ falUploadFile,
15
+ } from "./tools/fal";
15
16
 
16
17
  const server = new FastMCP({
17
18
  name: "Kalaasetu MCP Server",
18
- version: "1.1.0",
19
+ version: pkg.version as any,
19
20
  });
20
21
 
21
22
  // Gemini Image Tools
@@ -27,7 +28,7 @@ server.addTool(geminiEditImage);
27
28
  // server.addTool(geminiSingleSpeakerTts);
28
29
 
29
30
  // Gemini Video Analysis Tool
30
- server.addTool(geminiAnalyzeVideos);
31
+ // server.addTool(geminiAnalyzeVideos);
31
32
 
32
33
  // YouTube Analyzer Tool
33
34
  // server.addTool(analyzeYoutubeVideo);
@@ -35,16 +36,20 @@ server.addTool(geminiAnalyzeVideos);
35
36
  // Vertex AI Image-to-Video Tool
36
37
  server.addTool(imageToVideo);
37
38
 
38
- // FAL AI Infinitalk Tool
39
- // server.addTool(infinitalk);
40
-
41
- // FAL AI Hunyuan Avatar Tool
42
- // server.addTool(hunyuanAvatar);
43
-
44
39
  // Perplexity Search Tools
45
40
  // server.addTool(perplexityImages);
46
41
  // server.addTool(perplexityVideos);
47
42
 
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);
52
+
48
53
  server.start({
49
54
  transportType: "stdio",
50
55
  });
@@ -0,0 +1,52 @@
1
+ import { z } from "zod";
2
+ import { safeToolExecute } from "./utils/tool-wrapper";
3
+
4
+ async function runTest() {
5
+ console.log("Running Error Context Verification Test...");
6
+
7
+ // 1. Mock Schema Validation Error
8
+ const schema = z.object({
9
+ prompt: z.string(),
10
+ count: z.number().min(1).max(5),
11
+ });
12
+
13
+ const mockToolWithValidation = async (args: any) => {
14
+ // Simulate what happens in a real tool when Zod parsing fails inside the execute block
15
+ // OR if we manually parse and it fails
16
+ // Most tools currently don't re-parse inside execute because MCP handles it?
17
+ // Wait, the tools define `parameters` schema, but the `execute` function receives `args`.
18
+ // The MCP server framework usually does the validation BEFORE calling execute.
19
+ // BUT if we look at the code, some tools do manual checks or parsing.
20
+
21
+ // Let's simulate a manual validation failure inside execute, or a deep validation failure
22
+ try {
23
+ schema.parse(args);
24
+ } catch (error) {
25
+ throw error;
26
+ }
27
+ return "success";
28
+ };
29
+
30
+ const zodResult = await safeToolExecute(
31
+ async () => mockToolWithValidation({ prompt: 123, count: 10 }),
32
+ "ZodTool"
33
+ );
34
+ console.log("\n--- Zod Error Result ---");
35
+ console.log(JSON.stringify(zodResult, null, 2));
36
+
37
+ // 2. Mock API Error with Status
38
+ const mockToolWithApiError = async () => {
39
+ // Simulate a fetch error structure
40
+ const err: any = new Error("API request failed");
41
+ err.status = 429;
42
+ err.statusText = "Too Many Requests";
43
+ err.responseBody = "Rate limit exceeded. Retry in 60s.";
44
+ throw err;
45
+ };
46
+
47
+ const apiResult = await safeToolExecute(mockToolWithApiError, "ApiTool");
48
+ console.log("\n--- API Error Result ---");
49
+ console.log(JSON.stringify(apiResult, null, 2));
50
+ }
51
+
52
+ runTest();
@@ -0,0 +1,31 @@
1
+ import { safeToolExecute } from "./utils/tool-wrapper";
2
+
3
+ async function runTest() {
4
+ console.log("Running Error Handling Verification Test...");
5
+
6
+ // Mock tool that throws an error
7
+ const mockTool = async () => {
8
+ throw new Error("Simulated tool failure");
9
+ };
10
+
11
+ const result = await safeToolExecute(mockTool, "MockTool");
12
+
13
+ console.log("Result:", JSON.stringify(result, null, 2));
14
+
15
+ if (
16
+ result.isError === true &&
17
+ result.content &&
18
+ result.content[0]?.text.includes(
19
+ "Tool execution failed in MockTool: Simulated tool failure"
20
+ )
21
+ ) {
22
+ console.log(
23
+ "✅ Verification PASSED: Error was correctly caught and formatted."
24
+ );
25
+ } else {
26
+ console.error("❌ Verification FAILED: Unexpected result format.");
27
+ process.exit(1);
28
+ }
29
+ }
30
+
31
+ runTest();
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Configuration module for the fal.ai MCP server.
3
+ * Provides centralized configuration settings for API endpoints, timeouts, and server metadata.
4
+ */
5
+
6
+ // API URLs
7
+ export const FAL_BASE_URL = "https://fal.ai/api";
8
+ export const FAL_QUEUE_URL = "https://queue.fal.run";
9
+ export const FAL_DIRECT_URL = "https://fal.run";
10
+ export const FAL_REST_URL = "https://rest.alpha.fal.ai";
11
+
12
+ // Timeouts (in milliseconds)
13
+ export const DEFAULT_TIMEOUT = 30000;
14
+ export const AUTHENTICATED_TIMEOUT = 600000; // 10 minutes for long-running operations
15
+
16
+ // Environment variable names
17
+ export const API_KEY_ENV_VAR = "FAL_KEY";
18
+
19
+ // Server metadata
20
+ export const SERVER_NAME = "fal.ai MCP Server";
21
+ export const SERVER_DESCRIPTION =
22
+ "Access fal.ai models and generate content through MCP";
23
+ export const SERVER_VERSION = "1.0.0";
24
+
25
+ /**
26
+ * Get the fal.ai API key from environment variables.
27
+ */
28
+ export function getApiKey(): string {
29
+ const apiKey = process.env[API_KEY_ENV_VAR];
30
+ if (!apiKey) {
31
+ throw new Error(`${API_KEY_ENV_VAR} environment variable not set`);
32
+ }
33
+ return apiKey;
34
+ }
@@ -0,0 +1,146 @@
1
+ /**
2
+ * Generate module for fal.ai MCP server.
3
+ * Provides tools for generating content and managing queue operations with fal.ai models.
4
+ */
5
+
6
+ import { z } from "zod";
7
+ import { safeToolExecute } from "../../utils/tool-wrapper";
8
+ import {
9
+ FAL_QUEUE_URL,
10
+ FAL_DIRECT_URL,
11
+ AUTHENTICATED_TIMEOUT,
12
+ getApiKey,
13
+ } from "./config";
14
+
15
+ /**
16
+ * Make an authenticated request to fal.ai API.
17
+ */
18
+ async function authenticatedRequest(
19
+ url: string,
20
+ method: "GET" | "POST" | "PUT" = "GET",
21
+ jsonData?: Record<string, any>
22
+ ): Promise<any> {
23
+ const headers: Record<string, string> = {
24
+ Authorization: `Key ${getApiKey()}`,
25
+ "Content-Type": "application/json",
26
+ };
27
+
28
+ const options: RequestInit = {
29
+ method,
30
+ headers,
31
+ signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
32
+ };
33
+
34
+ if (jsonData && (method === "POST" || method === "PUT")) {
35
+ options.body = JSON.stringify(jsonData);
36
+ }
37
+
38
+ const response = await fetch(url, options);
39
+
40
+ if (!response.ok) {
41
+ const errorText = await response.text();
42
+ throw new Error(`[${response.status}] API error: ${errorText}`);
43
+ }
44
+
45
+ return response.json();
46
+ }
47
+
48
+ /**
49
+ * Sanitize parameters by removing null/undefined values.
50
+ */
51
+ function sanitizeParameters(
52
+ parameters: Record<string, any>
53
+ ): Record<string, any> {
54
+ return Object.fromEntries(
55
+ Object.entries(parameters).filter(([_, v]) => v != null)
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Generate content using a fal.ai model.
61
+ */
62
+ export const falGenerateContent = {
63
+ name: "fal_generate_content",
64
+ description:
65
+ "Generate content using any fal.ai model. Supports both direct execution and queued execution for long-running tasks.",
66
+ parameters: z.object({
67
+ model: z.string().describe('The model ID to use (e.g., "fal-ai/flux/dev")'),
68
+ parameters: z
69
+ .record(z.string(), z.any())
70
+ .describe("Model-specific parameters as a dictionary"),
71
+ queue: z
72
+ .boolean()
73
+ .optional()
74
+ .default(false)
75
+ .describe(
76
+ "Whether to use the queuing system for long-running tasks. Default: false"
77
+ ),
78
+ }),
79
+ execute: async (args: {
80
+ model: string;
81
+ parameters: Record<string, any>;
82
+ queue?: boolean;
83
+ }) => {
84
+ return safeToolExecute(async () => {
85
+ const sanitizedParams = sanitizeParameters(args.parameters);
86
+
87
+ const baseUrl = args.queue ? FAL_QUEUE_URL : FAL_DIRECT_URL;
88
+ const url = `${baseUrl}/${args.model}`;
89
+
90
+ console.error(`[fal_generate] Submitting request to ${url}...`);
91
+
92
+ const result = await authenticatedRequest(url, "POST", sanitizedParams);
93
+
94
+ console.error(`[fal_generate] Request completed successfully`);
95
+
96
+ return JSON.stringify(result);
97
+ }, "fal_generate_content");
98
+ },
99
+ };
100
+
101
+ /**
102
+ * Get the result of a queued request.
103
+ */
104
+ export const falGetResult = {
105
+ name: "fal_get_result",
106
+ description: "Get the result of a queued fal.ai request.",
107
+ parameters: z.object({
108
+ url: z.string().describe("The response_url from a queued request"),
109
+ }),
110
+ execute: async (args: { url: string }) => {
111
+ const result = await authenticatedRequest(args.url, "GET");
112
+ return JSON.stringify(result);
113
+ },
114
+ };
115
+
116
+ /**
117
+ * Check the status of a queued request.
118
+ */
119
+ export const falGetStatus = {
120
+ name: "fal_get_status",
121
+ description: "Check the status of a queued fal.ai request.",
122
+ parameters: z.object({
123
+ url: z.string().describe("The status_url from a queued request"),
124
+ }),
125
+ execute: async (args: { url: string }) => {
126
+ const result = await authenticatedRequest(args.url, "GET");
127
+ return JSON.stringify(result);
128
+ },
129
+ };
130
+
131
+ /**
132
+ * Cancel a queued request.
133
+ */
134
+ export const falCancelRequest = {
135
+ name: "fal_cancel_request",
136
+ description: "Cancel a queued fal.ai request.",
137
+ parameters: z.object({
138
+ url: z.string().describe("The cancel_url from a queued request"),
139
+ }),
140
+ execute: async (args: { url: string }) => {
141
+ return safeToolExecute(async () => {
142
+ const result = await authenticatedRequest(args.url, "PUT");
143
+ return JSON.stringify(result);
144
+ }, "fal_cancel_request");
145
+ },
146
+ };
@@ -0,0 +1,14 @@
1
+ /**
2
+ * fal.ai MCP Tools
3
+ * Export all fal.ai tools for registration with the MCP server.
4
+ */
5
+
6
+ export { falListModels, falSearchModels, falGetSchema } from "./models";
7
+ export {
8
+ falGenerateContent,
9
+ falGetResult,
10
+ falGetStatus,
11
+ falCancelRequest,
12
+ } from "./generate";
13
+ export { falUploadFile } from "./storage";
14
+ export { SERVER_NAME, SERVER_DESCRIPTION, SERVER_VERSION } from "./config";
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Models module for fal.ai MCP server.
3
+ * Provides tools for listing, searching, and retrieving schemas for fal.ai models.
4
+ */
5
+
6
+ import { z } from "zod";
7
+ import { FAL_BASE_URL, DEFAULT_TIMEOUT } from "./config";
8
+
9
+ /**
10
+ * Make a non-authenticated request to fal.ai API.
11
+ */
12
+ async function publicRequest(url: string): Promise<any> {
13
+ const response = await fetch(url, {
14
+ method: "GET",
15
+ signal: AbortSignal.timeout(DEFAULT_TIMEOUT),
16
+ });
17
+
18
+ if (!response.ok) {
19
+ const errorText = await response.text();
20
+ throw new Error(`[${response.status}] API error: ${errorText}`);
21
+ }
22
+
23
+ return response.json();
24
+ }
25
+
26
+ /**
27
+ * List available models on fal.ai with optional pagination.
28
+ */
29
+ export const falListModels = {
30
+ name: "fal_list_models",
31
+ description:
32
+ "List available models on fal.ai. Use the page and total arguments for pagination. Avoid listing all models at once.",
33
+ parameters: z.object({
34
+ page: z
35
+ .number()
36
+ .optional()
37
+ .describe("The page number of models to retrieve (pagination)"),
38
+ total: z
39
+ .number()
40
+ .optional()
41
+ .describe("The total number of models to retrieve per page"),
42
+ }),
43
+ execute: async (args: { page?: number; total?: number }) => {
44
+ let url = `${FAL_BASE_URL}/models`;
45
+
46
+ const params: string[] = [];
47
+ if (args.page !== undefined) params.push(`page=${args.page}`);
48
+ if (args.total !== undefined) params.push(`total=${args.total}`);
49
+
50
+ if (params.length > 0) {
51
+ url += "?" + params.join("&");
52
+ }
53
+
54
+ const result = await publicRequest(url);
55
+ return JSON.stringify(result);
56
+ },
57
+ };
58
+
59
+ /**
60
+ * Search for models on fal.ai based on keywords.
61
+ */
62
+ export const falSearchModels = {
63
+ name: "fal_search_models",
64
+ description: "Search for models on fal.ai based on keywords.",
65
+ parameters: z.object({
66
+ keywords: z.string().describe("The search terms to find models"),
67
+ }),
68
+ execute: async (args: { keywords: string }) => {
69
+ const url = `${FAL_BASE_URL}/models?keywords=${encodeURIComponent(
70
+ args.keywords
71
+ )}`;
72
+ const result = await publicRequest(url);
73
+ return JSON.stringify(result);
74
+ },
75
+ };
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
+ };
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Storage module for fal.ai MCP server.
3
+ * Provides tools for uploading files to fal.ai storage.
4
+ */
5
+
6
+ import { z } from "zod";
7
+ import * as fs from "fs";
8
+ import { safeToolExecute } from "../../utils/tool-wrapper";
9
+ import * as path from "path";
10
+ import { FAL_REST_URL, AUTHENTICATED_TIMEOUT, getApiKey } from "./config";
11
+
12
+ /**
13
+ * Get MIME type from file extension.
14
+ */
15
+ function getMimeType(filePath: string): string {
16
+ const ext = path.extname(filePath).toLowerCase();
17
+ const mimeTypes: Record<string, string> = {
18
+ ".jpg": "image/jpeg",
19
+ ".jpeg": "image/jpeg",
20
+ ".png": "image/png",
21
+ ".gif": "image/gif",
22
+ ".webp": "image/webp",
23
+ ".mp4": "video/mp4",
24
+ ".webm": "video/webm",
25
+ ".mov": "video/quicktime",
26
+ ".mp3": "audio/mpeg",
27
+ ".wav": "audio/wav",
28
+ ".ogg": "audio/ogg",
29
+ ".pdf": "application/pdf",
30
+ ".json": "application/json",
31
+ ".txt": "text/plain",
32
+ };
33
+ return mimeTypes[ext] || "application/octet-stream";
34
+ }
35
+
36
+ /**
37
+ * Upload a file to fal.ai storage.
38
+ */
39
+ export const falUploadFile = {
40
+ name: "fal_upload_file",
41
+ description: "Upload a file to fal.ai CDN storage.",
42
+ parameters: z.object({
43
+ path: z.string().describe("The absolute path to the file to upload"),
44
+ }),
45
+ execute: async (args: { path: string }) => {
46
+ return safeToolExecute(async () => {
47
+ // Validate file exists
48
+ if (!fs.existsSync(args.path)) {
49
+ throw new Error(`File not found: ${args.path}`);
50
+ }
51
+
52
+ const filename = path.basename(args.path);
53
+ const fileSize = fs.statSync(args.path).size;
54
+ const contentType = getMimeType(args.path);
55
+
56
+ // Step 1: Initiate upload
57
+ const initiateUrl = `${FAL_REST_URL}/storage/upload/initiate?storage_type=fal-cdn-v3`;
58
+ const initiatePayload = {
59
+ content_type: contentType,
60
+ file_name: filename,
61
+ };
62
+
63
+ console.error(`[fal_upload] Initiating upload for ${filename}...`);
64
+
65
+ const initiateResponse = await fetch(initiateUrl, {
66
+ method: "POST",
67
+ headers: {
68
+ Authorization: `Key ${getApiKey()}`,
69
+ "Content-Type": "application/json",
70
+ },
71
+ body: JSON.stringify(initiatePayload),
72
+ signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
73
+ });
74
+
75
+ if (!initiateResponse.ok) {
76
+ const errorText = await initiateResponse.text();
77
+ throw new Error(
78
+ `[${initiateResponse.status}] Failed to initiate upload: ${errorText}`
79
+ );
80
+ }
81
+
82
+ const initiateData = (await initiateResponse.json()) as {
83
+ file_url: string;
84
+ upload_url: string;
85
+ };
86
+ const { file_url, upload_url } = initiateData;
87
+
88
+ // Step 2: Upload file content
89
+ console.error(`[fal_upload] Uploading file content...`);
90
+
91
+ const fileContent = fs.readFileSync(args.path);
92
+
93
+ const uploadResponse = await fetch(upload_url, {
94
+ method: "PUT",
95
+ headers: {
96
+ "Content-Type": contentType,
97
+ },
98
+ body: fileContent,
99
+ signal: AbortSignal.timeout(AUTHENTICATED_TIMEOUT),
100
+ });
101
+
102
+ if (!uploadResponse.ok) {
103
+ const errorText = await uploadResponse.text();
104
+ throw new Error(
105
+ `[${uploadResponse.status}] Failed to upload file: ${errorText}`
106
+ );
107
+ }
108
+
109
+ console.error(`[fal_upload] Upload completed successfully`);
110
+
111
+ return JSON.stringify({
112
+ file_url,
113
+ file_name: filename,
114
+ file_size: fileSize,
115
+ content_type: contentType,
116
+ });
117
+ }, "fal_upload_file");
118
+ },
119
+ };