@mixio-pro/kalaasetu-mcp 2.3.28-exp → 2.3.29

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.
Files changed (150) hide show
  1. package/bin/cli.js +1 -1
  2. package/package.json +5 -7
  3. package/src/index.ts +162 -0
  4. package/src/runtime-env.test.ts +77 -0
  5. package/src/storage/gcs.ts +116 -0
  6. package/src/storage/index.ts +36 -0
  7. package/src/storage/interface.ts +7 -0
  8. package/src/storage/local.ts +63 -0
  9. package/src/tools/byteplus/README.md +52 -0
  10. package/src/tools/byteplus/common.ts +206 -0
  11. package/src/tools/byteplus/image-to-video.ts +419 -0
  12. package/src/tools/byteplus/reference-to-video.ts +351 -0
  13. package/src/tools/byteplus/scene-video.ts +466 -0
  14. package/src/tools/fal/config.ts +430 -0
  15. package/src/tools/fal/dynamic-tools.ts +467 -0
  16. package/src/tools/fal/generate.ts +486 -0
  17. package/{dist/tools/fal/index.d.ts → src/tools/fal/index.ts} +1 -0
  18. package/src/tools/fal/models.ts +297 -0
  19. package/src/tools/fal/storage.ts +139 -0
  20. package/src/tools/generate-image.ts +289 -0
  21. package/src/tools/generate-scene-image.ts +362 -0
  22. package/src/tools/get-status.ts +506 -0
  23. package/src/tools/image-to-video.ts +492 -0
  24. package/src/tools/ingredients-to-video.ts +530 -0
  25. package/src/tools/mixio/common.ts +155 -0
  26. package/src/tools/mixio/face-swap.ts +105 -0
  27. package/src/tools/mixio/grok.ts +233 -0
  28. package/src/tools/mixio/multi-angle-batch.ts +132 -0
  29. package/src/tools/mixio/multi-angle.ts +124 -0
  30. package/src/tools/mixio/next-scene.ts +87 -0
  31. package/src/tools/perplexity.ts +260 -0
  32. package/src/tools/smallestai/client.ts +54 -0
  33. package/{dist/tools/smallestai/index.d.ts → src/tools/smallestai/index.ts} +6 -1
  34. package/src/tools/smallestai/stt.ts +72 -0
  35. package/src/tools/smallestai/tts.ts +135 -0
  36. package/src/tools/smallestai/voice-clones.ts +194 -0
  37. package/src/tools/youtube.ts +89 -0
  38. package/src/utils/endpoint-encoder.ts +28 -0
  39. package/src/utils/fal-save.ts +122 -0
  40. package/src/utils/filename.ts +38 -0
  41. package/src/utils/google-auth.ts +166 -0
  42. package/src/utils/image-grid.ts +125 -0
  43. package/src/utils/index.ts +0 -0
  44. package/src/utils/logger.ts +104 -0
  45. package/src/utils/openmeter.ts +246 -0
  46. package/src/utils/remote-config.test.ts +162 -0
  47. package/src/utils/remote-config.ts +157 -0
  48. package/src/utils/remote-sync.ts +98 -0
  49. package/src/utils/sanitize.ts +35 -0
  50. package/src/utils/tool-credits.ts +319 -0
  51. package/src/utils/tool-pricing.ts +239 -0
  52. package/src/utils/tool-wrapper.test.ts +55 -0
  53. package/src/utils/tool-wrapper.ts +174 -0
  54. package/src/utils/url-file.ts +92 -0
  55. package/dist/index.d.ts +0 -2
  56. package/dist/index.js +0 -22
  57. package/dist/storage/gcs.d.ts +0 -12
  58. package/dist/storage/gcs.js +0 -87
  59. package/dist/storage/index.d.ts +0 -2
  60. package/dist/storage/index.js +0 -26
  61. package/dist/storage/interface.d.ts +0 -7
  62. package/dist/storage/interface.js +0 -1
  63. package/dist/storage/local.d.ts +0 -10
  64. package/dist/storage/local.js +0 -50
  65. package/dist/tools/byteplus/common.d.ts +0 -43
  66. package/dist/tools/byteplus/common.js +0 -162
  67. package/dist/tools/byteplus/image-to-video.d.ts +0 -60
  68. package/dist/tools/byteplus/image-to-video.js +0 -303
  69. package/dist/tools/byteplus/index.js +0 -3
  70. package/dist/tools/byteplus/reference-to-video.d.ts +0 -58
  71. package/dist/tools/byteplus/reference-to-video.js +0 -257
  72. package/dist/tools/byteplus/scene-video.d.ts +0 -57
  73. package/dist/tools/byteplus/scene-video.js +0 -344
  74. package/dist/tools/fal/config.d.ts +0 -61
  75. package/dist/tools/fal/config.js +0 -303
  76. package/dist/tools/fal/dynamic-tools.d.ts +0 -44
  77. package/dist/tools/fal/dynamic-tools.js +0 -332
  78. package/dist/tools/fal/generate.d.ts +0 -41
  79. package/dist/tools/fal/generate.js +0 -326
  80. package/dist/tools/fal/index.js +0 -8
  81. package/dist/tools/fal/models.d.ts +0 -31
  82. package/dist/tools/fal/models.js +0 -236
  83. package/dist/tools/fal/storage.d.ts +0 -19
  84. package/dist/tools/fal/storage.js +0 -107
  85. package/dist/tools/generate-image.d.ts +0 -26
  86. package/dist/tools/generate-image.js +0 -228
  87. package/dist/tools/generate-scene-image.d.ts +0 -34
  88. package/dist/tools/generate-scene-image.js +0 -279
  89. package/dist/tools/get-status.d.ts +0 -25
  90. package/dist/tools/get-status.js +0 -366
  91. package/dist/tools/image-to-video.d.ts +0 -48
  92. package/dist/tools/image-to-video.js +0 -377
  93. package/dist/tools/ingredients-to-video.d.ts +0 -48
  94. package/dist/tools/ingredients-to-video.js +0 -389
  95. package/dist/tools/mixio/common.d.ts +0 -26
  96. package/dist/tools/mixio/common.js +0 -124
  97. package/dist/tools/mixio/face-swap.d.ts +0 -20
  98. package/dist/tools/mixio/face-swap.js +0 -66
  99. package/dist/tools/mixio/grok.d.ts +0 -56
  100. package/dist/tools/mixio/grok.js +0 -158
  101. package/dist/tools/mixio/index.js +0 -5
  102. package/dist/tools/mixio/multi-angle-batch.d.ts +0 -46
  103. package/dist/tools/mixio/multi-angle-batch.js +0 -91
  104. package/dist/tools/mixio/multi-angle.d.ts +0 -42
  105. package/dist/tools/mixio/multi-angle.js +0 -90
  106. package/dist/tools/mixio/next-scene.d.ts +0 -20
  107. package/dist/tools/mixio/next-scene.js +0 -60
  108. package/dist/tools/perplexity.d.ts +0 -29
  109. package/dist/tools/perplexity.js +0 -196
  110. package/dist/tools/smallestai/client.d.ts +0 -5
  111. package/dist/tools/smallestai/client.js +0 -40
  112. package/dist/tools/smallestai/index.js +0 -4
  113. package/dist/tools/smallestai/stt.d.ts +0 -14
  114. package/dist/tools/smallestai/stt.js +0 -55
  115. package/dist/tools/smallestai/tts.d.ts +0 -35
  116. package/dist/tools/smallestai/tts.js +0 -100
  117. package/dist/tools/smallestai/voice-clones.d.ts +0 -47
  118. package/dist/tools/smallestai/voice-clones.js +0 -150
  119. package/dist/tools/youtube.d.ts +0 -14
  120. package/dist/tools/youtube.js +0 -67
  121. package/dist/utils/endpoint-encoder.d.ts +0 -9
  122. package/dist/utils/endpoint-encoder.js +0 -24
  123. package/dist/utils/filename.d.ts +0 -6
  124. package/dist/utils/filename.js +0 -33
  125. package/dist/utils/google-auth.d.ts +0 -13
  126. package/dist/utils/google-auth.js +0 -119
  127. package/dist/utils/image-grid.d.ts +0 -15
  128. package/dist/utils/image-grid.js +0 -103
  129. package/dist/utils/index.d.ts +0 -1
  130. package/dist/utils/index.js +0 -1
  131. package/dist/utils/logger.d.ts +0 -6
  132. package/dist/utils/logger.js +0 -82
  133. package/dist/utils/openmeter.d.ts +0 -56
  134. package/dist/utils/openmeter.js +0 -184
  135. package/dist/utils/remote-config.d.ts +0 -6
  136. package/dist/utils/remote-config.js +0 -125
  137. package/dist/utils/remote-sync.d.ts +0 -21
  138. package/dist/utils/remote-sync.js +0 -71
  139. package/dist/utils/sanitize.d.ts +0 -10
  140. package/dist/utils/sanitize.js +0 -33
  141. package/dist/utils/tool-credits.d.ts +0 -22
  142. package/dist/utils/tool-credits.js +0 -277
  143. package/dist/utils/tool-pricing.d.ts +0 -61
  144. package/dist/utils/tool-pricing.js +0 -136
  145. package/dist/utils/tool-wrapper.d.ts +0 -43
  146. package/dist/utils/tool-wrapper.js +0 -121
  147. package/dist/utils/url-file.d.ts +0 -11
  148. package/dist/utils/url-file.js +0 -79
  149. /package/{dist/tools/byteplus/index.d.ts → src/tools/byteplus/index.ts} +0 -0
  150. /package/{dist/tools/mixio/index.d.ts → src/tools/mixio/index.ts} +0 -0
package/bin/cli.js CHANGED
@@ -7,7 +7,7 @@ import { dirname, join } from 'path';
7
7
 
8
8
  const __filename = fileURLToPath(import.meta.url);
9
9
  const __dirname = dirname(__filename);
10
- const indexPath = join(__dirname, '..', 'dist', 'index.js');
10
+ const indexPath = join(__dirname, '..', 'src', 'index.ts');
11
11
 
12
12
  // Try to run with Bun first
13
13
  const bunProcess = spawn('bun', [indexPath], {
package/package.json CHANGED
@@ -1,15 +1,15 @@
1
1
  {
2
2
  "name": "@mixio-pro/kalaasetu-mcp",
3
- "version": "2.3.28-exp",
3
+ "version": "2.3.29",
4
4
  "description": "A powerful Model Context Protocol server providing AI tools for content generation and analysis",
5
5
  "type": "module",
6
- "module": "dist/index.js",
7
- "main": "dist/index.js",
6
+ "module": "src/index.ts",
7
+ "main": "src/index.ts",
8
8
  "bin": {
9
9
  "kalaasetu-mcp": "bin/cli.js"
10
10
  },
11
11
  "files": [
12
- "dist",
12
+ "src",
13
13
  "bin",
14
14
  "fal-config.json",
15
15
  "README.md",
@@ -17,9 +17,7 @@
17
17
  ],
18
18
  "scripts": {
19
19
  "start": "bun run src/index.ts",
20
- "dev": "bun --watch src/index.ts",
21
- "build": "bun build ./src/index.ts --outdir ./dist --target bun --minify --no-sourcemap --external \"@fal-ai/client\" --external \"@google/genai\" --external \"@openmeter/sdk\" --external \"@sentry/node\" --external \"@types/node\" --external \"@types/sharp\" --external \"@types/wav\" --external \"dotenv\" --external \"fastmcp\" --external \"form-data\" --external \"google-auth-library\" --external \"jsonata\" --external \"sharp\" --external \"wav\" --external \"winston\" --external \"winston-transport-sentry-node\" --external \"zod\"",
22
- "prepublishOnly": "bun run build"
20
+ "dev": "bun --watch src/index.ts"
23
21
  },
24
22
  "keywords": [
25
23
  "mcp",
package/src/index.ts ADDED
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env bun
2
+ import { FastMCP } from "fastmcp";
3
+ import pkg from "../package.json";
4
+ import {
5
+ falListPresets,
6
+ falGetPresetDetails,
7
+ falUploadFile,
8
+ falGenerate,
9
+ } from "./tools/fal";
10
+ import { createAllFalTools } from "./tools/fal/dynamic-tools";
11
+ // import { geminiEditImage, geminiTextToImage } from "./tools/gemini";
12
+ import { generateImage } from "./tools/generate-image";
13
+ import { generateSceneImage } from "./tools/generate-scene-image";
14
+ import { imageToVideo } from "./tools/image-to-video";
15
+ import { ingredientsToVideo } from "./tools/ingredients-to-video";
16
+ import {
17
+ byteplusImageToVideo,
18
+ byteplusRefToVideo,
19
+ byteplusSceneVideo,
20
+ } from "./tools/byteplus";
21
+ import { getGenerationStatus } from "./tools/get-status";
22
+ import {
23
+ smallestAiTtsToWav,
24
+ smallestAiTranscribeAudio,
25
+ smallestAiCreateClone,
26
+ smallestAiListClones,
27
+ smallestAiDeleteClone,
28
+ smallestAiListVoices,
29
+ } from "./tools/smallestai";
30
+ import {
31
+ mixioMultiAngle,
32
+ mixioNextScene,
33
+ mixioFaceSwap,
34
+ mixioMultiAngleBatch,
35
+ mixioEditImage,
36
+ mixioImageToVideo,
37
+ } from "./tools/mixio";
38
+
39
+ import { syncFalConfig } from "./tools/fal/config";
40
+ import { logger } from "./utils/logger";
41
+ import { loadRemoteConfig } from "./utils/remote-config";
42
+ import { z } from "zod";
43
+
44
+ const server = new FastMCP({
45
+ name: "Kalaasetu MCP Server",
46
+ version: pkg.version as any,
47
+ });
48
+
49
+ async function main() {
50
+ const args = process.argv.slice(2);
51
+ const transportArg = args.find((arg) => arg.startsWith("--transport="));
52
+ const portArg = args.find((arg) => arg.startsWith("--port="));
53
+
54
+ const transportType = transportArg?.split("=")[1] ?? "stdio";
55
+ const portValue = portArg?.split("=")[1];
56
+ const port = portValue ? parseInt(portValue, 10) : 3000;
57
+
58
+ logger.info("🚀 Initializing Kalaasetu MCP Server...");
59
+
60
+ const noRemoteConfig = args.includes("--no-remote-config");
61
+
62
+ // 0. Load Remote Config
63
+ await loadRemoteConfig({ enabled: !noRemoteConfig });
64
+
65
+ // 1. Sync Remote Configs
66
+ await Promise.all([syncFalConfig()]);
67
+
68
+ // 2. Add Google Tools (Veo & Image)
69
+ const googleVeoEnabled = process.env.GOOGLE_VEO_ENABLED !== "false";
70
+ const googleImageEnabled = process.env.GOOGLE_IMAGE_ENABLED !== "false";
71
+
72
+ if (googleVeoEnabled) {
73
+ server.addTool(imageToVideo);
74
+ server.addTool(ingredientsToVideo);
75
+ }
76
+
77
+ if (googleImageEnabled) {
78
+ server.addTool(generateImage);
79
+ server.addTool(generateSceneImage);
80
+ }
81
+
82
+ // if (process.env.BYTEDANCE_ENABLED && process.env.BYTEPLUS_API_KEY) {
83
+ // server.addTool(byteplusImageToVideo);
84
+ // server.addTool(byteplusRefToVideo);
85
+ // server.addTool(byteplusSceneVideo);
86
+ // }
87
+
88
+ if (process.env.WAVES_ENABLED && process.env.WAVES_API_KEY) {
89
+ server.addTool(smallestAiTtsToWav);
90
+ server.addTool(smallestAiTranscribeAudio);
91
+ server.addTool(smallestAiCreateClone);
92
+ server.addTool(smallestAiListClones);
93
+ server.addTool(smallestAiDeleteClone);
94
+ server.addTool(smallestAiListVoices);
95
+ }
96
+
97
+ // 3. Add Discovery Tools
98
+ if (process.env.FAL_ENABLED && process.env.FAL_KEY) {
99
+ server.addTool(falListPresets);
100
+ server.addTool(falGetPresetDetails);
101
+ server.addTool(falUploadFile);
102
+ server.addTool(falGenerate);
103
+ // 4. Register Dynamic FAL AI Tools
104
+ // These are now based on potentially synced remote config
105
+ const falTools = createAllFalTools();
106
+ for (const tool of falTools) {
107
+ server.addTool(tool);
108
+ }
109
+ }
110
+
111
+ // 4. Add Mixio Tools
112
+ // server.addTool(mixioMultiAngle);
113
+ // server.addTool(mixioMultiAngleBatch);
114
+ // server.addTool(mixioNextScene);
115
+ // server.addTool(mixioFaceSwap);
116
+ if (process.env.GROK_ENABLED === "true") {
117
+ server.addTool(mixioEditImage);
118
+ server.addTool(mixioImageToVideo);
119
+ }
120
+
121
+ // 5. Add Status Tool
122
+ server.addTool(getGenerationStatus);
123
+
124
+ // 6. Add Version Tool
125
+ server.addTool({
126
+ name: "get_version_" + process.env.CLIENT_ID,
127
+ description: "Get the current version of the Kalaasetu MCP server",
128
+ parameters: z.object({}),
129
+ execute: async () => {
130
+ return JSON.stringify({
131
+ version: pkg.version,
132
+ clientId: process.env.CLIENT_ID,
133
+ projectId: process.env.PROJECT_ID,
134
+ env: process.env,
135
+ mixioTokenFound: process.env.MIXIO_TOKEN_FOUND,
136
+ mixioToken: process.env.MIXIO_TOKEN,
137
+ });
138
+ },
139
+ });
140
+
141
+ if (transportType === "sse" || transportType === "httpStream") {
142
+ logger.info(
143
+ `✅ Starting server on port ${port} with transport: ${transportType}...`,
144
+ );
145
+ server.start({
146
+ transportType: "httpStream",
147
+ httpStream: {
148
+ port,
149
+ },
150
+ });
151
+ } else {
152
+ logger.info("✅ Starting server with transport: stdio...");
153
+ server.start({
154
+ transportType: "stdio",
155
+ });
156
+ }
157
+ }
158
+
159
+ main().catch((err) => {
160
+ logger.error("❌ Failed to start server:", err);
161
+ process.exit(1);
162
+ });
@@ -0,0 +1,77 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "bun:test";
2
+
3
+ describe("runtime env resolution", () => {
4
+ const originalEnv = { ...process.env };
5
+ const originalFetch = globalThis.fetch;
6
+
7
+ beforeEach(() => {
8
+ process.env = { ...originalEnv };
9
+ globalThis.fetch = originalFetch;
10
+ });
11
+
12
+ afterEach(() => {
13
+ process.env = { ...originalEnv };
14
+ globalThis.fetch = originalFetch;
15
+ });
16
+
17
+ it("uses runtime FAL_KEY in checkFalStatus", async () => {
18
+ delete process.env.FAL_KEY;
19
+ const mod = await import("./tools/get-status");
20
+
21
+ process.env.FAL_KEY = "runtime_fal_key";
22
+
23
+ let authHeader: string | null = null;
24
+ globalThis.fetch = async (_url, init) => {
25
+ const headers = new Headers(init?.headers);
26
+ authHeader = headers.get("Authorization");
27
+ return new Response(JSON.stringify({ status: "IN_PROGRESS" }), {
28
+ status: 200,
29
+ headers: { "content-type": "application/json" },
30
+ });
31
+ };
32
+
33
+ await mod.checkFalStatus("https://queue.fal.run/test");
34
+ expect(authHeader).toBe("Key runtime_fal_key");
35
+ });
36
+
37
+ it("uses runtime WAVES_API_KEY in waves client", async () => {
38
+ delete process.env.WAVES_API_KEY;
39
+ const mod = await import("./tools/smallestai/client");
40
+
41
+ process.env.WAVES_API_KEY = "runtime_waves_key";
42
+
43
+ let authHeader: string | null = null;
44
+ globalThis.fetch = async (_url, init) => {
45
+ const headers = new Headers(init?.headers);
46
+ authHeader = headers.get("Authorization");
47
+ return new Response(JSON.stringify({ ok: true }), {
48
+ status: 200,
49
+ headers: { "content-type": "application/json" },
50
+ });
51
+ };
52
+
53
+ await mod.wavesClient.request("/v1/test", { method: "GET" });
54
+ expect(authHeader).toBe("Bearer runtime_waves_key");
55
+ });
56
+
57
+ it("uses runtime BYTEPLUS_BASE_URL in createTask", async () => {
58
+ delete process.env.BYTEPLUS_BASE_URL;
59
+ const mod = await import("./tools/byteplus/common");
60
+
61
+ process.env.BYTEPLUS_BASE_URL = "https://example.byteplus.local/api/v3";
62
+
63
+ let requestUrl = "";
64
+ globalThis.fetch = async (url) => {
65
+ requestUrl = String(url);
66
+ return new Response(JSON.stringify({ id: "task_123" }), {
67
+ status: 200,
68
+ headers: { "content-type": "application/json" },
69
+ });
70
+ };
71
+
72
+ await mod.createTask("api_key", { prompt: "hello" });
73
+ expect(requestUrl.startsWith("https://example.byteplus.local/api/v3")).toBe(
74
+ true,
75
+ );
76
+ });
77
+ });
@@ -0,0 +1,116 @@
1
+ import { GoogleAuth } from "google-auth-library";
2
+ import type { StorageProvider } from "./interface";
3
+ import * as path from "path";
4
+ import {
5
+ getGoogleAuthClient,
6
+ getGoogleAccessToken,
7
+ } from "../utils/google-auth";
8
+
9
+ export class GCSStorageProvider implements StorageProvider {
10
+ private bucket: string;
11
+ private auth: GoogleAuth | null = null;
12
+
13
+ constructor(bucket: string) {
14
+ this.bucket = bucket;
15
+ }
16
+
17
+ async init(): Promise<void> {
18
+ this.auth = await getGoogleAuthClient({
19
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
20
+ });
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
+ // Use the centralized helper which includes gcloud fallback if needed,
30
+ // although GCSStorageProvider primarily relies on the initialized GoogleAuth.
31
+ // However, if we want to support the same fallback logic as tools:
32
+ return getGoogleAccessToken({
33
+ scopes: ["https://www.googleapis.com/auth/cloud-platform"],
34
+ });
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: new Uint8Array(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
+ }
@@ -0,0 +1,36 @@
1
+ import type { StorageProvider } from "./interface";
2
+ import { LocalStorageProvider } from "./local";
3
+ import { GCSStorageProvider } from "./gcs";
4
+ import { logger } from "../utils/logger";
5
+
6
+ let storageInstance: StorageProvider | null = null;
7
+
8
+ export function getStorage(): StorageProvider {
9
+ if (!storageInstance) {
10
+ const type = process.env.STORAGE_PROVIDER || "local";
11
+ logger.info(`Initializing storage provider: ${type}`);
12
+ logger.info(`Base path (cwd): ${process.cwd()}`);
13
+ if (process.env.VSCODE_CWD) {
14
+ logger.info(`VSCode CWD: ${process.env.VSCODE_CWD}`);
15
+ }
16
+
17
+ if (type === "gcs") {
18
+ const bucket = process.env.GCS_BUCKET;
19
+
20
+ if (!bucket) {
21
+ throw new Error("GCS_BUCKET is required when using gcs storage");
22
+ }
23
+
24
+ storageInstance = new GCSStorageProvider(bucket);
25
+ } else {
26
+ const basePath = process.env.VSCODE_CWD || process.cwd();
27
+ storageInstance = new LocalStorageProvider(basePath);
28
+ }
29
+
30
+ // Initialize async
31
+ storageInstance
32
+ .init()
33
+ .catch((err) => logger.error("Failed to init storage:", err));
34
+ }
35
+ return storageInstance;
36
+ }
@@ -0,0 +1,7 @@
1
+ export interface StorageProvider {
2
+ init(): Promise<void>;
3
+ readFile(path: string): Promise<Buffer>;
4
+ writeFile(path: string, data: Buffer | string): Promise<string>; // Returns public URL
5
+ exists(path: string): Promise<boolean>;
6
+ getPublicUrl(path: string): Promise<string>;
7
+ }
@@ -0,0 +1,63 @@
1
+ import * as fs from "fs";
2
+ import * as path from "path";
3
+ import type { StorageProvider } from "./interface";
4
+
5
+ export class LocalStorageProvider implements StorageProvider {
6
+ private basePath: string;
7
+
8
+ constructor(basePath: string = process.cwd()) {
9
+ this.basePath = basePath;
10
+ }
11
+
12
+ async init(): Promise<void> {
13
+ // No-op for local
14
+ }
15
+
16
+ private resolvePath(filePath: string): string {
17
+ if (path.isAbsolute(filePath)) {
18
+ return filePath;
19
+ }
20
+
21
+ // Handle LLM hallucinations: if path is relative but doesn't start with ./ or ../,
22
+ // and VSCODE_CWD is available, use it as the base.
23
+ if (
24
+ process.env.VSCODE_CWD &&
25
+ !filePath.startsWith("./") &&
26
+ !filePath.startsWith("../")
27
+ ) {
28
+ return path.resolve(process.env.VSCODE_CWD, filePath);
29
+ }
30
+
31
+ return path.resolve(this.basePath, filePath);
32
+ }
33
+
34
+ async readFile(filePath: string): Promise<Buffer> {
35
+ const fullPath = this.resolvePath(filePath);
36
+ return fs.promises.readFile(fullPath);
37
+ }
38
+
39
+ async writeFile(filePath: string, data: Buffer | string): Promise<string> {
40
+ const fullPath = this.resolvePath(filePath);
41
+
42
+ const dir = path.dirname(fullPath);
43
+ if (!fs.existsSync(dir)) {
44
+ await fs.promises.mkdir(dir, { recursive: true });
45
+ }
46
+ await fs.promises.writeFile(fullPath, data);
47
+ return fullPath;
48
+ }
49
+
50
+ async exists(filePath: string): Promise<boolean> {
51
+ const fullPath = this.resolvePath(filePath);
52
+ try {
53
+ await fs.promises.access(fullPath, fs.constants.F_OK);
54
+ return true;
55
+ } catch {
56
+ return false;
57
+ }
58
+ }
59
+
60
+ async getPublicUrl(filePath: string): Promise<string> {
61
+ return this.resolvePath(filePath);
62
+ }
63
+ }
@@ -0,0 +1,52 @@
1
+ # BytePlus Seedance 1.5 Pro — Video Generation Tools
2
+
3
+ Three MCP tools for video generation using [BytePlus ModelArk](https://docs.byteplus.com/en/docs/ModelArk) Seedance 1.5 Pro.
4
+
5
+ ## Tools
6
+
7
+ ### `generateVideoSeedance` — Image-to-Video
8
+ Single-shot video from a first frame image, with optional last frame for start→end interpolation.
9
+ Always generates in **draft mode** (480p). Use `finalize_draft_task_id` to promote to final quality.
10
+
11
+ **Use when:** animating a single image, or interpolating between start/end frames.
12
+
13
+ ### `generateVideoSeedanceRef` — Reference Images to Video
14
+ Generate video incorporating elements from 1–4 reference images. Prompt uses `[Image 1]`, `[Image 2]` syntax.
15
+ Rarely used. Always **draft mode**.
16
+
17
+ **Use when:** compositing characters/objects/styles from multiple reference images.
18
+
19
+ ### `generateVideoSeedanceScene` — Multi-Shot Scene
20
+ Chains multiple shots within one continuous scene. Each shot's last frame becomes the next shot's first frame.
21
+ Uses **standard mode** (not draft) because `return_last_frame` is required for chaining.
22
+ Handles MCP timeouts via `resume_state`.
23
+
24
+ **Use when:** generating longer videos or continuous multi-shot scenes (not cut scenes).
25
+
26
+ ## Environment Variables
27
+
28
+ | Variable | Required | Description |
29
+ |---|---|---|
30
+ | `BYTEPLUS_API_KEY` | Yes | API key from [BytePlus ModelArk console](https://console.byteplus.com/ark/region:ark+ap-southeast-1/apikey) |
31
+ | `BYTEPLUS_BASE_URL` | No | Override base URL (default: `https://ark.ap-southeast.bytepluses.com/api/v3`) |
32
+ | `GEMINI_API_KEY` | No | Required for LLM-based prompt enhancement (uses Gemini 2.0 Flash) |
33
+
34
+ ## API Reference
35
+
36
+ - [Video Generation API](https://docs.byteplus.com/en/docs/ModelArk/1366799)
37
+ - [Seedance 1.5 Pro Prompt Guide](https://docs.byteplus.com/en/docs/ModelArk/2168087)
38
+ - [Image-to-Video modes](https://docs.byteplus.com/en/docs/ModelArk/1520757)
39
+ - [Retrieve task status](https://docs.byteplus.com/en/docs/ModelArk/1521309)
40
+
41
+ ## Model
42
+
43
+ Default model: `seedance-1-5-pro-251215`
44
+
45
+ ### Draft Mode (Image-to-Video & Reference tools)
46
+ - 480p only — other resolutions will error
47
+ - Fewer tokens consumed (60% of standard with audio)
48
+ - `return_last_frame` not supported
49
+ - Draft task ID valid for 7 days for finalization
50
+
51
+ ### Finalization
52
+ Pass `finalize_draft_task_id` to promote a draft to a final video. The model reuses all inputs from the draft (prompt, images, seed, ratio, duration, camera_fixed). You can configure `final_resolution`, `watermark`, and `return_last_frame` during finalization.