@ulmeanua/kie-mcp 0.1.0

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 (52) hide show
  1. package/INSTALL.md +159 -0
  2. package/LICENSE +21 -0
  3. package/README.md +128 -0
  4. package/dist/client.d.ts +14 -0
  5. package/dist/client.d.ts.map +1 -0
  6. package/dist/client.js +115 -0
  7. package/dist/client.js.map +1 -0
  8. package/dist/config.d.ts +12 -0
  9. package/dist/config.d.ts.map +1 -0
  10. package/dist/config.js +42 -0
  11. package/dist/config.js.map +1 -0
  12. package/dist/downloader.d.ts +15 -0
  13. package/dist/downloader.d.ts.map +1 -0
  14. package/dist/downloader.js +87 -0
  15. package/dist/downloader.js.map +1 -0
  16. package/dist/endpoints.d.ts +8 -0
  17. package/dist/endpoints.d.ts.map +1 -0
  18. package/dist/endpoints.js +183 -0
  19. package/dist/endpoints.js.map +1 -0
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +25 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/poller.d.ts +10 -0
  25. package/dist/poller.d.ts.map +1 -0
  26. package/dist/poller.js +44 -0
  27. package/dist/poller.js.map +1 -0
  28. package/dist/registry.d.ts +5 -0
  29. package/dist/registry.d.ts.map +1 -0
  30. package/dist/registry.js +271 -0
  31. package/dist/registry.js.map +1 -0
  32. package/dist/server.d.ts +10 -0
  33. package/dist/server.d.ts.map +1 -0
  34. package/dist/server.js +57 -0
  35. package/dist/server.js.map +1 -0
  36. package/dist/store.d.ts +36 -0
  37. package/dist/store.d.ts.map +1 -0
  38. package/dist/store.js +108 -0
  39. package/dist/store.js.map +1 -0
  40. package/dist/tools.d.ts +14 -0
  41. package/dist/tools.d.ts.map +1 -0
  42. package/dist/tools.js +446 -0
  43. package/dist/tools.js.map +1 -0
  44. package/dist/types.d.ts +54 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +24 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/version.d.ts +3 -0
  49. package/dist/version.d.ts.map +1 -0
  50. package/dist/version.js +3 -0
  51. package/dist/version.js.map +1 -0
  52. package/package.json +63 -0
package/INSTALL.md ADDED
@@ -0,0 +1,159 @@
1
+ # Install @ulmeanua/kie-mcp — step-by-step guide
2
+
3
+ This MCP server gives you access to **30+ AI models** through Kie.ai (Veo, Suno, Runway, Flux, Nano Banana, ElevenLabs, Midjourney, etc.) directly inside Claude Desktop / Claude Code / Cursor / Windsurf. Lower cost than the official APIs, automatic polling, asset downloaded locally.
4
+
5
+ ---
6
+
7
+ ## 1. Get a Kie.ai API key
8
+
9
+ Go to [kie.ai/api-key](https://kie.ai/api-key), create an account and copy the key. Format: 32 hex characters (a–f + digits).
10
+
11
+ Tip: Kie.ai gives free welcome credits. Your first test call (nano-banana-2 at 1K) costs ~$0.04 — well within the free tier.
12
+
13
+ ## 2. Check Node.js
14
+
15
+ ```bash
16
+ node --version
17
+ ```
18
+
19
+ You need **22.0 or newer**. If older:
20
+ - Windows: [nodejs.org](https://nodejs.org) → download LTS
21
+ - macOS: `brew install node@22`
22
+ - Linux: [nodejs.org/en/download](https://nodejs.org/en/download)
23
+
24
+ ## 3. Configure in your MCP client
25
+
26
+ ### Claude Desktop
27
+
28
+ Edit the config file:
29
+ - **Mac**: `~/Library/Application Support/Claude/claude_desktop_config.json`
30
+ - **Windows**: `%APPDATA%\Claude\claude_desktop_config.json`
31
+
32
+ Add (or merge into `mcpServers`):
33
+
34
+ ```json
35
+ {
36
+ "mcpServers": {
37
+ "kie": {
38
+ "command": "npx",
39
+ "args": ["-y", "@ulmeanua/kie-mcp"],
40
+ "env": {
41
+ "KIE_API_KEY": "your-kie-key-here"
42
+ }
43
+ }
44
+ }
45
+ }
46
+ ```
47
+
48
+ Restart Claude Desktop. On your next conversation, the `kie_*` tools should be available.
49
+
50
+ ### Claude Code
51
+
52
+ In Claude Code's MCP settings, add the same snippet as above.
53
+
54
+ ### Cursor
55
+
56
+ Edit `~/.cursor/mcp.json` (Mac/Linux) or `%USERPROFILE%\.cursor\mcp.json` (Windows):
57
+
58
+ ```json
59
+ {
60
+ "mcpServers": {
61
+ "kie": {
62
+ "command": "npx",
63
+ "args": ["-y", "@ulmeanua/kie-mcp"],
64
+ "env": {
65
+ "KIE_API_KEY": "your-key-here"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ Restart Cursor.
73
+
74
+ ### Windsurf
75
+
76
+ Edit `~/.codeium/windsurf/mcp_config.json` with the same snippet.
77
+
78
+ ## 4. Quick test
79
+
80
+ In any MCP client (after restart), tell the AI:
81
+
82
+ > "Use kie_health to show me the server is running."
83
+
84
+ If it replies with a JSON containing `package: @ulmeanua/kie-mcp` and `api_key_set: true`, you're set.
85
+
86
+ Then:
87
+
88
+ > "Generate an image with nano-banana-2: a red apple on a wooden table."
89
+
90
+ The AI will call `kie_image` with `wait:true` (default), wait ~30–60s, download the asset and give you back the local path.
91
+
92
+ ## 5. Optional environment variables
93
+
94
+ | Env var | Default | What it does |
95
+ |---|---|---|
96
+ | `KIE_OUTPUT_DIR` | `$HOME/.kie-mcp/assets` | Where downloaded assets are written |
97
+ | `KIE_COST_BUDGET_USD` | not set | If set (e.g. `5.00`), MCP blocks calls past the cap |
98
+ | `KIE_POLL_INTERVAL_MS` | `3000` | Polling cadence for video/music (3s default) |
99
+ | `KIE_POLL_MAX_MS` | `600000` | Polling timeout (10 min default — large videos may need more) |
100
+
101
+ Add them in the MCP config's `env` block:
102
+
103
+ ```json
104
+ "env": {
105
+ "KIE_API_KEY": "...",
106
+ "KIE_COST_BUDGET_USD": "5.00",
107
+ "KIE_OUTPUT_DIR": "/Users/me/Desktop/kie-output"
108
+ }
109
+ ```
110
+
111
+ ## 6. Cost so far
112
+
113
+ At any time, ask the AI:
114
+
115
+ > "Run kie_cost_report."
116
+
117
+ You get total spend + per-model breakdown + remaining budget (if `KIE_COST_BUDGET_USD` is set).
118
+
119
+ ## 7. Compare models
120
+
121
+ > "Use kie_compare with prompt='X' and models=['nano-banana-2','flux-kontext-pro','seedream-v5-lite']."
122
+
123
+ Runs all 3 in parallel and downloads the grid. Useful when picking the best model for a given prompt.
124
+
125
+ ## 8. Available models
126
+
127
+ Ask: `kie_models` (or filtered by kind):
128
+
129
+ | Category | Registered models |
130
+ |---|---|
131
+ | **Image** | nano-banana-2 ✅, flux-kontext-pro, flux-kontext-max, gpt-image-2, seedream-v5-lite, qwen-image* |
132
+ | **Video** | veo3, veo3_fast, runway-aleph, seedance-2 |
133
+ | **Music** | suno-v5, suno-v4-5 |
134
+ | **Speech** | elevenlabs-tts, elevenlabs-sfx |
135
+
136
+ ✅ = verified live against the real API. The rest come from docs.kie.ai — if you get `422: model name not supported`, the current ID in kie.ai's catalog is different; please open an issue.
137
+
138
+ \* `qwen-image` was not accepted in May 2026 — kie.ai uses a different ID; open an issue if you know the real name.
139
+
140
+ ## 9. Common issues
141
+
142
+ **"Cannot find module '@ulmeanua/kie-mcp'"**
143
+ → Your Node version is <22. Update Node.
144
+
145
+ **"KIE_API_KEY is required"**
146
+ → The key isn't in the MCP config's `env`. Double-check.
147
+
148
+ **"kie.ai error (code=401): Invalid API key"**
149
+ → The key is wrong or expired. Regenerate at kie.ai/api-key.
150
+
151
+ **"polling timeout after 600000ms"**
152
+ → The video model took >10 min. Increase `KIE_POLL_MAX_MS` or use `wait:false` + `kie_wait` with a larger `timeout_ms`.
153
+
154
+ **"cost budget exceeded"**
155
+ → The feature is working. Remove `KIE_COST_BUDGET_USD` from config or raise the cap.
156
+
157
+ ## 10. Issues / contributions
158
+
159
+ Public repo at **https://github.com/ulmeanuadrian/kie-mcp**. Issues welcome. PRs require green evals (47/47 mocked + 2 live).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Adrian Ulmeanu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,128 @@
1
+ # @ulmeanua/kie-mcp
2
+
3
+ MCP server for [kie.ai](https://kie.ai) — an aggregator for generative media models (Veo, Suno, Runway, Flux, Nano Banana, Midjourney, ElevenLabs, etc.) with prices well below the official APIs.
4
+
5
+ **What sets this server apart:**
6
+
7
+ 1. **Sync-wait built-in.** `kie_video(...)` polls the task to completion and downloads the asset locally. No manual polling loop.
8
+ 2. **Auto-download.** Every successful task writes the file to a predictable local path; the tool returns the absolute path, not a URL that expires.
9
+ 3. **Five umbrella tools.** `kie_image`, `kie_video`, `kie_music`, `kie_speech`, `kie_compare` — dispatch by `model` param. Not 24 separate tools bloating your MCP context.
10
+ 4. **Cost telemetry.** `kie_cost_report` shows session + per-model + total spend. Optional `KIE_COST_BUDGET_USD` blocks calls past the cap.
11
+ 5. **Batch & compare.** `kie_compare(prompt, models=[...])` runs the same prompt on N models in parallel and returns a grid of results.
12
+
13
+ ## Install
14
+
15
+ ```bash
16
+ npm install -g @ulmeanua/kie-mcp
17
+ ```
18
+
19
+ Or run via `npx` from your MCP client config:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "kie": {
25
+ "command": "npx",
26
+ "args": ["-y", "@ulmeanua/kie-mcp"],
27
+ "env": {
28
+ "KIE_API_KEY": "sk-..."
29
+ }
30
+ }
31
+ }
32
+ }
33
+ ```
34
+
35
+ Detailed step-by-step install instructions for Claude Desktop, Claude Code, Cursor, and Windsurf are in [INSTALL.md](./INSTALL.md).
36
+
37
+ ## Tools (11 total)
38
+
39
+ | Tool | Purpose |
40
+ |---|---|
41
+ | `kie_image` | Generate / edit an image. Defaults to `wait:true, download:true`. |
42
+ | `kie_video` | Generate a video. Defaults to `wait:true, download:true`. |
43
+ | `kie_music` | Generate music (Suno). |
44
+ | `kie_speech` | TTS and sound effects (ElevenLabs). |
45
+ | `kie_compare` | Run the same prompt across N models in parallel (cap 4). Same-kind only. |
46
+ | `kie_wait` | Wait for an existing task_id until it terminates and download. |
47
+ | `kie_status` | Get current state of a task without polling. |
48
+ | `kie_assets` | List tasks from the local DB filtered by `model` / `state`. |
49
+ | `kie_cost_report` | Cumulative cost (all-time or hours window) + budget remaining. |
50
+ | `kie_models` | List registered models (id, kind, family, description). |
51
+ | `kie_health` | Health probe + echoed config (api_key set / output dir / db path). |
52
+
53
+ ## Registered models (verification status)
54
+
55
+ | Model | Kind | Endpoint family | Verified live |
56
+ |---|---|---|---|
57
+ | `nano-banana-2` | image | unified | ✅ 2026-05-27 (live smoke) |
58
+ | `flux-kontext-pro` | image | unified | docs only |
59
+ | `flux-kontext-max` | image | unified | docs only |
60
+ | `gpt-image-2` | image | gpt4o legacy | docs only |
61
+ | `seedream-v5-lite` | image | unified | docs only |
62
+ | `qwen-image` | image | unified | ⚠️ this ID was not accepted on the API (May 2026); check the current kie.ai catalog |
63
+ | `veo3`, `veo3_fast` | video | veo legacy | docs only |
64
+ | `runway-aleph` | video | runway legacy | docs only |
65
+ | `seedance-2` | video | unified | docs only |
66
+ | `suno-v5`, `suno-v4-5` | music | suno legacy | docs only |
67
+ | `elevenlabs-tts`, `elevenlabs-sfx` | speech | unified | docs only |
68
+
69
+ When an ID returns `422: model name not supported`, query `kie_models` to see the registered catalog and update `src/registry.ts` with the exact name from kie.ai's market.
70
+
71
+ ## Configuration
72
+
73
+ | Env var | Default | Role |
74
+ |---|---|---|
75
+ | `KIE_API_KEY` | — | Required. Your kie.ai key |
76
+ | `KIE_API_BASE` | `https://api.kie.ai/api/v1` | Override base URL |
77
+ | `KIE_TIMEOUT_MS` | `120000` | HTTP request timeout |
78
+ | `KIE_OUTPUT_DIR` | `$HOME/.kie-mcp/assets` | Where downloaded assets are written |
79
+ | `KIE_DB_PATH` | `$HOME/.kie-mcp/state.db` | SQLite path for task tracking + cost |
80
+ | `KIE_POLL_INTERVAL_MS` | `3000` | Polling cadence |
81
+ | `KIE_POLL_MAX_MS` | `600000` | Polling cap (10 min) |
82
+ | `KIE_COST_BUDGET_USD` | — | When set, blocks calls after exceeding the cap |
83
+
84
+ ## Development
85
+
86
+ ```bash
87
+ npm install
88
+ npm run build
89
+ npm run typecheck
90
+ npm run eval # 47 evals across 5 phases (mocked) — should be 47/47 green
91
+ npm run eval:live # adds 2 live calls to kie.ai (needs KIE_API_KEY, costs ~$0.04)
92
+ ```
93
+
94
+ ## Eval phases
95
+
96
+ | Phase | Eval file | Tests | Verifies |
97
+ |---|---|---|---|
98
+ | 0 | `phase0_scaffold.eval.ts` | 6 | Server boot, config validation, MCP round-trip |
99
+ | 1 | `phase1_client.eval.ts` | 11 | KieClient serialize / retry / parse across all 5 endpoint families |
100
+ | 2 | `phase2_tools.eval.ts` | 12 | Tool discovery, dispatch routing, cross-kind guard, Zod validation |
101
+ | 3 | `phase3_wait_download.eval.ts` | 12 | Poller, downloader, store round-trip, idempotency, budget enforcement |
102
+ | 4 | `phase4_telemetry_compare.eval.ts` | 5 | kie_compare parallel + graceful degrade, cost report aggregation |
103
+ | 5 | `phase5_live_smoke.eval.ts` | 2 (gated) | Real kie.ai call → asset downloaded + cost logged |
104
+
105
+ **Evals are the source of truth for the contract — any code change must keep 47/47 mocked evals green (or update the evals with an explicit reason in the commit message).**
106
+
107
+ ## Architecture
108
+
109
+ ```
110
+ MCP client
111
+ ↓ stdio
112
+ src/index.ts ── boot, wire dependencies
113
+ src/server.ts ── MCP request routing (tools/list, tools/call)
114
+ src/tools.ts ── 11 tool handlers (umbrella + utility + telemetry)
115
+
116
+ src/registry.ts ── MODEL_REGISTRY (id → endpoint family + Zod schema + cost estimator)
117
+
118
+ src/client.ts ── KieClient (fetch + retry + timeout + idempotency-key)
119
+ src/endpoints.ts ── 5 endpoint families (unified, veo, runway, suno, gpt4o)
120
+
121
+ src/poller.ts ── pollUntilTerminal (waiting → success/fail)
122
+ src/downloader.ts ── AssetDownloader (URL → KIE_OUTPUT_DIR/<task_id>.<ext>)
123
+ src/store.ts ── TaskStore (node:sqlite — tasks + idempotency + cost)
124
+ ```
125
+
126
+ ## License
127
+
128
+ MIT. See [LICENSE](./LICENSE).
@@ -0,0 +1,14 @@
1
+ import { Config } from './config.js';
2
+ import { CreateTaskResult, EndpointSpec, KieRequestOptions, NormalizedTaskStatus, RawKieResponse } from './types.js';
3
+ export interface FetchLike {
4
+ (input: string, init?: RequestInit): Promise<Response>;
5
+ }
6
+ export declare class KieClient {
7
+ private readonly config;
8
+ private readonly fetchImpl;
9
+ constructor(config: Config, fetchImpl?: FetchLike);
10
+ createTask(endpoint: EndpointSpec, model: string, input: Record<string, unknown>, callBackUrl?: string, options?: KieRequestOptions): Promise<CreateTaskResult>;
11
+ getTaskStatus(endpoint: EndpointSpec, taskId: string, options?: KieRequestOptions): Promise<NormalizedTaskStatus>;
12
+ request<T>(method: 'GET' | 'POST', path: string, body: unknown, options?: KieRequestOptions): Promise<RawKieResponse<T>>;
13
+ }
14
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,EACL,gBAAgB,EAChB,YAAY,EAEZ,iBAAiB,EAEjB,oBAAoB,EACpB,cAAc,EACf,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,SAAS;IACxB,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;CACxD;AAED,qBAAa,SAAS;IAIlB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAHzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAY;gBAGnB,MAAM,EAAE,MAAM,EAC/B,SAAS,GAAE,SAA6C;IAKpD,UAAU,CACd,QAAQ,EAAE,YAAY,EACtB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,WAAW,CAAC,EAAE,MAAM,EACpB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,gBAAgB,CAAC;IAoBtB,aAAa,CACjB,QAAQ,EAAE,YAAY,EACtB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,oBAAoB,CAAC;IAU1B,OAAO,CAAC,CAAC,EACb,MAAM,EAAE,KAAK,GAAG,MAAM,EACtB,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,OAAO,EACb,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC;CA2F9B"}
package/dist/client.js ADDED
@@ -0,0 +1,115 @@
1
+ import { KieApiError, KieTransientError, } from './types.js';
2
+ const RETRY_STATUS = new Set([408, 425, 429, 500, 502, 503, 504]);
3
+ const MAX_RETRIES = 3;
4
+ const BASE_DELAY_MS = 500;
5
+ export class KieClient {
6
+ config;
7
+ fetchImpl;
8
+ constructor(config, fetchImpl = globalThis.fetch.bind(globalThis)) {
9
+ this.config = config;
10
+ this.fetchImpl = fetchImpl;
11
+ }
12
+ async createTask(endpoint, model, input, callBackUrl, options = {}) {
13
+ const body = endpoint.buildCreateBody(model, input, callBackUrl);
14
+ const raw = await this.request('POST', endpoint.createPath, body, options);
15
+ const taskId = String((raw.data ?? {}).taskId ?? '');
16
+ if (!taskId) {
17
+ throw new KieApiError(200, raw.code, `kie.ai response missing taskId: ${raw.msg ?? 'no message'}`, raw);
18
+ }
19
+ return { taskId };
20
+ }
21
+ async getTaskStatus(endpoint, taskId, options = {}) {
22
+ const raw = await this.request('GET', endpoint.statusPath(taskId), undefined, options);
23
+ return endpoint.parseStatus(raw);
24
+ }
25
+ async request(method, path, body, options = {}) {
26
+ const url = `${this.config.apiBase}${path}`;
27
+ const headers = {
28
+ Authorization: `Bearer ${this.config.apiKey}`,
29
+ Accept: 'application/json',
30
+ };
31
+ if (method === 'POST') {
32
+ headers['Content-Type'] = 'application/json';
33
+ }
34
+ if (options.idempotencyKey) {
35
+ headers['Idempotency-Key'] = options.idempotencyKey;
36
+ }
37
+ let lastError = null;
38
+ for (let attempt = 0; attempt < MAX_RETRIES; attempt++) {
39
+ const controller = new AbortController();
40
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeoutMs);
41
+ const signal = options.signal
42
+ ? mergeSignals(options.signal, controller.signal)
43
+ : controller.signal;
44
+ try {
45
+ const response = await this.fetchImpl(url, {
46
+ method,
47
+ headers,
48
+ body: body === undefined ? undefined : JSON.stringify(body),
49
+ signal,
50
+ });
51
+ clearTimeout(timeoutId);
52
+ const text = await response.text();
53
+ let parsed;
54
+ try {
55
+ parsed = text ? JSON.parse(text) : { code: response.status, msg: '', data: null };
56
+ }
57
+ catch {
58
+ throw new KieApiError(response.status, null, `kie.ai returned non-JSON body (${response.status}): ${text.slice(0, 200)}`);
59
+ }
60
+ if (!response.ok) {
61
+ if (RETRY_STATUS.has(response.status) && attempt < MAX_RETRIES - 1) {
62
+ lastError = new KieTransientError(`HTTP ${response.status}: ${parsed.msg ?? text}`);
63
+ await sleep(backoffMs(attempt));
64
+ continue;
65
+ }
66
+ throw new KieApiError(response.status, parsed.code ?? null, `kie.ai error (${response.status}): ${parsed.msg ?? 'unknown'}`, parsed);
67
+ }
68
+ if (parsed.code && parsed.code !== 200) {
69
+ throw new KieApiError(response.status, parsed.code, `kie.ai business error (code=${parsed.code}): ${parsed.msg ?? 'unknown'}`, parsed);
70
+ }
71
+ return parsed;
72
+ }
73
+ catch (err) {
74
+ clearTimeout(timeoutId);
75
+ if (err instanceof KieApiError)
76
+ throw err;
77
+ if (err instanceof KieTransientError) {
78
+ lastError = err;
79
+ if (attempt < MAX_RETRIES - 1) {
80
+ await sleep(backoffMs(attempt));
81
+ continue;
82
+ }
83
+ throw err;
84
+ }
85
+ // network errors, timeouts, abort
86
+ lastError = err;
87
+ if (attempt < MAX_RETRIES - 1) {
88
+ await sleep(backoffMs(attempt));
89
+ continue;
90
+ }
91
+ const msg = err instanceof Error ? err.message : String(err);
92
+ throw new KieTransientError(`request failed after ${MAX_RETRIES} attempts: ${msg}`, err);
93
+ }
94
+ }
95
+ throw lastError instanceof Error
96
+ ? lastError
97
+ : new Error('unreachable: retry loop exited without resolution');
98
+ }
99
+ }
100
+ function backoffMs(attempt) {
101
+ return BASE_DELAY_MS * 2 ** attempt + Math.floor(Math.random() * 200);
102
+ }
103
+ function sleep(ms) {
104
+ return new Promise((resolve) => setTimeout(resolve, ms));
105
+ }
106
+ function mergeSignals(a, b) {
107
+ const controller = new AbortController();
108
+ const onAbort = () => controller.abort();
109
+ if (a.aborted || b.aborted)
110
+ controller.abort();
111
+ a.addEventListener('abort', onAbort);
112
+ b.addEventListener('abort', onAbort);
113
+ return controller.signal;
114
+ }
115
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,12 @@
1
+ export interface Config {
2
+ apiKey: string;
3
+ apiBase: string;
4
+ timeoutMs: number;
5
+ outputDir: string;
6
+ dbPath: string;
7
+ pollIntervalMs: number;
8
+ pollMaxMs: number;
9
+ costBudgetUsd: number | null;
10
+ }
11
+ export declare function loadConfig(env: NodeJS.ProcessEnv): Config;
12
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,MAAM;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AASD,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,CAAC,UAAU,GAAG,MAAM,CA4BzD"}
package/dist/config.js ADDED
@@ -0,0 +1,42 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ const DEFAULTS = {
4
+ apiBase: 'https://api.kie.ai/api/v1',
5
+ timeoutMs: 120_000,
6
+ pollIntervalMs: 3_000,
7
+ pollMaxMs: 600_000,
8
+ };
9
+ export function loadConfig(env) {
10
+ const apiKey = env.KIE_API_KEY?.trim();
11
+ if (!apiKey) {
12
+ throw new Error('[kie-mcp] KIE_API_KEY is required. Set it in MCP client env or .env');
13
+ }
14
+ const home = homedir();
15
+ const outputDir = env.KIE_OUTPUT_DIR?.trim() || join(home, '.kie-mcp', 'assets');
16
+ const dbPath = env.KIE_DB_PATH?.trim() || join(home, '.kie-mcp', 'state.db');
17
+ const budget = env.KIE_COST_BUDGET_USD?.trim();
18
+ const costBudgetUsd = budget ? Number(budget) : null;
19
+ if (budget && Number.isNaN(costBudgetUsd)) {
20
+ throw new Error(`[kie-mcp] KIE_COST_BUDGET_USD must be a number, got: ${budget}`);
21
+ }
22
+ return {
23
+ apiKey,
24
+ apiBase: env.KIE_API_BASE?.trim() || DEFAULTS.apiBase,
25
+ timeoutMs: parseIntOr(env.KIE_TIMEOUT_MS, DEFAULTS.timeoutMs),
26
+ outputDir,
27
+ dbPath,
28
+ pollIntervalMs: parseIntOr(env.KIE_POLL_INTERVAL_MS, DEFAULTS.pollIntervalMs),
29
+ pollMaxMs: parseIntOr(env.KIE_POLL_MAX_MS, DEFAULTS.pollMaxMs),
30
+ costBudgetUsd,
31
+ };
32
+ }
33
+ function parseIntOr(raw, fallback) {
34
+ if (!raw)
35
+ return fallback;
36
+ const n = Number(raw);
37
+ if (Number.isNaN(n) || n <= 0) {
38
+ throw new Error(`[kie-mcp] invalid integer: ${raw}`);
39
+ }
40
+ return n;
41
+ }
42
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAajC,MAAM,QAAQ,GAAG;IACf,OAAO,EAAE,2BAA2B;IACpC,SAAS,EAAE,OAAO;IAClB,cAAc,EAAE,KAAK;IACrB,SAAS,EAAE,OAAO;CACnB,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,GAAsB;IAC/C,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC;IACvC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,qEAAqE,CACtE,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,IAAI,EAAE,IAAI,IAAI,CAAC,IAAI,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IAE7E,MAAM,MAAM,GAAG,GAAG,CAAC,mBAAmB,EAAE,IAAI,EAAE,CAAC;IAC/C,MAAM,aAAa,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrD,IAAI,MAAM,IAAI,MAAM,CAAC,KAAK,CAAC,aAAa,CAAC,EAAE,CAAC;QAC1C,MAAM,IAAI,KAAK,CAAC,wDAAwD,MAAM,EAAE,CAAC,CAAC;IACpF,CAAC;IAED,OAAO;QACL,MAAM;QACN,OAAO,EAAE,GAAG,CAAC,YAAY,EAAE,IAAI,EAAE,IAAI,QAAQ,CAAC,OAAO;QACrD,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,cAAc,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC7D,SAAS;QACT,MAAM;QACN,cAAc,EAAE,UAAU,CAAC,GAAG,CAAC,oBAAoB,EAAE,QAAQ,CAAC,cAAc,CAAC;QAC7E,SAAS,EAAE,UAAU,CAAC,GAAG,CAAC,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC;QAC9D,aAAa;KACd,CAAC;AACJ,CAAC;AAED,SAAS,UAAU,CAAC,GAAuB,EAAE,QAAgB;IAC3D,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,CAAC,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IACtB,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,8BAA8B,GAAG,EAAE,CAAC,CAAC;IACvD,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface DownloadResult {
2
+ url: string;
3
+ path: string;
4
+ bytes: number;
5
+ contentType: string | null;
6
+ fromCache: boolean;
7
+ }
8
+ export declare class AssetDownloader {
9
+ private readonly outputDir;
10
+ private readonly fetchImpl;
11
+ constructor(outputDir: string, fetchImpl?: typeof fetch);
12
+ download(url: string, taskId: string, indexInTask: number): Promise<DownloadResult>;
13
+ downloadAll(urls: string[], taskId: string): Promise<DownloadResult[]>;
14
+ }
15
+ //# sourceMappingURL=downloader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloader.d.ts","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"AAqBA,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,SAAS;gBADT,SAAS,EAAE,MAAM,EACjB,SAAS,GAAE,OAAO,KAAyC;IAKxE,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAgDnF,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC;CAO7E"}
@@ -0,0 +1,87 @@
1
+ import { createWriteStream, mkdirSync, existsSync } from 'node:fs';
2
+ import { Readable } from 'node:stream';
3
+ import { pipeline } from 'node:stream/promises';
4
+ import { extname, join } from 'node:path';
5
+ import { URL } from 'node:url';
6
+ const EXT_FROM_CONTENT_TYPE = {
7
+ 'image/png': '.png',
8
+ 'image/jpeg': '.jpg',
9
+ 'image/webp': '.webp',
10
+ 'image/gif': '.gif',
11
+ 'video/mp4': '.mp4',
12
+ 'video/quicktime': '.mov',
13
+ 'video/webm': '.webm',
14
+ 'audio/mpeg': '.mp3',
15
+ 'audio/wav': '.wav',
16
+ 'audio/ogg': '.ogg',
17
+ 'audio/aac': '.aac',
18
+ 'audio/x-m4a': '.m4a',
19
+ };
20
+ export class AssetDownloader {
21
+ outputDir;
22
+ fetchImpl;
23
+ constructor(outputDir, fetchImpl = globalThis.fetch.bind(globalThis)) {
24
+ this.outputDir = outputDir;
25
+ this.fetchImpl = fetchImpl;
26
+ mkdirSync(this.outputDir, { recursive: true });
27
+ }
28
+ async download(url, taskId, indexInTask) {
29
+ const probableExt = guessExtFromUrl(url);
30
+ const suffix = indexInTask === 0 ? '' : `-${indexInTask}`;
31
+ let targetPath = join(this.outputDir, `${taskId}${suffix}${probableExt}`);
32
+ if (existsSync(targetPath)) {
33
+ return {
34
+ url,
35
+ path: targetPath,
36
+ bytes: 0,
37
+ contentType: null,
38
+ fromCache: true,
39
+ };
40
+ }
41
+ const response = await this.fetchImpl(url);
42
+ if (!response.ok || !response.body) {
43
+ throw new Error(`download failed (${response.status}) for ${url}: ${response.statusText}`);
44
+ }
45
+ const contentType = response.headers.get('content-type');
46
+ const ctExt = contentType ? EXT_FROM_CONTENT_TYPE[contentType.split(';')[0].trim()] : null;
47
+ if (ctExt && !probableExt) {
48
+ targetPath = join(this.outputDir, `${taskId}${suffix}${ctExt}`);
49
+ }
50
+ const tmpPath = `${targetPath}.partial`;
51
+ const fileStream = createWriteStream(tmpPath);
52
+ // Node fetch body is a WHATWG ReadableStream; convert.
53
+ const nodeStream = Readable.fromWeb(response.body);
54
+ await pipeline(nodeStream, fileStream);
55
+ // atomic rename
56
+ const { rename, stat } = await import('node:fs/promises');
57
+ await rename(tmpPath, targetPath);
58
+ const st = await stat(targetPath);
59
+ return {
60
+ url,
61
+ path: targetPath,
62
+ bytes: st.size,
63
+ contentType,
64
+ fromCache: false,
65
+ };
66
+ }
67
+ async downloadAll(urls, taskId) {
68
+ const out = [];
69
+ for (let i = 0; i < urls.length; i++) {
70
+ out.push(await this.download(urls[i], taskId, i));
71
+ }
72
+ return out;
73
+ }
74
+ }
75
+ function guessExtFromUrl(url) {
76
+ try {
77
+ const u = new URL(url);
78
+ const ext = extname(u.pathname);
79
+ if (ext && ext.length <= 5)
80
+ return ext.toLowerCase();
81
+ }
82
+ catch {
83
+ // fall through
84
+ }
85
+ return '';
86
+ }
87
+ //# sourceMappingURL=downloader.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"downloader.js","sourceRoot":"","sources":["../src/downloader.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { EndpointSpec } from './types.js';
2
+ export declare const UNIFIED_ENDPOINT: EndpointSpec;
3
+ export declare const VEO_ENDPOINT: EndpointSpec;
4
+ export declare const RUNWAY_ENDPOINT: EndpointSpec;
5
+ export declare const SUNO_ENDPOINT: EndpointSpec;
6
+ export declare const GPT4O_ENDPOINT: EndpointSpec;
7
+ export declare const ENDPOINTS: Record<EndpointSpec['family'], EndpointSpec>;
8
+ //# sourceMappingURL=endpoints.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"endpoints.d.ts","sourceRoot":"","sources":["../src/endpoints.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAGb,MAAM,YAAY,CAAC;AAsBpB,eAAO,MAAM,gBAAgB,EAAE,YA6B9B,CAAC;AAEF,eAAO,MAAM,YAAY,EAAE,YA8B1B,CAAC;AAEF,eAAO,MAAM,eAAe,EAAE,YAiC7B,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,YAwC3B,CAAC;AAEF,eAAO,MAAM,cAAc,EAAE,YA0B5B,CAAC;AAEF,eAAO,MAAM,SAAS,EAAE,MAAM,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,YAAY,CAOlE,CAAC"}