@craftstory/mcp 0.1.1 → 0.1.3

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/CHANGELOG.md ADDED
@@ -0,0 +1,23 @@
1
+ # Changelog
2
+
3
+ ## 0.1.3 - 2026-09-12
4
+ - tool annotations (readOnlyHint / destructiveHint) on every tool; Privacy Policy section in the README
5
+ - MCPB bundle for Claude Desktop (manifest.json, icon) attached to GitHub releases
6
+ - `mcpName` for the official MCP Registry; server.json
7
+
8
+ ## 0.1.2 - 2026-09-12
9
+ - package metadata: public repository and issue tracker on GitHub (itseez3d/craftstory-mcp); CHANGELOG shipped
10
+ - wait_for_job: every request inside the wait is capped by the time left, so the call really returns within timeout_s (+ a few seconds for the result fetch)
11
+ - create_audio_clip rejects ambiguous input (both voice ids, or a file together with text)
12
+ - image_path / file_path accept ~/ and are documented as absolute paths
13
+ - CRAFTSTORY_API_BASE must be https unless CRAFTSTORY_ALLOW_HTTP=1
14
+ - Node >= 20 (matches the dependency tree); prepack builds; SECURITY.md; GitHub Actions CI
15
+
16
+ ## 0.1.1 - 2026-09-12
17
+ - wait_for_job: default 45 s, max 55 s, deadline-aware polling (stays under the 60 s tool-call limit of most MCP clients)
18
+ - every API request has a 25 s HTTP timeout; network errors report their cause
19
+ - removed an internal repository URL from package metadata (0.1.0 is deprecated for that reason)
20
+
21
+ ## 0.1.0 - 2026-09-12
22
+ - first release: 11 tools over the CraftStory public API (models, voices, avatars, audio clips, cost preview,
23
+ CraftStory 2.0 and MiniMax H3 generation, status, result, bounded wait, upscale) and one prompt
package/README.md CHANGED
@@ -5,19 +5,21 @@ Generate talking-avatar videos from Claude, Cursor, Claude Code or any other [MC
5
5
  - **CraftStory 2.0** - a talking video of any length from one photo plus an audio clip (script + voice, your own recording, or a custom avatar). 8-15 minutes per video.
6
6
  - **MiniMax H3** - a clip of up to 15 s from one photo: description-driven with generated sound, or audio-driven with lip-sync. 1-3 minutes.
7
7
 
8
- You need a CraftStory account on a plan with API access and an API key (app: **Account -> API Access**, keys look like `sk-cs-...`). Generations are billed in credits exactly like in the app; failed jobs are refunded.
8
+ You need Node.js 20 or newer, a CraftStory account on a plan with API access, and an API key (app: **Account -> API Access**, keys look like `sk-cs-...`). Generations are billed in credits exactly like in the app; failed jobs are refunded.
9
9
 
10
10
  ## Install
11
11
 
12
12
  ### Claude Code
13
13
 
14
14
  ```bash
15
- claude mcp add craftstory -e CRAFTSTORY_API_KEY=sk-cs-... -- npx -y @craftstory/mcp
15
+ claude mcp add --scope user craftstory -e CRAFTSTORY_API_KEY=sk-cs-... -- npx -y @craftstory/mcp
16
16
  ```
17
17
 
18
+ (`--scope user` makes it available in every project; drop it to install for the current project only.)
19
+
18
20
  ### Claude Desktop
19
21
 
20
- Add to `claude_desktop_config.json` (Settings -> Developer -> Edit Config):
22
+ Add to `claude_desktop_config.json` (Settings -> Developer -> Edit Config), then restart Claude Desktop; the server shows up under the tools icon:
21
23
 
22
24
  ```json
23
25
  {
@@ -33,9 +35,9 @@ Add to `claude_desktop_config.json` (Settings -> Developer -> Edit Config):
33
35
 
34
36
  ### Cursor / other clients
35
37
 
36
- Same shape in `.cursor/mcp.json` (or the client's MCP config): command `npx`, args `["-y", "@craftstory/mcp"]`, env `CRAFTSTORY_API_KEY`.
38
+ Same shape in `~/.cursor/mcp.json` (user-level, so the key never lands in a repository): command `npx`, args `["-y", "@craftstory/mcp"]`, env `CRAFTSTORY_API_KEY`. In a project-level `.cursor/mcp.json` prefer `"CRAFTSTORY_API_KEY": "${env:CRAFTSTORY_API_KEY}"` and keep the real key in your shell environment.
37
39
 
38
- Environment variables: `CRAFTSTORY_API_KEY` (required), `CRAFTSTORY_API_BASE` (optional, default `https://api.craftstory.com/api/v1`).
40
+ Environment variables: `CRAFTSTORY_API_KEY` (required), `CRAFTSTORY_API_BASE` (optional, default `https://api.craftstory.com/api/v1`; must be https unless `CRAFTSTORY_ALLOW_HTTP=1`).
39
41
 
40
42
  ## Tools
41
43
 
@@ -57,27 +59,37 @@ Plus the prompt `talking_video_from_photo` (script + photo) that walks the model
57
59
 
58
60
  ## Example
59
61
 
60
- > Make a 15-second portrait video of the person in `~/photos/anna.jpg` saying "Welcome to our spring collection", calm gestures.
62
+ > Make a portrait video of the person in `/Users/me/photos/portrait.jpg` saying "Welcome to our spring collection", calm gestures.
61
63
 
62
64
  The assistant will: `list_voices` -> `create_audio_clip` -> `wait_for_job(audio-clip)` -> `preview_cost` -> `create_craftstory2_video` (resolution `720_1280`, gestures `calm`) -> `wait_for_job(craftstory-2)` a few times -> `get_job_result` -> the video URL.
63
65
 
64
- Long jobs: `wait_for_job` returns after at most `timeout_s` (max 55 s) plus one in-flight status request (25 s HTTP timeout), which keeps it under the 60 s tool-call limit of most clients in normal conditions. A CraftStory 2.0 video needs several calls; that is by design so agent runtimes do not time out.
66
+ Long jobs: `wait_for_job` returns within `timeout_s` (default 45 s, max 55 s) plus a few seconds for the final result fetch; every request inside it is capped by the time left, so it stays under the 60 s tool-call limit of most clients. A CraftStory 2.0 video needs several calls; that is by design so agent runtimes do not time out. A CraftStory 2.0 video needs several calls; that is by design so agent runtimes do not time out.
65
67
 
66
68
  ## Local files vs URLs
67
69
 
68
- Photos accept `image_url` or `image_path`; recordings and extra references are local paths (uploaded as multipart). Photos up to 20 MB (JPG/PNG/HEIC), audio WAV/MP3/M4A.
70
+ Photos accept `image_url` (JPG/PNG) or `image_path` (absolute path or `~/...`; JPG/PNG/HEIC, up to 20 MB, uploaded as multipart). Recordings and extra references are local paths too. Anything you pass as a path is uploaded to the CraftStory API, so keep your client's tool-approval prompts on (see SECURITY.md).
69
71
 
70
72
  ## Development
71
73
 
72
74
  ```bash
73
75
  npm install
74
76
  npm run build
75
- CRAFTSTORY_API_KEY=sk-cs-... npm run smoke # live check over stdio (add -- --h3 to also render a 5 s H3 clip)
77
+ CRAFTSTORY_API_KEY=sk-cs-... npm run smoke # live check over stdio: lists tools, makes an audio clip, previews cost
78
+ # add "-- --h3" and SMOKE_IMAGE=/path/to/photo.jpg to also render a 5 s MiniMax H3 clip (costs 17 credits)
76
79
  npm test
77
80
  ```
78
81
 
79
82
  Full API reference: https://api.craftstory.com/api/v1/docs/public/ and the curl walkthrough at https://api.craftstory.com/api/v1/docs/samples/curl/.
80
83
 
84
+ ## Privacy Policy
85
+
86
+ This server runs on your machine and keeps no data of its own. It sends your API key and the inputs you pass to tools
87
+ (text, photos, audio, reference files) only to the CraftStory API at `CRAFTSTORY_API_BASE` (default
88
+ `https://api.craftstory.com`), where they are processed under the CraftStory privacy policy:
89
+ https://craftstory.com/privacy/. Generated videos and uploaded files are stored in your CraftStory account and can be
90
+ deleted there or via the API; nothing is retained locally by the server. No analytics or third-party services are
91
+ called by the server itself. Questions: support via https://craftstory.com/contacts/.
92
+
81
93
  ## License
82
94
 
83
95
  MIT
package/dist/client.js CHANGED
@@ -3,9 +3,10 @@
3
3
  * Every method maps 1:1 onto a documented endpoint; no business logic lives here.
4
4
  */
5
5
  import { readFile } from "node:fs/promises";
6
+ import { homedir } from "node:os";
6
7
  import { basename } from "node:path";
7
8
  export const DEFAULT_BASE = "https://api.craftstory.com/api/v1";
8
- export const USER_AGENT = "craftstory-mcp/0.1.1";
9
+ export const USER_AGENT = "craftstory-mcp/0.1.3";
9
10
  /** Per-request HTTP timeout; keeps every tool call well under MCP clients' ~60 s limit. */
10
11
  export const REQUEST_TIMEOUT_MS = 25_000;
11
12
  export class ApiError extends Error {
@@ -24,8 +25,9 @@ export async function fileOrUrl(input, field) {
24
25
  if (input.url)
25
26
  return { url: input.url };
26
27
  if (input.path) {
27
- const bytes = await readFile(input.path);
28
- return { blob: new Blob([bytes]), name: basename(input.path) };
28
+ const path = input.path.startsWith("~/") ? homedir() + input.path.slice(1) : input.path;
29
+ const bytes = await readFile(path);
30
+ return { blob: new Blob([bytes]), name: basename(path) };
29
31
  }
30
32
  throw new Error(`${field}: a URL or a local file path is required`);
31
33
  }
@@ -36,9 +38,14 @@ export class CraftStoryClient {
36
38
  constructor(opts) {
37
39
  this.opts = opts;
38
40
  this.base = (opts.baseUrl ?? DEFAULT_BASE).replace(/\/+$/, "");
41
+ // The bearer key and uploads travel to this host: plaintext HTTP only on explicit request.
42
+ if (!this.base.startsWith("https://") && process.env.CRAFTSTORY_ALLOW_HTTP !== "1") {
43
+ throw new Error(`CRAFTSTORY_API_BASE must use https:// (got ${this.base}); set CRAFTSTORY_ALLOW_HTTP=1 to override for local testing`);
44
+ }
39
45
  this.fetchImpl = opts.fetchImpl ?? fetch;
40
46
  }
41
- async request(method, path, body) {
47
+ /** timeoutMs caps this one request (default REQUEST_TIMEOUT_MS); wait_for_job passes its remaining budget. */
48
+ async request(method, path, body, timeoutMs = REQUEST_TIMEOUT_MS) {
42
49
  const headers = { Authorization: `Bearer ${this.opts.apiKey}`, "User-Agent": USER_AGENT, Accept: "application/json" };
43
50
  let payload;
44
51
  if (body instanceof FormData)
@@ -49,12 +56,12 @@ export class CraftStoryClient {
49
56
  }
50
57
  let res;
51
58
  try {
52
- res = await this.fetchImpl(`${this.base}${path}`, { method, headers, body: payload, signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
59
+ res = await this.fetchImpl(`${this.base}${path}`, { method, headers, body: payload, signal: AbortSignal.timeout(Math.max(1000, timeoutMs)) });
53
60
  }
54
61
  catch (e) {
55
62
  // Node's fetch hides the reason behind "fetch failed"; surface the cause (DNS, TLS, reset...).
56
63
  if (e.name === "TimeoutError")
57
- throw new Error(`Timeout after ${REQUEST_TIMEOUT_MS / 1000}s calling ${method} ${path}`);
64
+ throw new Error(`Timeout after ${Math.round(Math.max(1000, timeoutMs) / 1000)}s calling ${method} ${path}`);
58
65
  const cause = e.cause;
59
66
  throw new Error(`Network error calling ${method} ${path}: ${cause?.code ?? ""} ${cause?.message ?? e.message}`.trim());
60
67
  }
@@ -70,8 +77,8 @@ export class CraftStoryClient {
70
77
  throw new ApiError(res.status, data, describeError(res.status, data));
71
78
  return data;
72
79
  }
73
- get(path) {
74
- return this.request("GET", path);
80
+ get(path, timeoutMs) {
81
+ return this.request("GET", path, undefined, timeoutMs);
75
82
  }
76
83
  post(path, body) {
77
84
  return this.request("POST", path, body);
@@ -163,11 +170,11 @@ export class CraftStoryClient {
163
170
  jobPath(kind, id) {
164
171
  return kind === "audio-clip" ? `/audio/clips/${id}/` : `/${kind}/${id}/`;
165
172
  }
166
- getStatus(kind, id) {
167
- return this.get(`${this.jobPath(kind, id)}status/`);
173
+ getStatus(kind, id, timeoutMs) {
174
+ return this.get(`${this.jobPath(kind, id)}status/`, timeoutMs);
168
175
  }
169
- getResult(kind, id) {
170
- return this.get(this.jobPath(kind, id));
176
+ getResult(kind, id, timeoutMs) {
177
+ return this.get(this.jobPath(kind, id), timeoutMs);
171
178
  }
172
179
  }
173
180
  /** Terminal-state classification shared by wait_for_job and the tool descriptions. */
package/dist/server.js CHANGED
@@ -14,9 +14,10 @@ const JOB_KINDS = ["craftstory-2", "minimax-h3", "audio-clip"];
14
14
  const text = (data) => ({ content: [{ type: "text", text: typeof data === "string" ? data : JSON.stringify(data, null, 2) }] });
15
15
  const fail = (err) => ({ isError: true, content: [{ type: "text", text: err instanceof Error ? err.message : String(err) }] });
16
16
  export function buildServer(client) {
17
- const server = new McpServer({ name: "craftstory", version: "0.1.1" });
17
+ const server = new McpServer({ name: "craftstory", version: "0.1.3" });
18
18
  server.registerTool("list_models", {
19
19
  title: "List CraftStory video models",
20
+ annotations: { readOnlyHint: true, openWorldHint: true },
20
21
  description: "Catalogue of the video models behind this server with their status, modes, limits and credit prices. " +
21
22
  "Two models today: craftstory-2 (a talking video of any length from one photo plus an audio clip; 8-15 min) " +
22
23
  "and minimax-h3 (a clip of up to 15 s from one photo, either description-driven with generated sound or audio-driven with lip-sync; 1-3 min). " +
@@ -32,6 +33,7 @@ export function buildServer(client) {
32
33
  });
33
34
  server.registerTool("list_voices", {
34
35
  title: "List voices for text-to-speech",
36
+ annotations: { readOnlyHint: true, openWorldHint: true },
35
37
  description: "Library voices (id, name, language, gender) usable as voice_id in create_audio_clip. " +
36
38
  "With include_cloned=true also returns the account's own cloned voices, usable as voice_user_id. Voices are cloned in the CraftStory app, not via the API.",
37
39
  inputSchema: { include_cloned: z.boolean().optional().describe("Also return the account's cloned voices (default false)") },
@@ -47,6 +49,7 @@ export function buildServer(client) {
47
49
  });
48
50
  server.registerTool("list_avatars", {
49
51
  title: "List custom avatars (and their scenes)",
52
+ annotations: { readOnlyHint: true, openWorldHint: true },
50
53
  description: "Custom avatars trained in the CraftStory app that craftstory-2 can generate with (pass an id as avatar_id). " +
51
54
  "Each avatar may carry a default voice {id, voice_kind}: voice_kind 'user' means send it as voice_user_id, 'library' as voice_id in create_audio_clip. " +
52
55
  "Pass avatar_id to list that avatar's scenes; a scene id can replace the photo (scene_id) in create_craftstory2_video.",
@@ -61,6 +64,7 @@ export function buildServer(client) {
61
64
  });
62
65
  server.registerTool("create_audio_clip", {
63
66
  title: "Create an audio clip (speech from text, or upload a recording)",
67
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
64
68
  description: "The soundtrack every video model takes as input. Either text (up to 2000 characters) plus exactly one voice (voice_id from list_voices, or voice_user_id for a cloned voice), " +
65
69
  "or file_path to upload a local WAV/MP3/M4A recording. Returns the clip id; it is ready when wait_for_job(model='audio-clip') reports done (usually seconds). " +
66
70
  "Longer scripts: create several clips and pass all ids to create_craftstory2_video in order.",
@@ -72,10 +76,14 @@ export function buildServer(client) {
72
76
  },
73
77
  }, async ({ text: script, voice_id, voice_user_id, file_path }) => {
74
78
  try {
79
+ if (file_path && (script || voice_id || voice_user_id))
80
+ throw new Error("Pass either file_path or text + one voice, not both");
75
81
  if (file_path)
76
82
  return text(await client.createAudioClipFromFile(file_path));
77
83
  if (!script)
78
84
  throw new Error("Pass text (with a voice) or file_path");
85
+ if (voice_id && voice_user_id)
86
+ throw new Error("Pass exactly one of voice_id or voice_user_id");
79
87
  if (!voice_id && !voice_user_id)
80
88
  throw new Error("Pass voice_id (library voice) or voice_user_id (cloned voice) with text");
81
89
  return text(await client.createAudioClipFromText(script, voice_id ? { voice_id } : { voice_user_id }));
@@ -86,6 +94,7 @@ export function buildServer(client) {
86
94
  });
87
95
  server.registerTool("preview_cost", {
88
96
  title: "Estimate the credit cost of a CraftStory 2.0 video",
97
+ annotations: { readOnlyHint: true, openWorldHint: true },
89
98
  description: "Credits a craftstory-2 job would cost for the given audio clips and settings, without creating anything. " +
90
99
  "Rate per second of audio: 480p 2.2 (2 with lipsync_mode=empty), 720p 3.3 (3 with empty); rounded up per job. MiniMax H3 is a flat 3.3 credits per billed second.",
91
100
  inputSchema: {
@@ -103,13 +112,14 @@ export function buildServer(client) {
103
112
  });
104
113
  server.registerTool("create_craftstory2_video", {
105
114
  title: "Create a CraftStory 2.0 talking video (photo + audio)",
115
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
106
116
  description: "Start a craftstory-2 generation: a photo of a person (image_url or image_path, or a custom avatar scene via scene_id) speaks the given audio clips with lip-sync, gestures and natural motion; any length. " +
107
117
  "resolution is WIDTH_HEIGHT (480_832 / 720_1280 portrait, 832_480 / 1280_720 landscape); 1080p is available afterwards via upscale_video. " +
108
118
  "Credits are charged on create (see preview_cost) and refunded if the job fails. Returns the job id and initial status; generation takes 8-15 minutes, " +
109
119
  "so call wait_for_job(model='craftstory-2') repeatedly until it reports done, then get_job_result for the video URL.",
110
120
  inputSchema: {
111
121
  image_url: z.string().url().optional().describe("Public URL of the photo (JPG/PNG)"),
112
- image_path: z.string().optional().describe("Local path of the photo to upload (<= 20 MB)"),
122
+ image_path: z.string().optional().describe("Absolute local path of the photo to upload (JPG/PNG/HEIC, <= 20 MB); ~/ is expanded"),
113
123
  scene_id: z.string().uuid().optional().describe("Custom avatar scene id (from list_avatars) used instead of a photo"),
114
124
  avatar_id: z.string().uuid().optional().describe("Custom avatar id (from list_avatars); its trained model drives identity"),
115
125
  audio_clip_ids: z.array(z.string().uuid()).min(1).describe("Audio clip ids (from create_audio_clip), played in order"),
@@ -144,6 +154,7 @@ export function buildServer(client) {
144
154
  });
145
155
  server.registerTool("create_minimax_h3_video", {
146
156
  title: "Create a MiniMax H3 clip (up to 15 s)",
157
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
147
158
  description: "Start a minimax-h3 generation from one photo. mode='basic': user_prompt (scene description) + requested_duration_s (5-15); the model animates the photo and generates the soundtrack itself. " +
148
159
  "mode='reference': one audio_clip_id drives the clip with lip-sync (first 15 s billed); user_prompt is optional; up to 8 extra image / 3 video / 2 audio reference_files with reference_captions keep a product or background consistent. " +
149
160
  "Output is 768 px on the short side, orientation follows the photo. Cost 3.3 credits per billed second, charged on create. Returns the job id; call wait_for_job(model='minimax-h3') until done (1-3 min).",
@@ -184,6 +195,7 @@ export function buildServer(client) {
184
195
  });
185
196
  server.registerTool("get_job_status", {
186
197
  title: "Get a job's status",
198
+ annotations: { readOnlyHint: true, openWorldHint: true },
187
199
  description: "Status of a video job or audio clip: status, status_percentage, status_failed, credits_refunded. Terminal states: done; failed*, rejected_* and not_pass_moderation (audio) are failures. " +
188
200
  "Prefer wait_for_job, which polls for you.",
189
201
  inputSchema: { model: z.enum(JOB_KINDS), id: z.string().uuid() },
@@ -197,6 +209,7 @@ export function buildServer(client) {
197
209
  });
198
210
  server.registerTool("get_job_result", {
199
211
  title: "Get a finished job (video URL and details)",
212
+ annotations: { readOnlyHint: true, openWorldHint: true },
200
213
  description: "Full record of a job. For craftstory-2 the video is in `video`, for minimax-h3 in `video_url`, for audio clips in `file`; all are signed URLs valid for 7 days (call again for a fresh link). Also returns the parameters used and the credits charged.",
201
214
  inputSchema: { model: z.enum(JOB_KINDS), id: z.string().uuid() },
202
215
  }, async ({ model, id }) => {
@@ -209,6 +222,7 @@ export function buildServer(client) {
209
222
  });
210
223
  server.registerTool("wait_for_job", {
211
224
  title: "Wait for a job (bounded polling)",
225
+ annotations: { readOnlyHint: true, openWorldHint: true },
212
226
  description: "Polls a job's status every few seconds for up to timeout_s (default 45, max 55 - most MCP clients cut a tool call at 60 s) and returns as soon as it is terminal. " +
213
227
  "If it returns state='running', call it again - craftstory-2 jobs take 8-15 minutes, minimax-h3 1-3 minutes, audio clips seconds. Reports progress notifications when the client supports them.",
214
228
  inputSchema: {
@@ -220,11 +234,15 @@ export function buildServer(client) {
220
234
  const deadline = Date.now() + (timeout_s ?? 45) * 1000;
221
235
  const token = extra._meta?.progressToken;
222
236
  let last;
237
+ const running = () => text({ state: "running", ...last, hint: "still running - call wait_for_job again" });
223
238
  try {
224
239
  while (true) {
225
- if (Date.now() >= deadline && last)
226
- return text({ state: "running", ...last, hint: "still running - call wait_for_job again" });
227
- last = await client.getStatus(model, id);
240
+ // Every request is capped by the time left, so the call returns within timeout_s
241
+ // (plus at most a few seconds for the final result fetch).
242
+ const budget = deadline - Date.now();
243
+ if (budget <= 0 && last)
244
+ return running();
245
+ last = await client.getStatus(model, id, Math.min(25_000, Math.max(3_000, budget)));
228
246
  const state = classify(last.status);
229
247
  if (token !== undefined) {
230
248
  await extra.sendNotification({
@@ -233,12 +251,20 @@ export function buildServer(client) {
233
251
  });
234
252
  }
235
253
  if (state !== "running") {
236
- const result = state === "done" ? await client.getResult(model, id) : undefined;
254
+ let result;
255
+ if (state === "done") {
256
+ try {
257
+ result = await client.getResult(model, id, Math.min(10_000, Math.max(3_000, deadline - Date.now())));
258
+ }
259
+ catch {
260
+ return text({ state, ...last, hint: "finished - call get_job_result for the video URL" });
261
+ }
262
+ }
237
263
  return text({ state, ...last, ...(result ? { result } : {}) });
238
264
  }
239
265
  const remaining = deadline - Date.now();
240
266
  if (remaining <= 0)
241
- return text({ state: "running", ...last, hint: "still running - call wait_for_job again" });
267
+ return running();
242
268
  await new Promise((r) => setTimeout(r, Math.min(model === "audio-clip" ? 2000 : 5000, remaining)));
243
269
  }
244
270
  }
@@ -248,6 +274,7 @@ export function buildServer(client) {
248
274
  });
249
275
  server.registerTool("upscale_video", {
250
276
  title: "Upscale a finished video (new job)",
277
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: true },
251
278
  description: "Creates a NEW job with the upscaled result; the original stays. craftstory-2: only 720p sources, resolution 1080_1920 (from 720_1280) or 1920_1080 (from 1280_720). minimax-h3: always 2x, no resolution needed. " +
252
279
  "Costs 0.2 credits per second. Poll the returned id with wait_for_job.",
253
280
  inputSchema: {
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "@craftstory/mcp",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
+ "mcpName": "io.github.itseez3d/craftstory-mcp",
4
5
  "description": "MCP server for the CraftStory API: talking-avatar videos from a photo (CraftStory 2.0) and short clips with generated sound (MiniMax H3), from Claude, Cursor and other MCP clients.",
5
6
  "type": "module",
6
7
  "bin": {
@@ -10,16 +11,18 @@
10
11
  "files": [
11
12
  "dist",
12
13
  "README.md",
14
+ "CHANGELOG.md",
13
15
  "LICENSE"
14
16
  ],
15
17
  "engines": {
16
- "node": ">=18"
18
+ "node": ">=20"
17
19
  },
18
20
  "scripts": {
19
21
  "build": "tsc -p tsconfig.json && chmod +x dist/index.js",
20
22
  "start": "node dist/index.js",
21
23
  "test": "node --import tsx --test test/*.test.ts",
22
- "smoke": "tsx test/smoke.ts"
24
+ "smoke": "tsx test/smoke.ts",
25
+ "prepack": "npm run build"
23
26
  },
24
27
  "keywords": [
25
28
  "mcp",
@@ -32,7 +35,7 @@
32
35
  "minimax"
33
36
  ],
34
37
  "license": "MIT",
35
- "homepage": "https://craftstory.com",
38
+ "homepage": "https://github.com/itseez3d/craftstory-mcp#readme",
36
39
  "dependencies": {
37
40
  "@modelcontextprotocol/sdk": "^1.30.0",
38
41
  "zod": "^3.24.0"
@@ -43,6 +46,10 @@
43
46
  "typescript": "^5.6.0"
44
47
  },
45
48
  "bugs": {
46
- "url": "https://api.craftstory.com/api/v1/docs/public/"
49
+ "url": "https://github.com/itseez3d/craftstory-mcp/issues"
50
+ },
51
+ "repository": {
52
+ "type": "git",
53
+ "url": "git+https://github.com/itseez3d/craftstory-mcp.git"
47
54
  }
48
55
  }