@mixio-pro/kalaasetu-mcp 1.0.13 → 1.0.15

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": "1.0.13",
3
+ "version": "1.0.15",
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",
@@ -1,6 +1,10 @@
1
1
  import { GoogleAuth } from "google-auth-library";
2
2
  import type { StorageProvider } from "./interface";
3
3
  import * as path from "path";
4
+ import {
5
+ getGoogleAuthClient,
6
+ getGoogleAccessToken,
7
+ } from "../utils/google-auth";
4
8
 
5
9
  export class GCSStorageProvider implements StorageProvider {
6
10
  private bucket: string;
@@ -8,7 +12,7 @@ export class GCSStorageProvider implements StorageProvider {
8
12
 
9
13
  constructor(bucket: string) {
10
14
  this.bucket = bucket;
11
- this.auth = new GoogleAuth({
15
+ this.auth = getGoogleAuthClient({
12
16
  scopes: ["https://www.googleapis.com/auth/cloud-platform"],
13
17
  });
14
18
  }
@@ -26,12 +30,12 @@ export class GCSStorageProvider implements StorageProvider {
26
30
  }
27
31
 
28
32
  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;
33
+ // Use the centralized helper which includes gcloud fallback if needed,
34
+ // although GCSStorageProvider primarily relies on the initialized GoogleAuth.
35
+ // However, if we want to support the same fallback logic as tools:
36
+ return getGoogleAccessToken({
37
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
38
+ });
35
39
  }
36
40
 
37
41
  async readFile(filePath: string): Promise<Buffer> {
@@ -167,18 +167,20 @@ async function processVideoInput(
167
167
  } else {
168
168
  // Local file processing - use File Upload API
169
169
  const storage = getStorage();
170
-
170
+
171
171
  // Check if file exists
172
172
  const exists = await storage.exists(input);
173
173
  if (!exists) {
174
174
  // Try to provide more helpful error information
175
175
  const isAbsolute = path.isAbsolute(input);
176
- const resolvedPath = isAbsolute ? input : path.resolve(process.cwd(), input);
176
+ const resolvedPath = isAbsolute
177
+ ? input
178
+ : path.resolve(process.cwd(), input);
177
179
  throw new Error(
178
180
  `Video file not found: ${input}\n` +
179
- `Resolved path: ${resolvedPath}\n` +
180
- `Is absolute: ${isAbsolute}\n` +
181
- `CWD: ${process.cwd()}`
181
+ `Resolved path: ${resolvedPath}\n` +
182
+ `Is absolute: ${isAbsolute}\n` +
183
+ `CWD: ${process.cwd()}`
182
184
  );
183
185
  }
184
186
 
@@ -192,7 +194,7 @@ async function processVideoInput(
192
194
  export const geminiTextToImage = {
193
195
  name: "generateImage",
194
196
  description:
195
- "Generate images from text prompts using Gemini image models with optional reference images",
197
+ "Generate images from text prompts using Gemini image models with optional reference images. Returns the URL of the generated image.",
196
198
  parameters: z.object({
197
199
  prompt: z.string().describe("Text description of the image to generate"),
198
200
  aspect_ratio: z
@@ -202,7 +204,9 @@ export const geminiTextToImage = {
202
204
  output_path: z
203
205
  .string()
204
206
  .optional()
205
- .describe("File path to save the generated image"),
207
+ .describe(
208
+ "File path to save the generated image (optional, auto-generated if not provided)"
209
+ ),
206
210
  reference_images: z
207
211
  .array(z.string())
208
212
  .optional()
@@ -243,31 +247,35 @@ export const geminiTextToImage = {
243
247
  textResponse += part.text;
244
248
  } else if (part.inlineData?.data) {
245
249
  const imageData = part.inlineData.data;
246
- if (args.output_path) {
247
- const storage = getStorage();
248
- const url = await storage.writeFile(
249
- args.output_path,
250
- Buffer.from(imageData, "base64")
251
- );
252
- images.push({
253
- url,
254
- filename: args.output_path,
255
- mimeType: "image/png",
256
- });
257
- }
250
+ // Always save the image - use provided path or generate one
251
+ const outputPath =
252
+ args.output_path ||
253
+ generateTimestampedFilename("generated_image.png");
254
+ const storage = getStorage();
255
+ const url = await storage.writeFile(
256
+ outputPath,
257
+ Buffer.from(imageData, "base64")
258
+ );
259
+ images.push({
260
+ url,
261
+ filename: outputPath,
262
+ mimeType: "image/png",
263
+ });
258
264
  }
259
265
  }
260
266
  }
261
267
 
262
268
  if (images.length > 0) {
269
+ // Return the URL directly for easy parsing
263
270
  return JSON.stringify({
271
+ url: images?.[0]?.url,
264
272
  images,
265
273
  message: textResponse || "Image generated successfully",
266
274
  });
267
275
  }
268
276
 
269
277
  return (
270
- textResponse || "Image generation completed but no response received"
278
+ textResponse || "Image generation completed but no image was produced"
271
279
  );
272
280
  } catch (error: any) {
273
281
  throw new Error(`Image generation failed: ${error.message}`);
@@ -465,7 +473,8 @@ export const geminiSingleSpeakerTts = {
465
473
  const audioBuffer = Buffer.from(data, "base64");
466
474
 
467
475
  // Use provided output path or generate default with timestamp
468
- const outputPath = args.output_path || generateTimestampedFilename("voice_output.wav");
476
+ const outputPath =
477
+ args.output_path || generateTimestampedFilename("voice_output.wav");
469
478
 
470
479
  const storage = getStorage();
471
480
  const url = await storage.writeFile(outputPath, audioBuffer);
@@ -5,50 +5,12 @@ import { z } from "zod";
5
5
  import { getStorage } from "../storage";
6
6
  import { generateTimestampedFilename } from "../utils/filename";
7
7
 
8
+ import { getGoogleAccessToken } from "../utils/google-auth";
9
+
8
10
  async function wait(ms: number): Promise<void> {
9
11
  return new Promise((resolve) => setTimeout(resolve, ms));
10
12
  }
11
13
 
12
- async function fetchAccessToken(): Promise<string> {
13
- try {
14
- const auth = new GoogleAuth({
15
- scopes: ["https://www.googleapis.com/auth/cloud-platform"],
16
- });
17
- const client = await auth.getClient();
18
- const token = await client.getAccessToken();
19
- if (!token || typeof token !== "string") {
20
- throw new Error("No token from GoogleAuth");
21
- }
22
- return token;
23
- } catch (e) {
24
- // Fallback to gcloud
25
- return await new Promise((resolve, reject) => {
26
- exec("gcloud auth print-access-token", (err, stdout, stderr) => {
27
- if (err) {
28
- reject(
29
- new Error(
30
- `Failed to fetch an access token (ADC and gcloud): ${
31
- stderr || err.message
32
- }`
33
- )
34
- );
35
- return;
36
- }
37
- const t = (stdout || "").trim();
38
- if (!t) {
39
- reject(
40
- new Error(
41
- "Failed to fetch an access token: empty token from gcloud"
42
- )
43
- );
44
- return;
45
- }
46
- resolve(t);
47
- });
48
- });
49
- }
50
- }
51
-
52
14
  async function fileToBase64(
53
15
  filePath: string
54
16
  ): Promise<{ data: string; mimeType: string }> {
@@ -191,7 +153,7 @@ export const imageToVideo = {
191
153
  durationSeconds = 8;
192
154
  }
193
155
 
194
- const token = await fetchAccessToken();
156
+ const token = await getGoogleAccessToken();
195
157
 
196
158
  const url = `https://${location}-aiplatform.googleapis.com/v1/projects/${projectId}/locations/${location}/publishers/google/models/${modelId}:predictLongRunning`;
197
159
 
@@ -0,0 +1,83 @@
1
+ import { GoogleAuth } from "google-auth-library";
2
+ import { exec } from "child_process";
3
+
4
+ export interface GoogleAuthOptions {
5
+ scopes?: string | string[];
6
+ }
7
+
8
+ /**
9
+ * Returns a GoogleAuth client, prioritizing GOOGLE_SERVICE_ACCOUNT_JSON env var.
10
+ */
11
+ export function getGoogleAuthClient(
12
+ options: GoogleAuthOptions = {}
13
+ ): GoogleAuth {
14
+ const opts: any = {
15
+ scopes: options.scopes || [
16
+ "https://www.googleapis.com/auth/cloud-platform",
17
+ ],
18
+ };
19
+
20
+ if (process.env.GOOGLE_SERVICE_ACCOUNT_JSON) {
21
+ try {
22
+ const credentials = JSON.parse(process.env.GOOGLE_SERVICE_ACCOUNT_JSON);
23
+ opts.credentials = credentials;
24
+ if (credentials.project_id) {
25
+ opts.projectId = credentials.project_id;
26
+ }
27
+ } catch (error) {
28
+ console.warn(
29
+ "Failed to parse GOOGLE_SERVICE_ACCOUNT_JSON, falling back to other methods:",
30
+ error
31
+ );
32
+ }
33
+ } else if (process.env.GOOGLE_SERVICE_ACCOUNT_JSON_PATH) {
34
+ // GoogleAuth natively supports keyFile/keyFilename
35
+ opts.keyFile = process.env.GOOGLE_SERVICE_ACCOUNT_JSON_PATH;
36
+ }
37
+
38
+ return new GoogleAuth(opts);
39
+ }
40
+
41
+ /**
42
+ * Returns an access token, trying GoogleAuth first (JSON > ADC), then gcloud CLI.
43
+ */
44
+ export async function getGoogleAccessToken(
45
+ options: GoogleAuthOptions = {}
46
+ ): Promise<string> {
47
+ try {
48
+ const auth = getGoogleAuthClient(options);
49
+ const client = await auth.getClient();
50
+ const token = await client.getAccessToken();
51
+ if (token.token) {
52
+ return token.token;
53
+ }
54
+ throw new Error("No token returned from GoogleAuth");
55
+ } catch (e) {
56
+ // Fallback to gcloud
57
+ console.warn("GoogleAuth failed, falling back to gcloud CLI:", e);
58
+ return await new Promise((resolve, reject) => {
59
+ exec("gcloud auth print-access-token", (err, stdout, stderr) => {
60
+ if (err) {
61
+ reject(
62
+ new Error(
63
+ `Failed to fetch an access token (Auth Library and gcloud): ${
64
+ stderr || err.message
65
+ }`
66
+ )
67
+ );
68
+ return;
69
+ }
70
+ const t = (stdout || "").trim();
71
+ if (!t) {
72
+ reject(
73
+ new Error(
74
+ "Failed to fetch an access token: empty token from gcloud"
75
+ )
76
+ );
77
+ return;
78
+ }
79
+ resolve(t);
80
+ });
81
+ });
82
+ }
83
+ }