@mixio-pro/kalaasetu-mcp 2.1.2-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 +1 -1
- package/src/index.ts +2 -0
- package/src/tools/generate-image.ts +178 -0
package/package.json
CHANGED
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
|
|
|
@@ -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(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
|
+
};
|