Skip to main content
Glama
skeetmtp
by skeetmtp
README.md
# seedance-mcp

An MCP server that lets Claude Code generate video with **BytePlus ModelArk Dreamina Seedance 2.5**.

Ask Claude Code for a video in plain language; it submits the task to BytePlus, polls until the
render finishes, and hands back the video URL with the metadata BytePlus reports.

---

## 1. What it does

Six tools over MCP stdio:

| Tool | Purpose |
| --- | --- |
| `seedance_create_video` | Submit a generation task. Returns a task ID immediately — generation is asynchronous. |
| `seedance_get_video` | One status check for a task; returns the video URL once it succeeds. |
| `seedance_wait_for_video` | Poll with backoff until the task succeeds, fails, or times out. |
| `seedance_download_video` | Save a finished video to a local file before its 24-hour URL expires. |
| `seedance_cancel_video` | Cancel a queued task, or delete a finished task's record. |
| `seedance_list_tasks` | List recent tasks, filterable by status and model. |

It handles the parts that are easy to get wrong: encoding local images and audio into the
Base64 data-URI form the API expects, enforcing the per-model parameter limits *before* a request
is sent, retrying transient failures with backoff, and keeping the API key out of every log line
and error message.

## 2. Prerequisites

- **Python 3.11+**
- **[uv](https://docs.astral.sh/uv/)** — `brew install uv` or `curl -LsSf https://astral.sh/uv/install.sh | sh`
- **Claude Code** 2.x
- A **BytePlus ModelArk account** with an API key and the Seedance model activated.

## 3. BytePlus configuration

1. Create an API key: [ModelArk console → API keys](https://console.byteplus.com/ark).
2. **Activate the model.** Seedance 2.5 is not enabled by default. BytePlus requires one of:
   - account balance above USD 30, or
   - an AI Savings Plan at the USD 30 tier or above, or
   - a Seedance resource pack with quota remaining.

   Activate under *ModelArk → Model activation → Computer Vision*. Without this, task creation
   fails with an authorization error even though the key itself is valid.
3. Note your **region**. The default base URL below is `ap-southeast` (Singapore). If your account
   is provisioned elsewhere, set `BYTEPLUS_BASE_URL` accordingly — a task created in one region is
   not visible from another.

## 4. Installation

```bash
git clone <this repo> ~/code/seedance-mcp   # or just use the directory you already have
cd ~/code/seedance-mcp
uv sync
```

Verify:

```bash
uv run pytest -q          # 93 tests, all offline against mocked HTTP
uv run ruff check .
```

## 5. `.env` setup

```bash
cp .env.example .env
```

Then fill in **one required** variable:

```env
BYTEPLUS_API_KEY=your-modelark-api-key
```

Everything else is optional and already defaulted:

```env
BYTEPLUS_BASE_URL=https://ark.ap-southeast.bytepluses.com/api/v3
SEEDANCE_MODEL_ID=dreamina-seedance-2-5-260628
```

`.env` is gitignored. The key is read from the environment only — it is never written to disk by
this server, never logged, and is scrubbed from API error messages before they reach Claude.

## 6. Choosing the Seedance 2.5 model ID

Seedance 2.5 is a **shared, universally available model ID** — you do not need to create a
dedicated endpoint. The default is:

```
dreamina-seedance-2-5-260628
```

Note the `dreamina-` prefix. This is a real inconsistency in BytePlus's naming: the 2.x Dreamina
models carry it, while the 1.x IDs do not (`seedance-1-5-pro-251215`). Copying a 1.x-shaped ID for
2.5 is the most common cause of a "model not found" error.

Confirm the current ID for your account in the
[ModelArk model list](https://docs.byteplus.com/en/docs/ModelArk/1330310).

**If you prefer a dedicated endpoint** (for per-endpoint rate limits, prepaid billing, or
monitoring), create one in the console and put its endpoint ID in `SEEDANCE_MODEL_ID` instead:

```env
SEEDANCE_MODEL_ID=ep-20260817120000-abcde
SEEDANCE_MODEL_PROFILE=seedance-2.5
```

`SEEDANCE_MODEL_PROFILE` is needed only in that case: an `ep-...` ID says nothing about which model
sits behind it, so without it the server cannot validate parameters locally and will pass
everything through to the API for validation instead.

## 7. Register with Claude Code

From this directory (use the absolute path — Claude Code launches the server from arbitrary
working directories):

```bash
claude mcp add \
  --transport stdio \
  --scope user \
  byteplus-seedance \
  -- uv --directory /Users/alban/code/seedance-mcp run python -m seedance_mcp
```

`--scope user` makes it available in every project. Use `--scope project` to share it with a repo's
collaborators via `.mcp.json`, or omit `--scope` for the current project only.

The server reads `.env` from its own directory, so no `-e` flags are needed. If you would rather
pass the key explicitly:

```bash
claude mcp add --scope user byteplus-seedance \
  -e BYTEPLUS_API_KEY=your-key \
  -- uv --directory /Users/alban/code/seedance-mcp run python -m seedance_mcp
```

## 8. Verification

```bash
claude mcp list
```

Expect a line like:

```
byteplus-seedance: uv --directory /Users/alban/code/seedance-mcp run python -m seedance_mcp - ✓ Connected
```

Then inside Claude Code, `/mcp` lists the server and its six tools. Ask it:

> List my recent Seedance tasks.

That exercises authentication and connectivity without spending generation credits — an empty list
is a success. If the key is wrong you get an explicit `HTTP 401` message instead.

For an end-to-end check that actually renders a file, use the minimum-cost recipe in §10.

## 9. Example Claude Code prompts

```text
Generate a 10-second 1080p cinematic video of Tokyo at night using Seedance 2.5.
```

```text
Use ./assets/reference.png as the visual reference and generate a slow cinematic push-in shot.
```

```text
Create the video and wait until generation finishes.
```

```text
Generate a 15-second 9:16 vertical clip of a surfer at sunrise, no audio, and give me the URL.
```

```text
Use ./assets/first.png as the first frame and ./assets/last.png as the last frame,
6 seconds, and wait for it.
```

```text
Check the status of task cgt-20260817... and download the video if it's ready.
```

```text
Download task cgt-20260818061514-8t2lv into ./renders/ and keep the last frame too.
```

```text
Cancel task cgt-20260817... — I queued the wrong prompt.
```

## 10. Cost and timing

Generation is billed per second of output, scaled by resolution and model. The cheapest way to
prove the pipeline works is a 4-second 480p clip — 4s is the shortest length any Seedance 2.x model
accepts.

**Free check**, no generation credits spent:

```text
List my recent Seedance tasks.
```

**Cheapest real generation.** Seedance 2.0 mini is the least expensive model on the account; during
the promotion running to 7 Sep 2026 its 720p output starts around USD 0.03/second, and 480p sits
below that:

```text
Using Seedance model seedance-2-0-mini-260615, generate a 4-second 480p video, 16:9, no audio,
prompt: "a red balloon floating up against a blue sky". Then wait for it and give me the URL.
```

**Cheapest test of the Seedance 2.5 default path** — worth running separately, since 2.5 is a
different model activation and a different price tier:

```text
Generate a 4-second 480p Seedance 2.5 video, 16:9, no audio,
prompt: "a red balloon floating up against a blue sky". Wait for it and give me the URL.
```

### Measured baseline

From one real Seedance 2.5 text-to-video run on 2026-08-18:

| Output | Wall-clock | Reported usage |
| --- | --- | --- |
| 4s · 480p · 16:9 · 24 fps · silent | ~105 s | 38,830 tokens |

That is the floor: the shortest duration at the lowest resolution with no reference media. It also
sets expectations for `seedance_wait_for_video`, whose 900-second default timeout is sized for jobs
an order of magnitude heavier than this one.

Scaling from that baseline is extrapolation, not measurement — usage tracks output seconds and
pixel count, so a 10-second 1080p clip is roughly 2.5× the seconds and ~5× the pixels, i.e. of
order 10× this run. Treat it as a planning estimate and confirm against your own billing.

### The `duration` cost trap

Seedance 2.5 defaults `duration` to **-1**, which lets the model pick any length **up to 30
seconds**. Since billing is per second of output, a request that doesn't state a duration can cost
around 7× an intended 4-second test. State the number of seconds explicitly in any cost-sensitive
run — the tool passes it straight through, and 4 is the floor.

Two smaller notes: 480p and 720p are excluded from the current 2.5 promotional discount (only 1080p
is discounted), so 480p remains cheapest in absolute terms — the discount simply doesn't apply to
it. And `no audio` mainly shortens generation time; nothing in the documentation shows it reduces
the price.

## 11. Troubleshooting

| Symptom | Cause and fix |
| --- | --- |
| `BYTEPLUS_API_KEY is not set` | No `.env` next to the project, or an empty value. The server resolves config on the first tool call, so this appears as a tool error rather than a startup failure. |
| `HTTP 401` | Wrong or revoked key, or a key from a different BytePlus account than the one holding the model activation. |
| `HTTP 404` on a task ID | The task was created in a different region, or it is older than 7 days (BytePlus purges task records after 7 days). |
| Model-not-found on create | `SEEDANCE_MODEL_ID` is wrong — check the `dreamina-` prefix — or Seedance 2.5 is not activated on the account (see §3). |
| `HTTP 429` | Rate limited. The client already retries with backoff and honours `Retry-After`; persistent 429s mean your account RPM is exhausted. |
| `Local video files cannot be uploaded` | Expected. BytePlus accepts reference video only as a public URL or `asset://` ID. Host the file first. |
| Task fails with `InvalidParameter.TaskTypeConstraint` | Seedance 2.5 inferred a different task type than your parameters allow. Set `omni_reference_task_type` explicitly to `edit` or `extend` so validation happens at submit time. |
| `video_url` returns 403 | Output URLs expire **24 hours** after completion, and Seedance 2.5 URLs allow at most 100 downloads. Re-generate, or configure BytePlus TOS data subscription for durable storage. Use `seedance_download_video` to save files inside the window. |
| `File already exists` on download | Guard against clobbering a previous render. Pass `overwrite: true`, or give a different `output_path`. |
| `Refusing to download from <host>` | `seedance_download_video` only fetches BytePlus-hosted output. Fetch other URLs outside this server. |
| Server shows as failed in `claude mcp list` | Run the command by hand — `uv --directory /path run python -m seedance_mcp` — and read stderr. Usually a stale venv; `uv sync` fixes it. |

## 12. Supported Seedance 2.5 features

**Task types** (mutually exclusive — BytePlus rejects mixtures):

- **Text-to-video** — prompt only.
- **Image-to-video** — `first_frame`, optionally plus `last_frame`. The output starts and ends on
  exactly those images.
- **Omni reference-to-video** — up to **30 reference images**, **10 reference videos** and
  **10 audio clips**, with audio-only input allowed. Cite assets in the prompt as `@Image 1`,
  `@Video 2`. Covers three sub-tasks: reference-to-video, **video editing**, and **video
  extension** — steer them with `omni_reference_task_type`.

**Output controls**

| Parameter | Seedance 2.5 values |
| --- | --- |
| `resolution` | `480p`, `720p` (default), `1080p` |
| `ratio` | `16:9`, `4:3`, `1:1`, `3:4`, `9:16`, `21:9`, `adaptive` (default) |
| `duration` | 4–30 seconds, or `-1` to let the model choose (default) |
| `generate_audio` | `true` (default) — synchronised speech, effects and music |
| `watermark` | `false` (default) |
| `return_last_frame` | `false` (default) — returns the closing frame as PNG to chain clips |
| `omni_reference_task_type` | `auto`, `edit`, `extend` |
| `service_tier` | `default` (online) or `flex` (cheaper offline inference) |

**Media inputs**

| Type | Formats | Per-file limit | Local file support |
| --- | --- | --- | --- |
| Image | jpeg, png, webp, bmp, tiff, gif, heic, heif | 30 MB | ✅ inlined as Base64 |
| Audio | wav, mp3 | 15 MB | ✅ inlined as Base64 |
| Video | mp4, mov | 200 MB | ❌ public URL or `asset://` only |

Prompts work in English, Spanish, Indonesian, Portuguese, Japanese, Malay, Thai, Arabic,
Vietnamese and Korean. Keep them under ~1000 English words.

**Saving output.** `seedance_download_video` takes a task ID, looks up the current URL itself, and
streams the file to disk. Defaults to `./<task_id>.mp4`; pass a file path or an existing directory
as `output_path`. It never overwrites without `overwrite: true`, cleans up partial files if a
download breaks, and can also fetch the closing PNG with `include_last_frame` when the task was
created with `return_last_frame`. Two deliberate restrictions: downloads go over its own
**unauthenticated** HTTP client, so the ModelArk key is never sent to the storage host, and the URL
host must end in `.volces.com`, `.bytepluses.com` or `.byteplus.com` — this is a Seedance output
fetcher, not a general-purpose downloader.

The server also targets older models via the `model` tool argument or `SEEDANCE_MODEL_ID` —
Seedance 2.0 / 2.0 fast / 2.0 mini, 1.5 pro, 1.0 pro and 1.0 pro fast — validating each against its
own limits (for example 4K is valid on 2.0 but not on 2.5).

## 13. Known API limitations

- **Generation is asynchronous.** Nothing returns a video synchronously; a 5–10 second clip
  typically takes a few minutes, longer at 1080p.
- **No settable `seed` or `camera_fixed` on Seedance 2.5.** The current API reference lists both as
  *input* parameters for Seedance 1.5 pro, 1.0 pro and 1.0 pro fast only. This server rejects them
  for 2.5 with an explicit message rather than silently dropping them. Express camera behaviour in
  the prompt instead. *(Older Seedance 1.x tutorials and third-party examples still show these
  parameters — they no longer apply to 2.5.)*

  Observed in a real 2.5 run: the task **response** still reports a `seed` (surfaced as
  `VideoResult.seed`, e.g. `80969`), because the model picks one internally. So you can see which
  seed produced a clip, but you cannot ask for it back — **2.5 generations are not reproducible**.
- **No `frames` on Seedance 2.5.** Sub-second durations via frame count are a 1.0 pro feature.
- **Local video cannot be uploaded.** Images and audio have a Base64 form; video does not.
- **64 MB request-body ceiling.** Inlining several large images will hit it; the server checks
  before sending and tells you to switch to URLs.
- **Real human faces are restricted.** Seedance 2.x rejects reference images and videos containing
  real human faces unless they are a prior Seedance output from your own account (within 30 days),
  a preset digital character, or an authorised real-person asset.
- **Only queued tasks can be cancelled.** Once a task is running, it runs to completion.
- **Output URLs live 24 hours**, with a 100-download cap on Seedance 2.5. Both limits are baked
  into the signed URL itself — a returned link carries `X-Tos-Expires=86400` and
  `X-Tos-Max-Requests=100`. There is no re-issue endpoint, and `seedance_list_tasks` can only hand
  back a URL that is still inside that window. Use `seedance_download_video` to save anything worth
  keeping; once the window closes the only remedy is to generate again.
- **Task records live 7 days.**
- **Reference-media duration limits are not checked locally.** Per-clip (2–30s) and total (30s)
  limits for reference video and audio need media probing; the server does not add a decoder
  dependency for it, so BytePlus enforces those and reports them as task errors.
- **Prices and constraints change.** The capability table in `src/seedance_mcp/capabilities.py` was
  transcribed from the BytePlus docs on 2026-08-17; re-check it if BytePlus ships a new model
  revision.

## 14. Project layout

```
seedance-mcp/
├── pyproject.toml
├── README.md
├── .env.example
├── .gitignore
├── src/seedance_mcp/
│   ├── __init__.py
│   ├── __main__.py        # stdio entry point
│   ├── server.py          # the six MCP tools
│   ├── client.py          # BytePlus HTTP client: retries, error parsing
│   ├── payload.py         # request building + validation
│   ├── capabilities.py    # per-model limits from the official docs
│   ├── media.py           # local file -> data URI, with validation
│   ├── models.py          # typed request/response models
│   ├── config.py          # environment configuration
│   └── errors.py          # error types + secret redaction
└── tests/
```

`capabilities.py`, `payload.py` and `media.py` are additions to the layout sketched in the brief:
the documented per-model constraint table, the request builder and the media handling each carry
real logic and their own tests, and folding them into `server.py` or `models.py` would have made
both hard to read.

## 15. Sources

Every API detail above was verified against current official BytePlus documentation:

- [Create a video generation task](https://docs.byteplus.com/en/docs/ModelArk/1520757)
- [Retrieve a video generation task](https://docs.byteplus.com/en/docs/ModelArk/1521309)
- [List video generation tasks](https://docs.byteplus.com/en/docs/ModelArk/Querying_a_list_of_video_generation_tasks)
- [Cancel or delete a video generation task](https://docs.byteplus.com/en/docs/ModelArk/1521720)
- [Dreamina Seedance 2.5 tutorial](https://docs.byteplus.com/en/docs/ModelArk/2607688)
- [Model list](https://docs.byteplus.com/en/docs/ModelArk/1330310)
- [Video generation API tutorial](https://docs.byteplus.com/en/docs/ModelArk/2298881)

TDQS

A4.6/5.0

Scored across 6 tools

Disambiguation5/5

Each tool serves a distinct purpose: creating, checking status, waiting, downloading, canceling, and listing tasks. There is no overlap, and the descriptions clearly differentiate their use cases.

Naming Consistency5/5

All tools follow a consistent verb_noun pattern prefixed with 'seedance_': create_video, get_video, wait_for_video, download_video, cancel_video, list_tasks. No mixing of conventions.

Tool Count5/5

With 6 tools, the set is well-scoped for a video generation service. It covers the necessary operations without being too sparse or bloated.

Completeness5/5

The tool surface covers the full lifecycle: creation, monitoring (with two convenient patterns), output retrieval, cancellation/deletion, and history listing. No obvious gaps for the intended domain.

Maintenance

ActivitySlowing
ResponsivenessNo issues