@mixio-pro/kalaasetu-mcp 1.2.2 → 2.0.1-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/fal-config.json +106 -0
- package/package.json +2 -1
- package/src/index.ts +0 -9
- package/src/tools/fal/config.ts +120 -23
- package/src/tools/fal/generate.ts +361 -103
- package/src/tools/fal/index.ts +2 -7
- package/src/tools/fal/models.ts +157 -32
- package/src/tools/gemini.ts +40 -2
- package/src/tools/image-to-video.ts +333 -118
- package/src/utils/llm-prompt-enhancer.ts +302 -0
- package/src/utils/prompt-enhancer-presets.ts +303 -0
- package/src/utils/prompt-enhancer.ts +186 -0
package/src/tools/fal/models.ts
CHANGED
|
@@ -1,54 +1,159 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import {
|
|
3
|
+
loadFalConfig,
|
|
4
|
+
saveFalConfig,
|
|
5
|
+
FAL_BASE_URL,
|
|
6
|
+
DEFAULT_TIMEOUT,
|
|
7
|
+
} from "./config";
|
|
8
|
+
import { safeToolExecute } from "../../utils/tool-wrapper";
|
|
9
|
+
|
|
1
10
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
11
|
+
* Extract simplified input schema from FAL OpenAPI response.
|
|
12
|
+
* Returns only the properties object from the input schema.
|
|
4
13
|
*/
|
|
14
|
+
function extractInputSchema(openApiSchema: any): Record<string, any> | null {
|
|
15
|
+
try {
|
|
16
|
+
const schemas = openApiSchema?.components?.schemas;
|
|
17
|
+
if (!schemas) return null;
|
|
5
18
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
19
|
+
// Find the input schema - usually named like "Ltx2ImageToVideoInput" or similar
|
|
20
|
+
const inputSchemaKey = Object.keys(schemas).find(
|
|
21
|
+
(key) =>
|
|
22
|
+
key.toLowerCase().includes("input") &&
|
|
23
|
+
!key.toLowerCase().includes("output")
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
if (!inputSchemaKey) return null;
|
|
27
|
+
|
|
28
|
+
const inputSchema = schemas[inputSchemaKey];
|
|
29
|
+
if (!inputSchema?.properties) return null;
|
|
30
|
+
|
|
31
|
+
// Extract simplified properties
|
|
32
|
+
const simplified: Record<string, any> = {};
|
|
33
|
+
for (const [propName, propDef] of Object.entries(
|
|
34
|
+
inputSchema.properties as Record<string, any>
|
|
35
|
+
)) {
|
|
36
|
+
simplified[propName] = {
|
|
37
|
+
type: propDef.type,
|
|
38
|
+
description: propDef.description,
|
|
39
|
+
...(propDef.enum && { enum: propDef.enum }),
|
|
40
|
+
...(propDef.default !== undefined && { default: propDef.default }),
|
|
41
|
+
...(propDef.examples && { example: propDef.examples[0] }),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return simplified;
|
|
46
|
+
} catch (e) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
9
50
|
|
|
10
51
|
/**
|
|
11
52
|
* Tool to list available generation presets and their intents.
|
|
53
|
+
* Dynamically fetches and caches input schemas from FAL API.
|
|
12
54
|
*/
|
|
13
55
|
export const falListPresets = {
|
|
14
56
|
name: "fal_list_presets",
|
|
15
57
|
description:
|
|
16
58
|
"The entry point for discovering fal.ai capabilities on this server. " +
|
|
17
59
|
"Lists all available generation presets, including their high-level 'intent' (e.g., 'Generate cinematic video'), " +
|
|
18
|
-
"and
|
|
19
|
-
parameters: z.object({
|
|
20
|
-
|
|
21
|
-
|
|
60
|
+
"input/output types, and INPUT SCHEMA with parameter details. Call this first when you need to perform an AI generation task.",
|
|
61
|
+
parameters: z.object({
|
|
62
|
+
refresh_schemas: z
|
|
63
|
+
.boolean()
|
|
64
|
+
.optional()
|
|
65
|
+
.describe("If true, re-fetch schemas from FAL API even if cached."),
|
|
66
|
+
}),
|
|
67
|
+
timeoutMs: 60000, // Allow more time for schema fetching
|
|
68
|
+
execute: async (args: { refresh_schemas?: boolean }) => {
|
|
22
69
|
return safeToolExecute(async () => {
|
|
23
70
|
const config = loadFalConfig();
|
|
71
|
+
let updated = false;
|
|
72
|
+
|
|
73
|
+
// Fetch schemas for presets that don't have them (or if refresh requested)
|
|
74
|
+
for (const preset of config.presets) {
|
|
75
|
+
const shouldFetch = !preset.input_schema || args.refresh_schemas;
|
|
76
|
+
console.log(
|
|
77
|
+
`[fal_list_presets] ${
|
|
78
|
+
preset.presetName
|
|
79
|
+
}: shouldFetch=${shouldFetch}, hasSchema=${!!preset.input_schema}, refresh=${
|
|
80
|
+
args.refresh_schemas
|
|
81
|
+
}`
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
if (shouldFetch) {
|
|
85
|
+
try {
|
|
86
|
+
const url = `https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=${preset.modelId}`;
|
|
87
|
+
console.log(`[fal_list_presets] Fetching schema from: ${url}`);
|
|
88
|
+
const response = await fetch(url, {
|
|
89
|
+
method: "GET",
|
|
90
|
+
signal: AbortSignal.timeout(10000),
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
if (response.ok) {
|
|
94
|
+
const openApiSchema = await response.json();
|
|
95
|
+
const simplified = extractInputSchema(openApiSchema);
|
|
96
|
+
console.log(
|
|
97
|
+
`[fal_list_presets] Extracted schema for ${preset.presetName}:`,
|
|
98
|
+
simplified ? Object.keys(simplified) : null
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
if (simplified) {
|
|
102
|
+
preset.input_schema = simplified;
|
|
103
|
+
updated = true;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
console.log(
|
|
107
|
+
`[fal_list_presets] Fetch failed: ${response.status}`
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
} catch (e: any) {
|
|
111
|
+
console.log(
|
|
112
|
+
`[fal_list_presets] Error fetching schema for ${preset.presetName}:`,
|
|
113
|
+
e.message
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Save updated config if schemas were fetched
|
|
120
|
+
if (updated) {
|
|
121
|
+
console.log(`[fal_list_presets] Saving updated config...`);
|
|
122
|
+
const saved = saveFalConfig(config);
|
|
123
|
+
console.log(`[fal_list_presets] Config saved: ${saved}`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Return enriched preset list
|
|
24
127
|
const summary = config.presets.map((p) => ({
|
|
25
128
|
presetName: p.presetName,
|
|
26
129
|
intent: p.intent,
|
|
27
130
|
inputType: p.inputType,
|
|
28
131
|
outputType: p.outputType,
|
|
29
132
|
description: p.description,
|
|
133
|
+
inputSchema: p.input_schema,
|
|
30
134
|
}));
|
|
135
|
+
|
|
31
136
|
return JSON.stringify(summary, null, 2);
|
|
32
137
|
}, "fal_list_presets");
|
|
33
138
|
},
|
|
34
139
|
};
|
|
35
140
|
|
|
36
141
|
/**
|
|
37
|
-
* Tool to get full details for a specific preset, including default parameters
|
|
142
|
+
* Tool to get full details for a specific preset, including default parameters
|
|
143
|
+
* and real-time model metadata from fal.ai.
|
|
38
144
|
*/
|
|
39
145
|
export const falGetPresetDetails = {
|
|
40
146
|
name: "fal_get_preset_details",
|
|
41
147
|
description:
|
|
42
148
|
"Retrieve full details for a specific generation preset. " +
|
|
43
|
-
"
|
|
44
|
-
"
|
|
149
|
+
"This tool fetches both the local preset configuration (like 'defaultParams') " +
|
|
150
|
+
"and the live model metadata (schema, benchmarks, etc.) from fal.ai. " +
|
|
151
|
+
"Use this to understand the full capabilities and constraints of a model. " +
|
|
45
152
|
"ONLY USE WHEN WORKING WITH FAL MODELS/PRESETS.",
|
|
46
153
|
parameters: z.object({
|
|
47
154
|
preset_name: z
|
|
48
155
|
.string()
|
|
49
|
-
.describe(
|
|
50
|
-
"The name of the preset to inspect (e.g., 'ltx_image_to_video')."
|
|
51
|
-
),
|
|
156
|
+
.describe("The name of the preset to inspect (e.g., 'cinematic_image')."),
|
|
52
157
|
}),
|
|
53
158
|
timeoutMs: 30000,
|
|
54
159
|
execute: async (args: { preset_name: string }) => {
|
|
@@ -60,7 +165,43 @@ export const falGetPresetDetails = {
|
|
|
60
165
|
if (!preset) {
|
|
61
166
|
throw new Error(`Preset '${args.preset_name}' not found.`);
|
|
62
167
|
}
|
|
63
|
-
|
|
168
|
+
|
|
169
|
+
// Fetch live model metadata/schema from fal.ai API
|
|
170
|
+
// Based on: https://fal.ai/api/openapi/queue/openapi.json?endpoint_id={model_id}
|
|
171
|
+
let modelMetadata: any = null;
|
|
172
|
+
let schemaSource = "none";
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const url = `https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=${preset.modelId}`;
|
|
176
|
+
const schema = await publicRequest(url);
|
|
177
|
+
|
|
178
|
+
// Extract relevant schema parts if possible, or return full schema
|
|
179
|
+
if (schema) {
|
|
180
|
+
modelMetadata = schema;
|
|
181
|
+
schemaSource = "api";
|
|
182
|
+
}
|
|
183
|
+
} catch (e) {
|
|
184
|
+
// console.error(`Failed to fetch schema for ${preset.modelId}:`, e);
|
|
185
|
+
// Fallback to locally defined schema if available
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Fallback: If API failed or returned nothing, use manual schema from config
|
|
189
|
+
if (!modelMetadata && preset.input_schema) {
|
|
190
|
+
modelMetadata = preset.input_schema;
|
|
191
|
+
schemaSource = "local_config";
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return JSON.stringify(
|
|
195
|
+
{
|
|
196
|
+
preset,
|
|
197
|
+
modelMetadata,
|
|
198
|
+
_meta: {
|
|
199
|
+
schemaSource,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
null,
|
|
203
|
+
2
|
|
204
|
+
);
|
|
64
205
|
}, "fal_get_preset_details");
|
|
65
206
|
},
|
|
66
207
|
};
|
|
@@ -82,22 +223,6 @@ async function publicRequest(url: string): Promise<any> {
|
|
|
82
223
|
return response.json();
|
|
83
224
|
}
|
|
84
225
|
|
|
85
|
-
/**
|
|
86
|
-
* Tool to retrieve the current full FAL configuration.
|
|
87
|
-
*/
|
|
88
|
-
export const falGetConfig = {
|
|
89
|
-
name: "fal_get_config",
|
|
90
|
-
description: "Retrieve the full FAL configuration JSON file content.",
|
|
91
|
-
parameters: z.object({}),
|
|
92
|
-
timeoutMs: 30000,
|
|
93
|
-
execute: async () => {
|
|
94
|
-
return safeToolExecute(async () => {
|
|
95
|
-
const config = loadFalConfig();
|
|
96
|
-
return JSON.stringify(config, null, 2);
|
|
97
|
-
}, "fal_get_config");
|
|
98
|
-
},
|
|
99
|
-
};
|
|
100
|
-
|
|
101
226
|
/*
|
|
102
227
|
// ORIGINAL UNRESTRICTED TOOLS - Commented out for reference
|
|
103
228
|
|
package/src/tools/gemini.ts
CHANGED
|
@@ -12,6 +12,10 @@ import { PassThrough } from "stream";
|
|
|
12
12
|
import { getStorage } from "../storage";
|
|
13
13
|
import { generateTimestampedFilename } from "../utils/filename";
|
|
14
14
|
import { safeToolExecute } from "../utils/tool-wrapper";
|
|
15
|
+
import {
|
|
16
|
+
resolveEnhancer,
|
|
17
|
+
listImageEnhancerPresets,
|
|
18
|
+
} from "../utils/prompt-enhancer-presets";
|
|
15
19
|
|
|
16
20
|
const ai = new GoogleGenAI({
|
|
17
21
|
apiKey: process.env.GEMINI_API_KEY || "",
|
|
@@ -232,6 +236,13 @@ export const geminiTextToImage = {
|
|
|
232
236
|
.describe(
|
|
233
237
|
"Optional: local paths or URLs of images to use as visual references for style or composition."
|
|
234
238
|
),
|
|
239
|
+
enhancer_preset: z
|
|
240
|
+
.string()
|
|
241
|
+
.optional()
|
|
242
|
+
.describe(
|
|
243
|
+
"Optional: Name of a prompt enhancer preset to apply (e.g., 'cinematic', 'photorealistic', 'anime'). " +
|
|
244
|
+
"Automatically enhances the prompt with professional style modifiers."
|
|
245
|
+
),
|
|
235
246
|
}),
|
|
236
247
|
timeoutMs: 300000,
|
|
237
248
|
execute: async (args: {
|
|
@@ -239,10 +250,20 @@ export const geminiTextToImage = {
|
|
|
239
250
|
aspect_ratio?: string;
|
|
240
251
|
output_path?: string;
|
|
241
252
|
reference_images?: string[];
|
|
253
|
+
enhancer_preset?: string;
|
|
242
254
|
}) => {
|
|
243
255
|
return safeToolExecute(async () => {
|
|
244
256
|
try {
|
|
245
|
-
|
|
257
|
+
// Apply prompt enhancement if preset specified
|
|
258
|
+
let enhancedPrompt = args.prompt;
|
|
259
|
+
if (args.enhancer_preset) {
|
|
260
|
+
const enhancer = resolveEnhancer(args.enhancer_preset);
|
|
261
|
+
if (enhancer.hasTransformations()) {
|
|
262
|
+
enhancedPrompt = enhancer.enhance(args.prompt);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const contents: any[] = [enhancedPrompt];
|
|
246
267
|
|
|
247
268
|
if (args.reference_images && Array.isArray(args.reference_images)) {
|
|
248
269
|
for (const refPath of args.reference_images) {
|
|
@@ -337,6 +358,13 @@ export const geminiEditImage = {
|
|
|
337
358
|
.describe(
|
|
338
359
|
"Optional: additional images to guide the edit (e.g., to reference a specific character or object style)."
|
|
339
360
|
),
|
|
361
|
+
enhancer_preset: z
|
|
362
|
+
.string()
|
|
363
|
+
.optional()
|
|
364
|
+
.describe(
|
|
365
|
+
"Optional: Name of a prompt enhancer preset to apply (e.g., 'cinematic', 'photorealistic'). " +
|
|
366
|
+
"Enhances the edit instructions with professional style modifiers."
|
|
367
|
+
),
|
|
340
368
|
}),
|
|
341
369
|
timeoutMs: 300000,
|
|
342
370
|
execute: async (args: {
|
|
@@ -344,11 +372,21 @@ export const geminiEditImage = {
|
|
|
344
372
|
prompt: string;
|
|
345
373
|
output_path?: string;
|
|
346
374
|
reference_images?: string[];
|
|
375
|
+
enhancer_preset?: string;
|
|
347
376
|
}) => {
|
|
348
377
|
return safeToolExecute(async () => {
|
|
349
378
|
try {
|
|
379
|
+
// Apply prompt enhancement if preset specified
|
|
380
|
+
let enhancedPrompt = args.prompt;
|
|
381
|
+
if (args.enhancer_preset) {
|
|
382
|
+
const enhancer = resolveEnhancer(args.enhancer_preset);
|
|
383
|
+
if (enhancer.hasTransformations()) {
|
|
384
|
+
enhancedPrompt = enhancer.enhance(args.prompt);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
350
388
|
const imagePart = await fileToGenerativePart(args.image_path);
|
|
351
|
-
const contents: any[] = [
|
|
389
|
+
const contents: any[] = [enhancedPrompt, imagePart];
|
|
352
390
|
|
|
353
391
|
if (args.reference_images) {
|
|
354
392
|
for (const refPath of args.reference_images) {
|