@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.
- package/bin/cli.js +1 -1
- package/package.json +5 -7
- package/src/index.ts +162 -0
- package/src/runtime-env.test.ts +77 -0
- package/src/storage/gcs.ts +116 -0
- package/src/storage/index.ts +36 -0
- package/src/storage/interface.ts +7 -0
- package/src/storage/local.ts +63 -0
- package/src/tools/byteplus/README.md +52 -0
- package/src/tools/byteplus/common.ts +206 -0
- package/src/tools/byteplus/image-to-video.ts +419 -0
- package/src/tools/byteplus/reference-to-video.ts +351 -0
- package/src/tools/byteplus/scene-video.ts +466 -0
- package/src/tools/fal/config.ts +430 -0
- package/src/tools/fal/dynamic-tools.ts +467 -0
- package/src/tools/fal/generate.ts +486 -0
- package/{dist/tools/fal/index.d.ts → src/tools/fal/index.ts} +1 -0
- package/src/tools/fal/models.ts +297 -0
- package/src/tools/fal/storage.ts +139 -0
- package/src/tools/generate-image.ts +289 -0
- package/src/tools/generate-scene-image.ts +362 -0
- package/src/tools/get-status.ts +506 -0
- package/src/tools/image-to-video.ts +492 -0
- package/src/tools/ingredients-to-video.ts +530 -0
- package/src/tools/mixio/common.ts +155 -0
- package/src/tools/mixio/face-swap.ts +105 -0
- package/src/tools/mixio/grok.ts +233 -0
- package/src/tools/mixio/multi-angle-batch.ts +132 -0
- package/src/tools/mixio/multi-angle.ts +124 -0
- package/src/tools/mixio/next-scene.ts +87 -0
- package/src/tools/perplexity.ts +260 -0
- package/src/tools/smallestai/client.ts +54 -0
- package/{dist/tools/smallestai/index.d.ts → src/tools/smallestai/index.ts} +6 -1
- package/src/tools/smallestai/stt.ts +72 -0
- package/src/tools/smallestai/tts.ts +135 -0
- package/src/tools/smallestai/voice-clones.ts +194 -0
- package/src/tools/youtube.ts +89 -0
- package/src/utils/endpoint-encoder.ts +28 -0
- package/src/utils/fal-save.ts +122 -0
- package/src/utils/filename.ts +38 -0
- package/src/utils/google-auth.ts +166 -0
- package/src/utils/image-grid.ts +125 -0
- package/src/utils/index.ts +0 -0
- package/src/utils/logger.ts +104 -0
- package/src/utils/openmeter.ts +246 -0
- package/src/utils/remote-config.test.ts +162 -0
- package/src/utils/remote-config.ts +157 -0
- package/src/utils/remote-sync.ts +98 -0
- package/src/utils/sanitize.ts +35 -0
- package/src/utils/tool-credits.ts +319 -0
- package/src/utils/tool-pricing.ts +239 -0
- package/src/utils/tool-wrapper.test.ts +55 -0
- package/src/utils/tool-wrapper.ts +174 -0
- package/src/utils/url-file.ts +92 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -22
- package/dist/storage/gcs.d.ts +0 -12
- package/dist/storage/gcs.js +0 -87
- package/dist/storage/index.d.ts +0 -2
- package/dist/storage/index.js +0 -26
- package/dist/storage/interface.d.ts +0 -7
- package/dist/storage/interface.js +0 -1
- package/dist/storage/local.d.ts +0 -10
- package/dist/storage/local.js +0 -50
- package/dist/tools/byteplus/common.d.ts +0 -43
- package/dist/tools/byteplus/common.js +0 -162
- package/dist/tools/byteplus/image-to-video.d.ts +0 -60
- package/dist/tools/byteplus/image-to-video.js +0 -303
- package/dist/tools/byteplus/index.js +0 -3
- package/dist/tools/byteplus/reference-to-video.d.ts +0 -58
- package/dist/tools/byteplus/reference-to-video.js +0 -257
- package/dist/tools/byteplus/scene-video.d.ts +0 -57
- package/dist/tools/byteplus/scene-video.js +0 -344
- package/dist/tools/fal/config.d.ts +0 -61
- package/dist/tools/fal/config.js +0 -303
- package/dist/tools/fal/dynamic-tools.d.ts +0 -44
- package/dist/tools/fal/dynamic-tools.js +0 -332
- package/dist/tools/fal/generate.d.ts +0 -41
- package/dist/tools/fal/generate.js +0 -326
- package/dist/tools/fal/index.js +0 -8
- package/dist/tools/fal/models.d.ts +0 -31
- package/dist/tools/fal/models.js +0 -236
- package/dist/tools/fal/storage.d.ts +0 -19
- package/dist/tools/fal/storage.js +0 -107
- package/dist/tools/generate-image.d.ts +0 -26
- package/dist/tools/generate-image.js +0 -228
- package/dist/tools/generate-scene-image.d.ts +0 -34
- package/dist/tools/generate-scene-image.js +0 -279
- package/dist/tools/get-status.d.ts +0 -25
- package/dist/tools/get-status.js +0 -366
- package/dist/tools/image-to-video.d.ts +0 -48
- package/dist/tools/image-to-video.js +0 -377
- package/dist/tools/ingredients-to-video.d.ts +0 -48
- package/dist/tools/ingredients-to-video.js +0 -389
- package/dist/tools/mixio/common.d.ts +0 -26
- package/dist/tools/mixio/common.js +0 -124
- package/dist/tools/mixio/face-swap.d.ts +0 -20
- package/dist/tools/mixio/face-swap.js +0 -66
- package/dist/tools/mixio/grok.d.ts +0 -56
- package/dist/tools/mixio/grok.js +0 -158
- package/dist/tools/mixio/index.js +0 -5
- package/dist/tools/mixio/multi-angle-batch.d.ts +0 -46
- package/dist/tools/mixio/multi-angle-batch.js +0 -91
- package/dist/tools/mixio/multi-angle.d.ts +0 -42
- package/dist/tools/mixio/multi-angle.js +0 -90
- package/dist/tools/mixio/next-scene.d.ts +0 -20
- package/dist/tools/mixio/next-scene.js +0 -60
- package/dist/tools/perplexity.d.ts +0 -29
- package/dist/tools/perplexity.js +0 -196
- package/dist/tools/smallestai/client.d.ts +0 -5
- package/dist/tools/smallestai/client.js +0 -40
- package/dist/tools/smallestai/index.js +0 -4
- package/dist/tools/smallestai/stt.d.ts +0 -14
- package/dist/tools/smallestai/stt.js +0 -55
- package/dist/tools/smallestai/tts.d.ts +0 -35
- package/dist/tools/smallestai/tts.js +0 -100
- package/dist/tools/smallestai/voice-clones.d.ts +0 -47
- package/dist/tools/smallestai/voice-clones.js +0 -150
- package/dist/tools/youtube.d.ts +0 -14
- package/dist/tools/youtube.js +0 -67
- package/dist/utils/endpoint-encoder.d.ts +0 -9
- package/dist/utils/endpoint-encoder.js +0 -24
- package/dist/utils/filename.d.ts +0 -6
- package/dist/utils/filename.js +0 -33
- package/dist/utils/google-auth.d.ts +0 -13
- package/dist/utils/google-auth.js +0 -119
- package/dist/utils/image-grid.d.ts +0 -15
- package/dist/utils/image-grid.js +0 -103
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/logger.d.ts +0 -6
- package/dist/utils/logger.js +0 -82
- package/dist/utils/openmeter.d.ts +0 -56
- package/dist/utils/openmeter.js +0 -184
- package/dist/utils/remote-config.d.ts +0 -6
- package/dist/utils/remote-config.js +0 -125
- package/dist/utils/remote-sync.d.ts +0 -21
- package/dist/utils/remote-sync.js +0 -71
- package/dist/utils/sanitize.d.ts +0 -10
- package/dist/utils/sanitize.js +0 -33
- package/dist/utils/tool-credits.d.ts +0 -22
- package/dist/utils/tool-credits.js +0 -277
- package/dist/utils/tool-pricing.d.ts +0 -61
- package/dist/utils/tool-pricing.js +0 -136
- package/dist/utils/tool-wrapper.d.ts +0 -43
- package/dist/utils/tool-wrapper.js +0 -121
- package/dist/utils/url-file.d.ts +0 -11
- package/dist/utils/url-file.js +0 -79
- /package/{dist/tools/byteplus/index.d.ts → src/tools/byteplus/index.ts} +0 -0
- /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, '..', '
|
|
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.
|
|
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": "
|
|
7
|
-
"main": "
|
|
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
|
-
"
|
|
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.
|