@mixio-pro/kalaasetu-mcp 2.1.1-beta → 2.1.3-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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mixio-pro/kalaasetu-mcp",
3
- "version": "2.1.1-beta",
3
+ "version": "2.1.3-beta",
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 { generateImageConversational } from "./tools/generate-image";
11
12
  import { imageToVideo } from "./tools/image-to-video";
12
13
  import { getGenerationStatus } from "./tools/get-status";
13
14
 
@@ -27,9 +28,10 @@ async function main() {
27
28
  await Promise.all([syncFalConfig(), syncPromptEnhancerConfigs()]);
28
29
 
29
30
  // 2. Add Gemini Tools
30
- server.addTool(geminiTextToImage);
31
- server.addTool(geminiEditImage);
31
+ // server.addTool(geminiTextToImage);
32
+ // server.addTool(geminiEditImage);
32
33
  server.addTool(imageToVideo);
34
+ server.addTool(generateImageConversational);
33
35
 
34
36
  // 3. Add Discovery Tools
35
37
  server.addTool(falListPresets);
@@ -0,0 +1,178 @@
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 generateImageConversational = {
38
+ name: "generateImageConversational",
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 response = await ai.models.generateContent({
121
+ model: "gemini-3-pro-image-preview",
122
+ contents: contents, // Pass contents directly which is interpreted as User role parts
123
+ config: {
124
+ responseModalities: ["TEXT", "IMAGE"],
125
+ imageConfig: {
126
+ aspectRatio: args.aspect_ratio || "9:16",
127
+ },
128
+ },
129
+ });
130
+
131
+ const images = [];
132
+ let textResponse = "";
133
+
134
+ if (response.candidates && response.candidates[0]?.content?.parts) {
135
+ for (const part of response.candidates[0].content.parts) {
136
+ if (part.text) {
137
+ textResponse += part.text;
138
+ } else if (part.inlineData?.data) {
139
+ const imageData = part.inlineData.data;
140
+ // Always save the image
141
+ const outputPath =
142
+ args.output_path ||
143
+ generateTimestampedFilename("generated_image.png");
144
+ const storage = getStorage();
145
+ const url = await storage.writeFile(
146
+ outputPath,
147
+ Buffer.from(imageData, "base64"),
148
+ );
149
+ images.push({
150
+ url,
151
+ filename: outputPath,
152
+ mimeType: "image/png",
153
+ });
154
+ }
155
+ }
156
+ }
157
+
158
+ if (images.length > 0) {
159
+ return JSON.stringify({
160
+ url: images?.[0]?.url,
161
+ images,
162
+ message: textResponse || "Image generated successfully",
163
+ });
164
+ }
165
+
166
+ return (
167
+ textResponse ||
168
+ "Image generation completed but no image was produced"
169
+ );
170
+ } catch (error: any) {
171
+ throw new Error(`Image generation failed: ${error.message}`);
172
+ }
173
+ },
174
+ "gemini-generateImageConversational",
175
+ { toolName: "generateImageConversational" },
176
+ );
177
+ },
178
+ };
@@ -13,9 +13,8 @@ const DEFAULT_SENTRY_DSN =
13
13
  "https://349c280f4b1c731bc1ac1a1189cae5e7@o4510770034245632.ingest.us.sentry.io/4510770047025152";
14
14
 
15
15
  // Custom format for console output (cleaner for MCP inspector)
16
- const consoleFormat = printf(({ level, message, timestamp, tags }) => {
17
- const clientId = tags?.client_id ? ` [${tags.client_id}]` : "";
18
- return `${timestamp} [${level}]${clientId} ${message}`;
16
+ const consoleFormat = printf(({ level, message, timestamp }) => {
17
+ return `${timestamp} [${level}] ${message}`;
19
18
  });
20
19
 
21
20
  // Determine log level from environment
@@ -58,12 +57,7 @@ transports.push(
58
57
  export const logger = winston.createLogger({
59
58
  level: LOG_LEVEL,
60
59
  format: combine(timestamp(), winston.format.json()),
61
- defaultMeta: {
62
- service: "kalaasetu-mcp",
63
- tags: {
64
- client_id: process.env.CLIENT_ID || "unknown",
65
- },
66
- },
60
+ defaultMeta: { service: "kalaasetu-mcp" },
67
61
  transports,
68
62
  });
69
63
 
@@ -53,7 +53,7 @@ export function initOpenMeter(): OpenMeter | null {
53
53
  baseUrl: OPENMETER_CONFIG.baseUrl,
54
54
  apiKey: OPENMETER_CONFIG.apiToken,
55
55
  });
56
- logger.info(`[OpenMeter] Client initialized`);
56
+ logger.info(`[OpenMeter] Client initialized for customer: ${clientId}`);
57
57
  }
58
58
 
59
59
  return openmeterClient;
@@ -114,7 +114,7 @@ export async function trackToolCall(context: ToolCallContext): Promise<void> {
114
114
  });
115
115
 
116
116
  logger.info(
117
- `[OpenMeter] Tracked tool call: ${context.toolName} (${creditConfig.credits} credits)`,
117
+ `[OpenMeter] Tracked tool call: ${context.toolName} (${creditConfig.credits} credits) for customer: ${clientId}`,
118
118
  );
119
119
  } catch (error) {
120
120
  // Log but don't throw - tracking failure shouldn't break tool execution