@mixio-pro/kalaasetu-mcp 2.0.8-beta → 2.0.10-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.0.8-beta",
3
+ "version": "2.0.10-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",
@@ -52,7 +52,7 @@
52
52
  "@google/genai": "^1.28.0",
53
53
  "@types/node": "^24.10.1",
54
54
  "@types/wav": "^1.0.4",
55
- "fastmcp": "^3.22.0",
55
+ "fastmcp": "3.26.8",
56
56
  "form-data": "^4.0.5",
57
57
  "google-auth-library": "^10.5.0",
58
58
  "wav": "^1.0.2",
package/src/index.ts CHANGED
@@ -11,46 +11,47 @@ import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
11
11
  import { imageToVideo } from "./tools/image-to-video";
12
12
  import { getGenerationStatus } from "./tools/get-status";
13
13
 
14
+ import { syncFalConfig } from "./tools/fal/config";
15
+ import { syncPromptEnhancerConfigs } from "./utils/prompt-enhancer-presets";
16
+
14
17
  const server = new FastMCP({
15
18
  name: "Kalaasetu MCP Server",
16
19
  version: pkg.version as any,
17
20
  });
18
21
 
19
- // Gemini Image Tools
20
- server.addTool(geminiTextToImage);
21
- server.addTool(geminiEditImage);
22
- // server.addTool(geminiAnalyzeImages);
23
-
24
- // Gemini TTS Tool
25
- // server.addTool(geminiSingleSpeakerTts);
22
+ async function main() {
23
+ console.log("🚀 Initializing Kalaasetu MCP Server...");
26
24
 
27
- // Gemini Video Analysis Tool
28
- // server.addTool(geminiAnalyzeVideos);
25
+ // 1. Sync Remote Configs
26
+ await Promise.all([syncFalConfig(), syncPromptEnhancerConfigs()]);
29
27
 
30
- // YouTube Analyzer Tool
31
- // server.addTool(analyzeYoutubeVideo);
28
+ // 2. Add Gemini Tools
29
+ server.addTool(geminiTextToImage);
30
+ server.addTool(geminiEditImage);
31
+ server.addTool(imageToVideo);
32
32
 
33
- // Vertex AI Image-to-Video Tool
34
- server.addTool(imageToVideo);
33
+ // 3. Add Discovery Tools
34
+ server.addTool(falListPresets);
35
+ server.addTool(falGetPresetDetails);
36
+ server.addTool(falUploadFile);
35
37
 
36
- // Perplexity Search Tools
37
- // server.addTool(perplexityImages);
38
- // server.addTool(perplexityVideos);
38
+ // 4. Register Dynamic FAL AI Tools
39
+ // These are now based on potentially synced remote config
40
+ const falTools = createAllFalTools();
41
+ for (const tool of falTools) {
42
+ server.addTool(tool);
43
+ }
39
44
 
40
- // Discovery and Utility Tools
41
- server.addTool(falListPresets);
42
- server.addTool(falGetPresetDetails);
43
- server.addTool(falUploadFile);
45
+ // 5. Add Status Tool
46
+ server.addTool(getGenerationStatus);
44
47
 
45
- // Dynamic FAL AI Tools - each preset becomes a separate tool (prefixed with fal_)
46
- const falTools = createAllFalTools();
47
- for (const tool of falTools) {
48
- server.addTool(tool);
48
+ console.log("✅ Starting server transport...");
49
+ server.start({
50
+ transportType: "stdio",
51
+ });
49
52
  }
50
53
 
51
- // Unified Status Tool (works with both FAL and Vertex AI)
52
- server.addTool(getGenerationStatus);
53
-
54
- server.start({
55
- transportType: "stdio",
54
+ main().catch((err) => {
55
+ console.error("❌ Failed to start server:", err);
56
+ process.exit(1);
56
57
  });
@@ -129,58 +129,32 @@ export const DEFAULT_PRESETS: FalPresetConfig[] = [
129
129
  },
130
130
  ];
131
131
 
132
- /**
133
- * Load the FAL configuration from a JSON file.
134
- * Defaults to DEFAULT_PRESETS if no path is provided or if the file cannot be read.
135
- */
136
- export function loadFalConfig(): FalConfig {
137
- let configPath = process.env.FAL_CONFIG_JSON_PATH;
138
-
139
- // Fallback to default file search if no env var
140
- if (!configPath) {
141
- const potentialPaths = [
142
- "fal-config.json", // in CWD
143
- path.join(__dirname, "../../fal-config.json"), // from src/tools/fal in source
144
- path.join(__dirname, "../../../fal-config.json"), // from dist/tools/fal in build
145
- ];
146
-
147
- for (const p of potentialPaths) {
148
- if (fs.existsSync(p)) {
149
- configPath = p;
150
- break;
151
- }
152
- }
153
- }
154
-
155
- if (!configPath) {
156
- console.error(
157
- "FAL_CONFIG_JSON_PATH not set and no fal-config.json found. Using internal default presets."
158
- );
159
- return { presets: DEFAULT_PRESETS };
160
- }
132
+ import { syncRemoteConfig } from "../../utils/remote-sync";
161
133
 
162
- try {
163
- const absolutePath = path.resolve(configPath);
134
+ let currentConfig: FalConfig = { presets: DEFAULT_PRESETS };
164
135
 
165
- if (!fs.existsSync(absolutePath)) {
166
- console.error(
167
- `FAL config file not found at ${absolutePath}. Using default presets.`
168
- );
169
- return { presets: DEFAULT_PRESETS };
170
- }
171
-
172
- const fileContent = fs.readFileSync(absolutePath, "utf-8");
173
- const config = JSON.parse(fileContent) as FalConfig;
174
-
175
- if (!config.presets || !Array.isArray(config.presets)) {
176
- throw new Error("Invalid FAL config: 'presets' must be an array.");
177
- }
136
+ /**
137
+ * Syncs the FAL configuration with the remote server.
138
+ */
139
+ export async function syncFalConfig(): Promise<FalConfig> {
140
+ currentConfig = await syncRemoteConfig<FalConfig>({
141
+ name: "fal-config",
142
+ remoteUrl: "https://config.mixio.pro/mcp/fal/config.json",
143
+ envVar: "FAL_CONFIG_JSON_PATH",
144
+ fallback: { presets: DEFAULT_PRESETS },
145
+ validate: (data: any): data is FalConfig => {
146
+ return data && Array.isArray(data.presets);
147
+ },
148
+ });
149
+ return currentConfig;
150
+ }
178
151
 
179
- return config;
180
- } catch (error: any) {
181
- console.error(`Error loading FAL config: ${error.message}`);
182
- return { presets: DEFAULT_PRESETS };
183
- }
152
+ /**
153
+ * Load the FAL configuration.
154
+ * Returns the currently synced config or internal defaults.
155
+ */
156
+ export function loadFalConfig(): FalConfig {
157
+ return currentConfig;
184
158
  }
185
159
 
186
160
  /**
@@ -126,7 +126,38 @@ export const VIDEO_PRESETS: Record<string, PromptEnhancerConfig> = {
126
126
  styleGuide:
127
127
  "cinematic composition, present tense, flowing narrative, clear camera language, atmospheric lighting, emotive expressions through physical cues",
128
128
  negativeElements:
129
- "text, logos, signage, brand names, complex physics, chaotic motion, jumping, juggling, conflicting light sources, overloaded scene, too many characters, internal emotional states without visual cues",
129
+ "text, logos, signage, brand names, complex physics, chaotic motion, jumping, juggling, conflicting light sources, overloaded scene, too many characters, internal emotional states without visual cues, high-frequency patterns, brick walls, mesh, micro-check fabrics, moiré",
130
+ guidelines:
131
+ "LTX-2 requires a narrative, screenplay-like approach. 1. Establish the shot (cinematography, scale, focal length). 2. Set the scene (lighting, atmosphere, textures). 3. Describe subject and action as a natural sequence flowing from start to end. 4. Specify camera movements relative to the subject. Describe what happens after the movement for coherence.",
132
+ dos: [
133
+ "Write in single flowing paragraphs (4-8 sentences)",
134
+ "Use present tense for all actions (e.g., 'glides', 'turns')",
135
+ "Describe cinematography first to anchor the frame",
136
+ "Express emotion through physical cues (e.g., 'furrows brow', 'eyes widen')",
137
+ "Match detail level to shot scale (close-ups need more detail)",
138
+ "Use lens and shutter language (e.g., '50mm', '180° shutter equivalent')",
139
+ "Describe audio as SFX or spoken dialogue in quotation marks",
140
+ ],
141
+ donts: [
142
+ "Avoid 'vibe dumping' (e.g., 'epic amazing 4K')",
143
+ "Don't use internal emotional labels (e.g., 'feels sad')",
144
+ "Avoid high-frequency patterns that cause moiré (bricks, mesh)",
145
+ "Don't request abrupt reframing or chaotic handheld motion",
146
+ "Avoid complex physics or multiple interacting subjects",
147
+ "No readable text or logos",
148
+ ],
149
+ examples: [
150
+ {
151
+ input: "A girl puppet singing in the rain",
152
+ output:
153
+ "A close-up of a cheerful girl puppet with curly auburn yarn hair and wide button eyes, holding a small red umbrella above her head. Rain falls gently around her. She looks upward and begins to sing with joy in English: 'It's raining, it's raining, I love it when its raining.' Her fabric mouth opening and closing to a melodic tune. Her hands grip the umbrella handle as she sways slightly from side to side in rhythm. The camera holds steady as the rain sparkles against the soft lighting. Her eyes blink occasionally as she sings.",
154
+ },
155
+ {
156
+ input: "A woman exploring an abandoned town",
157
+ output:
158
+ "A cinematic medium-wide shot of an abandoned town street at dawn with light mist clinging to the cracked pavement. An explorer in a leather satchel and messy brown hair walks slowly along the empty street. The camera begins slightly behind her, then performs a smooth tracking shot as she turns her head to look at the boarded-up windows. The camera pulls out as she continues walking, revealing the scale of the quiet, empty town. Soft primary colors and Kodak 2383 film look.",
159
+ },
160
+ ],
130
161
  },
131
162
 
132
163
  /**
@@ -223,20 +254,80 @@ export const VIDEO_PRESETS: Record<string, PromptEnhancerConfig> = {
223
254
  negativeElements:
224
255
  "wide shot, distant, cold lighting, static expression, fast movement",
225
256
  },
257
+ /**
258
+ * Google Veo specific preset based on official prompting guidelines.
259
+ * Focuses on subject, action, context, and cinematography.
260
+ */
261
+ veo: {
262
+ prefix: "A cinematic video.",
263
+ styleGuide:
264
+ "high-fidelity, fluid motion, accurate physics, complex lighting, detailed textures, consistent subjects",
265
+ negativeElements:
266
+ "warped limbs, flickering artifacts, low resolution, jumping frames, blurry faces, text, logo",
267
+ guidelines:
268
+ "Veo excels at detailed cinematography. Follow the structure: 1. Subject (Specific) 2. Action (Precise) 3. Context (Environment/Lighting) 4. Cinematography (Movement/Angle). Optionally add SFX: descriptions at the end.",
269
+ dos: [
270
+ "Use 'cinematic' and 'photorealistic' for high quality",
271
+ "Clearly define camera movements like 'arc shot' or 'dolly zoom'",
272
+ "Add specific lighting like 'golden hour' or 'neon glow'",
273
+ "Describe audio using 'SFX:' prefix if needed",
274
+ "Use (no subtitles) if you want to avoid text overlays",
275
+ ],
276
+ donts: [
277
+ "Don't be vague about the setting",
278
+ "Avoid repetitive words",
279
+ "Don't overload the frame with too many subjects",
280
+ "Minimize the use of negative words like 'no' within the prompt (use the negative elements section instead)",
281
+ ],
282
+ examples: [
283
+ {
284
+ input: "A cowboy in a desert",
285
+ output:
286
+ "A cinematic medium shot of a rugged cowboy in a weathered leather hat standing in a vast, sun-scorched desert. The camera performs a slow 180-degree arc shot around him as dust swirls at his feet. SFX: The howling wind and the distant clink of spurs.",
287
+ },
288
+ {
289
+ input: "A futuristic city",
290
+ output:
291
+ "A wide aerial tracking shot over a sprawling neon-lit futuristic city at night. Rain slicks the metallic surfaces, reflecting the vibrant purple and teal lights. Flying vehicles navigate between towering skyscrapers with seamless, fluid motion. SFX: The ambient hum of a high-tech metropolis.",
292
+ },
293
+ ],
294
+ },
226
295
  };
227
296
 
228
- // =============================================================================
229
- // COMBINED PRESETS DICTIONARY
230
- // =============================================================================
297
+ import { syncRemoteConfig } from "./remote-sync";
231
298
 
232
299
  /**
233
- * All available prompt enhancer presets.
300
+ * Dynamic registry for prompt enhancer presets.
234
301
  */
235
- export const PROMPT_ENHANCER_PRESETS: Record<string, PromptEnhancerConfig> = {
302
+ export let PROMPT_ENHANCER_PRESETS: Record<string, PromptEnhancerConfig> = {
236
303
  ...IMAGE_PRESETS,
237
304
  ...VIDEO_PRESETS,
238
305
  };
239
306
 
307
+ /**
308
+ * Syncs the prompt enhancer configurations with the remote server.
309
+ */
310
+ export async function syncPromptEnhancerConfigs(): Promise<void> {
311
+ const remoteConfig = await syncRemoteConfig<Record<string, PromptEnhancerConfig>>({
312
+ name: "prompt-enhancers",
313
+ remoteUrl: "https://config.mixio.pro/mcp/prompt-enhancers.json",
314
+ envVar: "PROMPT_ENHANCER_CONFIG_PATH",
315
+ fallback: {},
316
+ validate: (data: any): data is Record<string, PromptEnhancerConfig> => {
317
+ return data && typeof data === "object" && !Array.isArray(data);
318
+ },
319
+ });
320
+
321
+ // Merge remote settings into the defaults
322
+ PROMPT_ENHANCER_PRESETS = {
323
+ ...IMAGE_PRESETS,
324
+ ...VIDEO_PRESETS,
325
+ ...remoteConfig,
326
+ };
327
+
328
+ console.log(`[Sync] Prompt Enhancer Presets updated. Total: ${Object.keys(PROMPT_ENHANCER_PRESETS).length}`);
329
+ }
330
+
240
331
  // =============================================================================
241
332
  // HELPER FUNCTIONS
242
333
  // =============================================================================
@@ -24,6 +24,16 @@ export interface PromptEnhancerConfig {
24
24
  * Example: "A cinematic shot of {prompt}, dramatic lighting"
25
25
  */
26
26
  wrapTemplate?: string;
27
+ /** Detailed system instructions for LLM-based enhancement */
28
+ llmSystemPrompt?: string;
29
+ /** Do's for the prompt (e.g. ['Use active voice', 'Describe motion']) */
30
+ dos?: string[];
31
+ /** Don'ts for the prompt (e.g. ['Avoid jargon', 'No technical camera terms']) */
32
+ donts?: string[];
33
+ /** Generic guidelines or reference text */
34
+ guidelines?: string;
35
+ /** Few-shot examples for the LLM { input: string, output: string } */
36
+ examples?: Array<{ input: string; output: string }>;
27
37
  }
28
38
 
29
39
  /**
@@ -177,6 +187,73 @@ export class PromptEnhancer {
177
187
  );
178
188
  }
179
189
 
190
+ /**
191
+ * Generates a comprehensive system prompt for an LLM to follow when enhancing a prompt.
192
+ */
193
+ getSystemPrompt(): string {
194
+ const lines: string[] = [];
195
+
196
+ if (this.config.llmSystemPrompt) {
197
+ lines.push(this.config.llmSystemPrompt);
198
+ } else {
199
+ lines.push(
200
+ "You are an expert prompt engineer specializing in AI image and video generation."
201
+ );
202
+ lines.push(
203
+ "Your task is to expand and enhance the user's input prompt into a high-quality, detailed description."
204
+ );
205
+ }
206
+
207
+ if (this.config.guidelines) {
208
+ lines.push("");
209
+ lines.push("### GUIDELINES");
210
+ lines.push(this.config.guidelines);
211
+ }
212
+
213
+ if (this.config.styleGuide) {
214
+ lines.push("");
215
+ lines.push("### STYLE AND QUALITY");
216
+ lines.push(`Incorporate these elements: ${this.config.styleGuide}`);
217
+ }
218
+
219
+ if (this.config.dos && this.config.dos.length > 0) {
220
+ lines.push("");
221
+ lines.push("### DO'S");
222
+ this.config.dos.forEach((item) => lines.push(`- ${item}`));
223
+ }
224
+
225
+ if (this.config.donts && this.config.donts.length > 0) {
226
+ lines.push("");
227
+ lines.push("### DON'TS");
228
+ this.config.donts.forEach((item) => lines.push(`- ${item}`));
229
+ }
230
+
231
+ if (this.config.negativeElements) {
232
+ lines.push("");
233
+ lines.push("### NEGATIVE PROMPT (AVOID)");
234
+ lines.push(this.config.negativeElements);
235
+ }
236
+
237
+ if (this.config.examples && this.config.examples.length > 0) {
238
+ lines.push("");
239
+ lines.push("### EXAMPLES");
240
+ this.config.examples.forEach((example, index) => {
241
+ lines.push(`Example ${index + 1}:`);
242
+ lines.push(`Input: ${example.input}`);
243
+ lines.push(`Output: ${example.output}`);
244
+ lines.push("");
245
+ });
246
+ }
247
+
248
+ lines.push("");
249
+ lines.push("### OUTPUT FORMAT");
250
+ lines.push(
251
+ "Return ONLY the enhanced prompt text. No explanations or conversational filler."
252
+ );
253
+
254
+ return lines.join("\n").trim();
255
+ }
256
+
180
257
  /**
181
258
  * Export the enhancer's configuration.
182
259
  */
@@ -0,0 +1,89 @@
1
+ import * as fs from "node:fs";
2
+ import * as path from "node:path";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+
5
+ const CACHE_DIR = path.join(process.cwd(), ".cache");
6
+
7
+ /**
8
+ * Options for remote configuration syncing.
9
+ */
10
+ interface SyncOptions<T> {
11
+ /** The name of the config (used for file naming in cache) */
12
+ name: string;
13
+ /** The remote URL to fetch from */
14
+ remoteUrl: string;
15
+ /** Environment variable that overrides the remote URL or providing a local path */
16
+ envVar?: string;
17
+ /** Internal default to use if both remote and cache fail */
18
+ fallback: T;
19
+ /** Optional validation function */
20
+ validate?: (data: any) => data is T;
21
+ }
22
+
23
+ /**
24
+ * Syncs a configuration from a remote source, local file, or environment variable.
25
+ * Implements a caching strategy to ensure availability even when the remote is down.
26
+ */
27
+ export async function syncRemoteConfig<T>(options: SyncOptions<T>): Promise<T> {
28
+ const { name, remoteUrl, envVar, fallback, validate } = options;
29
+ const cachePath = path.join(CACHE_DIR, `${name}.json`);
30
+
31
+ // 1. Check for Environment Variable Override (Local Path)
32
+ if (envVar && process.env[envVar]) {
33
+ const localPath = process.env[envVar]!;
34
+ try {
35
+ if (fs.existsSync(localPath)) {
36
+ const content = await readFile(localPath, "utf-8");
37
+ const data = JSON.parse(content);
38
+ if (!validate || validate(data)) {
39
+ console.log(`[Sync] Using local override from ${envVar}: ${localPath}`);
40
+ return data as T;
41
+ }
42
+ }
43
+ } catch (e) {
44
+ console.warn(`[Sync] Failed to read local override from ${envVar}: ${e}`);
45
+ }
46
+ }
47
+
48
+ // 2. Attempt to Fetch from Remote
49
+ try {
50
+ console.log(`[Sync] Attempting to fetch ${name} from ${remoteUrl}...`);
51
+ const response = await fetch(remoteUrl, {
52
+ signal: AbortSignal.timeout(5000), // 5s timeout
53
+ });
54
+
55
+ if (response.ok) {
56
+ const data = await response.json();
57
+ if (!validate || validate(data)) {
58
+ // Cache the successful fetch
59
+ await mkdir(CACHE_DIR, { recursive: true });
60
+ await writeFile(cachePath, JSON.stringify(data, null, 2));
61
+ console.log(`[Sync] Successfully updated ${name} and cached at ${cachePath}`);
62
+ return data as T;
63
+ }
64
+ console.warn(`[Sync] Remote data for ${name} failed validation.`);
65
+ } else {
66
+ console.warn(`[Sync] Remote fetch for ${name} failed with status: ${response.status}`);
67
+ }
68
+ } catch (e) {
69
+ console.warn(`[Sync] Error fetching ${name} from remote: ${e}`);
70
+ }
71
+
72
+ // 3. Fallback to Cache
73
+ try {
74
+ if (fs.existsSync(cachePath)) {
75
+ const cacheContent = await readFile(cachePath, "utf-8");
76
+ const data = JSON.parse(cacheContent);
77
+ if (data && (!validate || validate(data))) {
78
+ console.log(`[Sync] Using cached version of ${name} from ${cachePath}`);
79
+ return data as T;
80
+ }
81
+ }
82
+ } catch (e) {
83
+ console.warn(`[Sync] Failed to read cache for ${name}: ${e}`);
84
+ }
85
+
86
+ // 4. Final Fallback to Internal Defaults
87
+ console.warn(`[Sync] All sync methods failed for ${name}. Using internal defaults.`);
88
+ return fallback;
89
+ }