@mixio-pro/kalaasetu-mcp 2.1.2-beta → 2.1.2

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": "2.1.2-beta",
3
+ "version": "2.1.2",
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
@@ -8,6 +8,7 @@ import {
8
8
  } from "./tools/fal";
9
9
  import { createAllFalTools } from "./tools/fal/dynamic-tools";
10
10
  import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
11
+ import { generateImage } from "./tools/generate-image";
11
12
  import { imageToVideo } from "./tools/image-to-video";
12
13
  import { getGenerationStatus } from "./tools/get-status";
13
14
 
@@ -30,6 +31,7 @@ async function main() {
30
31
  // server.addTool(geminiTextToImage);
31
32
  // server.addTool(geminiEditImage);
32
33
  server.addTool(imageToVideo);
34
+ server.addTool(generateImage);
33
35
 
34
36
  // 3. Add Discovery Tools
35
37
  server.addTool(falListPresets);
@@ -0,0 +1,219 @@
1
+ import { z } from "zod";
2
+ import { GoogleGenAI } from "@google/genai";
3
+ import * as fs from "fs";
4
+ import { getStorage } from "../storage";
5
+ import { generateTimestampedFilename } from "../utils/filename";
6
+ import { safeToolExecute } from "../utils/tool-wrapper";
7
+ import { ensureLocalFile } from "../utils/url-file";
8
+ import { resolveEnhancer } from "../utils/prompt-enhancer-presets";
9
+
10
+ const ai = new GoogleGenAI({
11
+ apiKey: process.env.GEMINI_API_KEY || "",
12
+ });
13
+
14
+ async function fileToGenerativePart(filePath: string) {
15
+ const fileResult = await ensureLocalFile(filePath);
16
+
17
+ try {
18
+ let imageBytes: Buffer;
19
+ if (fileResult.isTemp) {
20
+ imageBytes = fs.readFileSync(fileResult.path);
21
+ } else {
22
+ const storage = getStorage();
23
+ imageBytes = await storage.readFile(fileResult.path);
24
+ }
25
+
26
+ return {
27
+ inlineData: {
28
+ data: Buffer.from(imageBytes).toString("base64"),
29
+ mimeType: "image/jpeg",
30
+ },
31
+ };
32
+ } finally {
33
+ fileResult.cleanup();
34
+ }
35
+ }
36
+
37
+ export const generateImage = {
38
+ name: "generateImage",
39
+ description:
40
+ "Generate high-quality images from text prompts using Google's Imagen 3 model via Gemini. " +
41
+ "This tool supports conversational history with reference images and their descriptions. " +
42
+ "Use this tool when you need to provide specific reference images with context/descriptions to guide the generation.",
43
+ parameters: z.object({
44
+ prompt: z
45
+ .string()
46
+ .describe("Detailed text description of the image to generate."),
47
+ aspect_ratio: z
48
+ .string()
49
+ .optional()
50
+ .describe(
51
+ "Supported ratios: 1:1, 3:4, 4:3, 9:16, or 16:9. Default is 9:16.",
52
+ ),
53
+ output_path: z
54
+ .string()
55
+ .optional()
56
+ .describe(
57
+ "Optional: specific local path or filename to save the image. " +
58
+ "If omitted, a timestamped filename is generated automatically.",
59
+ ),
60
+ reference_images: z
61
+ .array(
62
+ z.object({
63
+ path: z
64
+ .string()
65
+ .describe("Local path or URL to the reference image."),
66
+ description: z
67
+ .string()
68
+ .optional()
69
+ .describe("Optional description of the reference image."),
70
+ }),
71
+ )
72
+ .optional()
73
+ .describe(
74
+ "Optional: List of reference images with optional descriptions to guide the style or content.",
75
+ ),
76
+ enhancer_preset: z
77
+ .string()
78
+ .optional()
79
+ .describe(
80
+ "Optional: Name of a prompt enhancer preset to apply (e.g., 'cinematic', 'photorealistic', 'anime'). " +
81
+ "Automatically enhances the prompt with professional style modifiers.",
82
+ ),
83
+ }),
84
+ timeoutMs: 300000,
85
+ execute: async (args: {
86
+ prompt: string;
87
+ aspect_ratio?: string;
88
+ output_path?: string;
89
+ reference_images?: { path: string; description?: string }[];
90
+ enhancer_preset?: string;
91
+ }) => {
92
+ return safeToolExecute(
93
+ async () => {
94
+ try {
95
+ // Apply prompt enhancement if preset specified
96
+ let enhancedPrompt = args.prompt;
97
+ if (args.enhancer_preset) {
98
+ const enhancer = resolveEnhancer(args.enhancer_preset);
99
+ if (enhancer.hasTransformations()) {
100
+ enhancedPrompt = enhancer.enhance(args.prompt);
101
+ }
102
+ }
103
+
104
+ const contents: any[] = [];
105
+
106
+ // Add reference images with descriptions (conversational history style)
107
+ if (args.reference_images && Array.isArray(args.reference_images)) {
108
+ for (const refImg of args.reference_images) {
109
+ if (refImg.description) {
110
+ contents.push({ text: refImg.description });
111
+ }
112
+ const imagePart = await fileToGenerativePart(refImg.path);
113
+ contents.push(imagePart);
114
+ }
115
+ }
116
+
117
+ // Add main prompt
118
+ contents.push({ text: enhancedPrompt });
119
+
120
+ const PRIMARY_MODEL = "gemini-3-pro-image-preview";
121
+ const FALLBACK_MODEL = "gemini-2.5-flash-image";
122
+
123
+ const generateWithFallback = async () => {
124
+ const config = {
125
+ contents: contents,
126
+ config: {
127
+ responseModalities: ["TEXT" as const, "IMAGE" as const],
128
+ imageConfig: {
129
+ aspectRatio: args.aspect_ratio || "9:16",
130
+ },
131
+ },
132
+ };
133
+
134
+ const isUnavailableError = (err: any): boolean => {
135
+ if (err?.status === 503) return true;
136
+ if (err?.error?.code === 503) return true;
137
+ const msg = String(err?.message || "");
138
+ return (
139
+ msg.includes("503") ||
140
+ msg.includes("UNAVAILABLE") ||
141
+ msg.includes("high demand")
142
+ );
143
+ };
144
+
145
+ try {
146
+ return await ai.models.generateContent({
147
+ model: PRIMARY_MODEL,
148
+ ...config,
149
+ });
150
+ } catch (err: any) {
151
+ if (isUnavailableError(err)) {
152
+ console.warn(
153
+ `⚠️ ${PRIMARY_MODEL} unavailable (503), falling back to ${FALLBACK_MODEL}`,
154
+ );
155
+ try {
156
+ return await ai.models.generateContent({
157
+ model: FALLBACK_MODEL,
158
+ ...config,
159
+ });
160
+ } catch (fallbackErr: any) {
161
+ throw new Error(
162
+ `Both models unavailable — primary (${PRIMARY_MODEL}): ${err.message}; fallback (${FALLBACK_MODEL}): ${fallbackErr.message}`,
163
+ );
164
+ }
165
+ }
166
+ throw err;
167
+ }
168
+ };
169
+
170
+ const response = await generateWithFallback();
171
+
172
+ const images = [];
173
+ let textResponse = "";
174
+
175
+ if (response.candidates && response.candidates[0]?.content?.parts) {
176
+ for (const part of response.candidates[0].content.parts) {
177
+ if (part.text) {
178
+ textResponse += part.text;
179
+ } else if (part.inlineData?.data) {
180
+ const imageData = part.inlineData.data;
181
+ // Always save the image
182
+ const outputPath =
183
+ args.output_path ||
184
+ generateTimestampedFilename("generated_image.png");
185
+ const storage = getStorage();
186
+ const url = await storage.writeFile(
187
+ outputPath,
188
+ Buffer.from(imageData, "base64"),
189
+ );
190
+ images.push({
191
+ url,
192
+ filename: outputPath,
193
+ mimeType: "image/png",
194
+ });
195
+ }
196
+ }
197
+ }
198
+
199
+ if (images.length > 0) {
200
+ return JSON.stringify({
201
+ url: images?.[0]?.url,
202
+ images,
203
+ message: textResponse || "Image generated successfully",
204
+ });
205
+ }
206
+
207
+ return (
208
+ textResponse ||
209
+ "Image generation completed but no image was produced"
210
+ );
211
+ } catch (error: any) {
212
+ throw new Error(`Image generation failed: ${error.message}`);
213
+ }
214
+ },
215
+ "gemini-generateImage",
216
+ { toolName: "generateImage" },
217
+ );
218
+ },
219
+ };
@@ -23,6 +23,7 @@ interface VertexOperation {
23
23
  [key: string]: any;
24
24
  };
25
25
  error?: any;
26
+ _vertexError?: string;
26
27
  [key: string]: any;
27
28
  }
28
29
 
@@ -99,6 +100,19 @@ export async function checkVertexStatus(resumeEndpoint: string): Promise<any> {
99
100
 
100
101
  const result = (await response.json()) as VertexOperation;
101
102
 
103
+ // If the operation completed with an error, surface it immediately
104
+ if (result.done && result.error) {
105
+ const errMsg =
106
+ typeof result.error === "object"
107
+ ? result.error.message ||
108
+ result.error.code ||
109
+ JSON.stringify(result.error)
110
+ : String(result.error);
111
+ // Attach a structured error to the result for callers to inspect
112
+ result._vertexError = errMsg;
113
+ return result;
114
+ }
115
+
102
116
  // If completed, save videos if present
103
117
  const done = !!result.done || !!result.response;
104
118
  if (done) {
@@ -515,6 +515,30 @@ export const imageToVideo = {
515
515
  });
516
516
  }
517
517
 
518
+ // Check if the operation failed with an error
519
+ if (current.error || current._vertexError) {
520
+ const errMsg =
521
+ current._vertexError ||
522
+ (typeof current.error === "object"
523
+ ? current.error.message ||
524
+ current.error.code ||
525
+ JSON.stringify(current.error)
526
+ : String(current.error));
527
+ return JSON.stringify({
528
+ status: "ERROR",
529
+ message: `Vertex AI video generation failed: ${errMsg}`,
530
+ operationName,
531
+ error:
532
+ typeof current.error === "object"
533
+ ? {
534
+ code: current.error.code,
535
+ message: current.error.message,
536
+ status: current.error.status,
537
+ }
538
+ : current.error,
539
+ });
540
+ }
541
+
518
542
  const resp = current.response || current;
519
543
 
520
544
  // checkVertexStatus already handles saving videos and sanitizing base64
@@ -20,13 +20,20 @@ export const TOOL_CREDITS: Record<string, ToolCreditConfig> = {
20
20
  credits: 5,
21
21
  provider: "google-gemini",
22
22
  chargeable: true,
23
- modelName: "imagen-3.0-generate-002",
23
+ modelName: "gemini-3-pro-image-preview",
24
24
  },
25
25
  editImage: {
26
26
  credits: 5,
27
27
  provider: "google-gemini",
28
28
  chargeable: true,
29
- modelName: "imagen-3.0-capability-001",
29
+ modelName: "gemini-3-pro-image-preview",
30
+ },
31
+
32
+ generateImageConversational: {
33
+ credits: 5,
34
+ provider: "google-gemini",
35
+ chargeable: true,
36
+ modelName: "gemini-3-pro-image-preview",
30
37
  },
31
38
 
32
39
  // Vertex AI Video Generation - 20 credits (premium)
@@ -34,7 +41,7 @@ export const TOOL_CREDITS: Record<string, ToolCreditConfig> = {
34
41
  credits: 20,
35
42
  provider: "google-vertex",
36
43
  chargeable: true,
37
- modelName: "veo-2.0-generate-001",
44
+ modelName: "veo-3.1-fast-generate-001",
38
45
  },
39
46
 
40
47
  // FAL AI Video Generation - 15 credits