@mixio-pro/kalaasetu-mcp 1.0.6 → 1.0.7
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/README.md +6 -2
- package/package.json +3 -1
- package/src/storage/gcs.ts +116 -0
- package/src/storage/index.ts +11 -8
- package/src/storage/interface.ts +1 -1
- package/src/storage/local.ts +0 -2
- package/src/tools/gemini.ts +12 -11
- package/src/tools/image-to-video.ts +4 -7
- package/src/storage/payload.ts +0 -46
package/README.md
CHANGED
|
@@ -68,7 +68,9 @@ Add to your Cursor settings (`~/.cursor/config.json` or via Settings → MCP):
|
|
|
68
68
|
"env": {
|
|
69
69
|
"GEMINI_API_KEY": "your-gemini-api-key",
|
|
70
70
|
"FAL_KEY": "your-fal-api-key",
|
|
71
|
-
"PERPLEXITY_API_KEY": "your-perplexity-api-key"
|
|
71
|
+
"PERPLEXITY_API_KEY": "your-perplexity-api-key",
|
|
72
|
+
"STORAGE_PROVIDER":"gcs",
|
|
73
|
+
"GCS_BUCKET":"your-gcs-bucket-name"
|
|
72
74
|
}
|
|
73
75
|
}
|
|
74
76
|
}
|
|
@@ -88,7 +90,9 @@ Add to your OpenCode MCP configuration:
|
|
|
88
90
|
"environment": {
|
|
89
91
|
"GEMINI_API_KEY": "your-gemini-api-key",
|
|
90
92
|
"FAL_KEY": "your-fal-api-key",
|
|
91
|
-
"PERPLEXITY_API_KEY": "your-perplexity-api-key"
|
|
93
|
+
"PERPLEXITY_API_KEY": "your-perplexity-api-key",
|
|
94
|
+
"STORAGE_PROVIDER":"gcs",
|
|
95
|
+
"GCS_BUCKET":"your-bucket-name"
|
|
92
96
|
}
|
|
93
97
|
}
|
|
94
98
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mixio-pro/kalaasetu-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
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",
|
|
@@ -49,8 +49,10 @@
|
|
|
49
49
|
"dependencies": {
|
|
50
50
|
"@fal-ai/client": "^1.7.2",
|
|
51
51
|
"@google/genai": "^1.28.0",
|
|
52
|
+
"@types/node": "^24.10.1",
|
|
52
53
|
"@types/wav": "^1.0.4",
|
|
53
54
|
"fastmcp": "^3.22.0",
|
|
55
|
+
"form-data": "^4.0.5",
|
|
54
56
|
"google-auth-library": "^10.5.0",
|
|
55
57
|
"wav": "^1.0.2",
|
|
56
58
|
"zod": "^4.1.12"
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { GoogleAuth } from "google-auth-library";
|
|
2
|
+
import type { StorageProvider } from "./interface";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
export class GCSStorageProvider implements StorageProvider {
|
|
6
|
+
private bucket: string;
|
|
7
|
+
private auth: GoogleAuth;
|
|
8
|
+
|
|
9
|
+
constructor(bucket: string) {
|
|
10
|
+
this.bucket = bucket;
|
|
11
|
+
this.auth = new GoogleAuth({
|
|
12
|
+
scopes: ["https://www.googleapis.com/auth/cloud-platform"],
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async init(): Promise<void> {
|
|
17
|
+
console.log(
|
|
18
|
+
`Initializing GCS Storage Provider with bucket: ${this.bucket}`
|
|
19
|
+
);
|
|
20
|
+
// Verify we can get credentials
|
|
21
|
+
try {
|
|
22
|
+
await this.auth.getClient();
|
|
23
|
+
} catch (error) {
|
|
24
|
+
console.warn(`Warning: Could not initialize GCS client: ${error}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
private async getAccessToken(): Promise<string> {
|
|
29
|
+
const client = await this.auth.getClient();
|
|
30
|
+
const token = await client.getAccessToken();
|
|
31
|
+
if (!token.token) {
|
|
32
|
+
throw new Error("Failed to get GCS access token");
|
|
33
|
+
}
|
|
34
|
+
return token.token;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async readFile(filePath: string): Promise<Buffer> {
|
|
38
|
+
const objectName = path.basename(filePath);
|
|
39
|
+
const url = `https://storage.googleapis.com/storage/v1/b/${
|
|
40
|
+
this.bucket
|
|
41
|
+
}/o/${encodeURIComponent(objectName)}?alt=media`;
|
|
42
|
+
|
|
43
|
+
const token = await this.getAccessToken();
|
|
44
|
+
const response = await fetch(url, {
|
|
45
|
+
headers: {
|
|
46
|
+
Authorization: `Bearer ${token}`,
|
|
47
|
+
},
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
if (!response.ok) {
|
|
51
|
+
throw new Error(
|
|
52
|
+
`Failed to read file from GCS: ${response.status} ${response.statusText}`
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
57
|
+
return Buffer.from(arrayBuffer);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async writeFile(filePath: string, data: Buffer | string): Promise<string> {
|
|
61
|
+
const objectName = path.basename(filePath);
|
|
62
|
+
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data);
|
|
63
|
+
|
|
64
|
+
// Upload using JSON API
|
|
65
|
+
const url = `https://storage.googleapis.com/upload/storage/v1/b/${
|
|
66
|
+
this.bucket
|
|
67
|
+
}/o?uploadType=media&name=${encodeURIComponent(objectName)}`;
|
|
68
|
+
|
|
69
|
+
const token = await this.getAccessToken();
|
|
70
|
+
const response = await fetch(url, {
|
|
71
|
+
method: "POST",
|
|
72
|
+
headers: {
|
|
73
|
+
Authorization: `Bearer ${token}`,
|
|
74
|
+
"Content-Type": "application/octet-stream",
|
|
75
|
+
"Content-Length": buffer.length.toString(),
|
|
76
|
+
},
|
|
77
|
+
body: buffer,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
const errorText = await response.text();
|
|
82
|
+
throw new Error(
|
|
83
|
+
`Failed to upload to GCS: ${response.status} ${errorText}`
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Return public URL
|
|
88
|
+
return `https://storage.googleapis.com/${this.bucket}/${objectName}`;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async exists(filePath: string): Promise<boolean> {
|
|
92
|
+
try {
|
|
93
|
+
const objectName = path.basename(filePath);
|
|
94
|
+
const url = `https://storage.googleapis.com/storage/v1/b/${
|
|
95
|
+
this.bucket
|
|
96
|
+
}/o/${encodeURIComponent(objectName)}`;
|
|
97
|
+
|
|
98
|
+
const token = await this.getAccessToken();
|
|
99
|
+
const response = await fetch(url, {
|
|
100
|
+
method: "GET",
|
|
101
|
+
headers: {
|
|
102
|
+
Authorization: `Bearer ${token}`,
|
|
103
|
+
},
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
return response.ok;
|
|
107
|
+
} catch {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async getPublicUrl(filePath: string): Promise<string> {
|
|
113
|
+
const objectName = path.basename(filePath);
|
|
114
|
+
return `https://storage.googleapis.com/${this.bucket}/${objectName}`;
|
|
115
|
+
}
|
|
116
|
+
}
|
package/src/storage/index.ts
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
1
|
import type { StorageProvider } from "./interface";
|
|
2
2
|
import { LocalStorageProvider } from "./local";
|
|
3
|
-
import {
|
|
3
|
+
import { GCSStorageProvider } from "./gcs";
|
|
4
4
|
|
|
5
5
|
let storageInstance: StorageProvider | null = null;
|
|
6
6
|
|
|
7
7
|
export function getStorage(): StorageProvider {
|
|
8
8
|
if (!storageInstance) {
|
|
9
9
|
const type = process.env.STORAGE_PROVIDER || "local";
|
|
10
|
-
console.error(`Initializing storage provider: ${type}`);
|
|
10
|
+
console.error(`Initializing storage provider: ${type}`);
|
|
11
11
|
|
|
12
|
-
if (type === "
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
if (type === "gcs") {
|
|
13
|
+
const bucket = process.env.GCS_BUCKET;
|
|
14
|
+
|
|
15
|
+
if (!bucket) {
|
|
16
|
+
throw new Error("GCS_BUCKET is required when using gcs storage");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
storageInstance = new GCSStorageProvider(bucket);
|
|
17
20
|
} else {
|
|
18
21
|
storageInstance = new LocalStorageProvider(process.cwd());
|
|
19
22
|
}
|
|
20
23
|
|
|
21
|
-
// Initialize async
|
|
24
|
+
// Initialize async
|
|
22
25
|
storageInstance
|
|
23
26
|
.init()
|
|
24
27
|
.catch((err) => console.error("Failed to init storage:", err));
|
package/src/storage/interface.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export interface StorageProvider {
|
|
2
2
|
init(): Promise<void>;
|
|
3
3
|
readFile(path: string): Promise<Buffer>;
|
|
4
|
-
writeFile(path: string, data: Buffer | string): Promise<string>; // Returns
|
|
4
|
+
writeFile(path: string, data: Buffer | string): Promise<string>; // Returns public URL
|
|
5
5
|
exists(path: string): Promise<boolean>;
|
|
6
6
|
getPublicUrl(path: string): Promise<string>;
|
|
7
7
|
}
|
package/src/storage/local.ts
CHANGED
|
@@ -14,7 +14,6 @@ export class LocalStorageProvider implements StorageProvider {
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
async readFile(filePath: string): Promise<Buffer> {
|
|
17
|
-
// Handle absolute paths by checking if it starts with basePath or just use it if it exists
|
|
18
17
|
let fullPath = filePath;
|
|
19
18
|
if (!path.isAbsolute(filePath)) {
|
|
20
19
|
fullPath = path.resolve(this.basePath, filePath);
|
|
@@ -45,7 +44,6 @@ export class LocalStorageProvider implements StorageProvider {
|
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
async getPublicUrl(filePath: string): Promise<string> {
|
|
48
|
-
// For local, we just return the absolute path
|
|
49
47
|
let fullPath = filePath;
|
|
50
48
|
if (!path.isAbsolute(filePath)) {
|
|
51
49
|
fullPath = path.resolve(this.basePath, filePath);
|
package/src/tools/gemini.ts
CHANGED
|
@@ -164,7 +164,7 @@ async function processVideoInput(
|
|
|
164
164
|
}
|
|
165
165
|
|
|
166
166
|
export const geminiTextToImage = {
|
|
167
|
-
name: "
|
|
167
|
+
name: "generateImage",
|
|
168
168
|
description:
|
|
169
169
|
"Generate images from text prompts using Gemini 2.5 Flash Image model",
|
|
170
170
|
parameters: z.object({
|
|
@@ -203,11 +203,11 @@ export const geminiTextToImage = {
|
|
|
203
203
|
const imageData = part.inlineData.data;
|
|
204
204
|
if (args.output_path) {
|
|
205
205
|
const storage = getStorage();
|
|
206
|
-
await storage.writeFile(
|
|
206
|
+
const url = await storage.writeFile(
|
|
207
207
|
args.output_path,
|
|
208
208
|
Buffer.from(imageData, "base64")
|
|
209
209
|
);
|
|
210
|
-
result += `\nImage saved to: ${
|
|
210
|
+
result += `\nImage saved to: ${url}`;
|
|
211
211
|
} else {
|
|
212
212
|
result += `\nGenerated image (base64): ${imageData.substring(
|
|
213
213
|
0,
|
|
@@ -225,7 +225,7 @@ export const geminiTextToImage = {
|
|
|
225
225
|
};
|
|
226
226
|
|
|
227
227
|
export const geminiEditImage = {
|
|
228
|
-
name: "
|
|
228
|
+
name: "editImage",
|
|
229
229
|
description:
|
|
230
230
|
"Edit existing images with text instructions using Gemini 2.5 Flash Image Preview",
|
|
231
231
|
parameters: z.object({
|
|
@@ -270,11 +270,11 @@ export const geminiEditImage = {
|
|
|
270
270
|
const imageData = part.inlineData.data;
|
|
271
271
|
if (args.output_path) {
|
|
272
272
|
const storage = getStorage();
|
|
273
|
-
await storage.writeFile(
|
|
273
|
+
const url = await storage.writeFile(
|
|
274
274
|
args.output_path,
|
|
275
275
|
Buffer.from(imageData, "base64")
|
|
276
276
|
);
|
|
277
|
-
result += `\nEdited image saved to: ${
|
|
277
|
+
result += `\nEdited image saved to: ${url}`;
|
|
278
278
|
} else {
|
|
279
279
|
result += `\nEdited image (base64): ${imageData.substring(
|
|
280
280
|
0,
|
|
@@ -292,7 +292,7 @@ export const geminiEditImage = {
|
|
|
292
292
|
};
|
|
293
293
|
|
|
294
294
|
export const geminiAnalyzeImages = {
|
|
295
|
-
name: "
|
|
295
|
+
name: "analyzeImages",
|
|
296
296
|
description:
|
|
297
297
|
"Analyze and describe images using Gemini 2.5 Pro with advanced multimodal understanding",
|
|
298
298
|
parameters: z.object({
|
|
@@ -358,7 +358,7 @@ export const geminiAnalyzeImages = {
|
|
|
358
358
|
};
|
|
359
359
|
|
|
360
360
|
export const geminiSingleSpeakerTts = {
|
|
361
|
-
name: "
|
|
361
|
+
name: "generateSpeech",
|
|
362
362
|
description:
|
|
363
363
|
"Generate single speaker voice audio from text using Gemini 2.5 Pro Preview TTS model",
|
|
364
364
|
parameters: z.object({
|
|
@@ -407,9 +407,10 @@ export const geminiSingleSpeakerTts = {
|
|
|
407
407
|
// Generate output filename if not provided
|
|
408
408
|
const outputPath = args.output_path || `voice_output_${Date.now()}.wav`;
|
|
409
409
|
|
|
410
|
-
|
|
410
|
+
const storage = getStorage();
|
|
411
|
+
const url = await storage.writeFile(outputPath, audioBuffer);
|
|
411
412
|
|
|
412
|
-
return `Audio generated successfully: ${
|
|
413
|
+
return `Audio generated successfully: ${url}`;
|
|
413
414
|
} catch (error: any) {
|
|
414
415
|
throw new Error(`Voice generation failed: ${error.message}`);
|
|
415
416
|
}
|
|
@@ -417,7 +418,7 @@ export const geminiSingleSpeakerTts = {
|
|
|
417
418
|
};
|
|
418
419
|
|
|
419
420
|
export const geminiAnalyzeVideos = {
|
|
420
|
-
name: "
|
|
421
|
+
name: "analyzeVideos",
|
|
421
422
|
description:
|
|
422
423
|
"Analyze and understand video content using Gemini 2.5 Flash model. Intelligently handles YouTube URLs and local videos (files <20MB processed inline, ≥20MB uploaded via File API). Supports timestamp queries, clipping, and custom frame rates with default 5 FPS for local videos to optimize processing.",
|
|
423
424
|
parameters: z.object({
|
|
@@ -64,7 +64,7 @@ async function fileToBase64(
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
export const imageToVideo = {
|
|
67
|
-
name: "
|
|
67
|
+
name: "generateVideoi2v",
|
|
68
68
|
description:
|
|
69
69
|
"Generate videos from an image as starting first frame using Vertex Veo models (predictLongRunning + fetchPredictOperation).",
|
|
70
70
|
parameters: z.object({
|
|
@@ -291,14 +291,11 @@ export const imageToVideo = {
|
|
|
291
291
|
? args.output_path
|
|
292
292
|
: args.output_path.replace(/\.mp4$/i, `_${index}.mp4`)
|
|
293
293
|
: `video_output_${Date.now()}${index === 0 ? "" : "_" + index}.mp4`;
|
|
294
|
-
// For storage provider, we use the path as is (relative or absolute)
|
|
295
|
-
// If using LocalStorage, it handles resolving.
|
|
296
|
-
// If using Payload, it handles the key.
|
|
297
294
|
|
|
298
295
|
const buf = Buffer.from(base64, "base64");
|
|
299
296
|
const storage = getStorage();
|
|
300
|
-
await storage.writeFile(filePath, buf);
|
|
301
|
-
outputs.push(
|
|
297
|
+
const url = await storage.writeFile(filePath, buf);
|
|
298
|
+
outputs.push(url);
|
|
302
299
|
};
|
|
303
300
|
|
|
304
301
|
if (Array.isArray(resp?.videos) && resp.videos.length > 0) {
|
|
@@ -310,7 +307,7 @@ export const imageToVideo = {
|
|
|
310
307
|
}
|
|
311
308
|
}
|
|
312
309
|
if (outputs.length > 0) {
|
|
313
|
-
return `Video(s) saved: ${outputs.join(", ")}`;
|
|
310
|
+
return `Video(s) saved to: ${outputs.join(", ")}`;
|
|
314
311
|
}
|
|
315
312
|
|
|
316
313
|
// If nothing saved, return a concise summary plus head/tail snippets of JSON
|
package/src/storage/payload.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import type { StorageProvider } from "./interface";
|
|
2
|
-
|
|
3
|
-
export class PayloadStorageProvider implements StorageProvider {
|
|
4
|
-
private apiUrl: string;
|
|
5
|
-
private apiKey: string;
|
|
6
|
-
private collection: string;
|
|
7
|
-
|
|
8
|
-
constructor(apiUrl: string, apiKey: string, collection: string = "media") {
|
|
9
|
-
this.apiUrl = apiUrl;
|
|
10
|
-
this.apiKey = apiKey;
|
|
11
|
-
this.collection = collection;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
async init(): Promise<void> {
|
|
15
|
-
console.log("Initializing Payload Storage Provider...");
|
|
16
|
-
// TODO: Verify connection to Payload CMS
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
async readFile(filePath: string): Promise<Buffer> {
|
|
20
|
-
// TODO: Implement fetching file from Payload CMS
|
|
21
|
-
// 1. Search for file by filename or ID
|
|
22
|
-
// 2. Download the file buffer
|
|
23
|
-
console.log(`[Payload] Reading file: ${filePath}`);
|
|
24
|
-
throw new Error("PayloadStorageProvider.readFile not implemented yet.");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async writeFile(filePath: string, data: Buffer | string): Promise<string> {
|
|
28
|
-
// TODO: Implement uploading file to Payload CMS
|
|
29
|
-
// 1. Create FormData
|
|
30
|
-
// 2. POST to /api/{collection}
|
|
31
|
-
console.log(`[Payload] Writing file: ${filePath}`);
|
|
32
|
-
throw new Error("PayloadStorageProvider.writeFile not implemented yet.");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async exists(filePath: string): Promise<boolean> {
|
|
36
|
-
// TODO: Check if file exists in Payload
|
|
37
|
-
console.log(`[Payload] Checking existence: ${filePath}`);
|
|
38
|
-
return false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async getPublicUrl(filePath: string): Promise<string> {
|
|
42
|
-
// TODO: Return the public URL of the file in Payload
|
|
43
|
-
console.log(`[Payload] Getting public URL: ${filePath}`);
|
|
44
|
-
return `${this.apiUrl}/${this.collection}/${filePath}`;
|
|
45
|
-
}
|
|
46
|
-
}
|