Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Changelog

## 0.53.1

### Fixed — seedance-2.5 first-and-last-frame is refused by name on Solana

0.53.0 advertised `last_frame_url` on seedance-2.5 on every rail.
`sol.blockrun.ai` is a separate deploy that has not added 2.5 to its
first-and-last-frame list: it answers `400 "does not support first-and-last-frame
video"` before quoting, while `blockrun.ai` quotes the same body (unsigned
probes, 2026-09-29; 2.0, 2.0-fast, 2.0-mini and 1.5-pro quote on both). No
payment was ever at risk, but the caller got "the endpoint did not return a
quote". The tool now refuses that one combination on the Solana wallet rail,
names Base and the account rail as the places that serve it, and lists the
models that work on Solana. The parameter description and
`docs/seedance-capabilities.md` say the same.

The output controls (`output_format`, `bitrate_mode`, `watermark`,
`return_last_frame`) were probed on both gateways as well. Both validate them
in their request schemas, so they are not silently dropped on Solana.

## 0.53.0

### Added — Seedance reference media, 2.5 first/last frame, output controls
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -252,9 +252,9 @@ Package managers have shown install size for decades. Almost no MCP server shows

| Profile | Tools | Context |
|---------|-------|---------|
| `full` *(default)* | 19 | 14,000 |
| `full` *(default)* | 19 | 14,011 |
| `trading` | 8 | 5,411 |
| `media` | 7 | 6,784 |
| `media` | 7 | 6,795 |
| `research` | 5 | 2,752 |
| `chat` | 3 | 2,079 |

Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.53.0
0.53.1
4 changes: 2 additions & 2 deletions docs/mcp-schema-overhead.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ Written 2026-09-01, verified against `@modelcontextprotocol/sdk` 1.29.0. Numbers

| Profile | Tools | Context |
|---------|-------|---------|
| `full` *(default)* | 19 | 14,000 |
| `full` *(default)* | 19 | 14,011 |
| `trading` | 8 | 5,411 |
| `media` | 7 | 6,784 |
| `media` | 7 | 6,795 |
| `research` | 5 | 2,752 |
| `chat` | 3 | 2,079 |

Expand Down
11 changes: 9 additions & 2 deletions docs/seedance-capabilities.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,22 @@ Both wallet gateways refuse them with a `400` **before** issuing a quote
`blockrun_video` refuses them client-side on the wallet rails rather than
forwarding a request that cannot succeed, so no payment is taken and no DNS
lookup is spent on the reference URLs. Frame seeding (`image_url`,
`last_frame_url`) is unaffected and works on every rail.
`last_frame_url`) is unaffected and works on every rail, with one exception
below.

## Supported combinations

| Model | First + last frame | Reference images | Reference video/audio |
| --- | --- | --- | --- |
| Seedance 1.5-pro | Yes | No | No |
| Seedance 2.0 / 2.0-fast / 2.0-mini | Yes | 1–9 | Image + video, image + audio, video + audio, or all three; 1–3 clips of each type |
| Seedance 2.5 | Yes | 1–30 | No |
| Seedance 2.5 | Yes, except on the Solana wallet rail | 1–30 | No |

`sol.blockrun.ai` has not yet added 2.5 to its first-and-last-frame list. It
refuses `last_frame_url` on 2.5 with a `400` before quoting, while
`blockrun.ai` quotes the same body; 2.0, 2.0-fast, 2.0-mini and 1.5-pro quote
on both (unsigned probes, 2026-09-29). The tool refuses that one combination on
Solana by name, so no payment is taken; use Base or the account rail for it.

Seedance 2.5 takes reference **images** but no reference clips — the gateway
registry carries `supportsReferenceImages: true` with
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@blockrun/mcp",
"version": "0.53.0",
"version": "0.53.1",
"mcpName": "io.github.BlockRunAI/blockrun-mcp",
"description": "BlockRun MCP Server - Give your AI agent web search, deep research, prediction markets, and crypto data. Pay per call from a USDC wallet (Solana or Base) or a BlockRun API key.",
"type": "module",
Expand Down
13 changes: 11 additions & 2 deletions src/tools/video.ts
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ Returns a permanent blockrun-hosted video URL (the gateway mirrors the asset to
generate_audio: z.boolean().optional().describe("Seedance only: whether to generate a synced audio track. Defaults ON for text-to-video and OFF for image/RealFace-conditioned. The auto-generated audio is occasionally rejected by upstream moderation ('output audio may contain sensitive information') even for benign prompts — pass false to skip audio and avoid that failure. Ignored by xAI/Sora."),
resolution: z.enum(["480p", "720p", "1080p", "4K"]).optional().describe("Output resolution. Seedance defaults to 720p and is token-priced (~2.25x at 1080p, ~9x at 4K); per-model sets from token360's published schema: seedance-2.0 480p/720p/1080p/4K · 1.5-pro 480p/720p/1080p · 2.0-fast, 2.0-mini and 2.5 480p/720p only. grok-imagine-video honours 480p (default, $0.05/sec) and 720p ($0.07/sec) and rejects anything higher. Ignored by Sora only (dropped from the request)."),
aspect_ratio: z.enum(["adaptive", "16:9", "9:16", "1:1", "4:3", "3:4", "21:9"]).optional().describe("Output aspect ratio. Seedance honors the full set; Sora uses it only to pick portrait vs landscape (9:16 / 3:4 -> portrait); Grok ignores it (the gateway never forwards it to xAI). Defaults to the model's own default. (9:21 removed 2026-08-07 — no Seedance model offers it; use 9:16 for vertical.)"),
last_frame_url: z.string().url().optional().describe("Seedance 1.5-pro / 2.0 / 2.0-fast / 2.0-mini / 2.5: first-and-last-frame interpolation. A second image URL that seeds the FINAL frame so the model tweens from image_url (first frame) → last_frame_url (last frame). Requires image_url; mutually exclusive with real_face_asset_id."),
last_frame_url: z.string().url().optional().describe("Seedance 1.5-pro / 2.0 / 2.0-fast / 2.0-mini / 2.5 (2.5 not on the Solana wallet rail): first-and-last-frame interpolation. A second image URL that seeds the FINAL frame so the model tweens from image_url (first frame) → last_frame_url (last frame). Requires image_url; mutually exclusive with real_face_asset_id."),
model: z.enum(["azure/sora-2", "xai/grok-imagine-video", "bytedance/seedance-1.5-pro", "bytedance/seedance-2.0-mini", "bytedance/seedance-2.0-fast", "bytedance/seedance-2.0", "bytedance/seedance-2.5"]).optional().default("xai/grok-imagine-video").describe("Video model to use"),
reference_image_urls: z.array(z.string().url().max(2048)).min(1).max(30).optional().describe("ACCOUNT RAIL ONLY (BLOCKRUN_API_KEY) — the Base and Solana gateways refuse reference media with a 400. Character/style reference images, cited as 'image 1', 'image 2' in the prompt: up to 9 on seedance-2.0 / 2.0-fast / 2.0-mini, 30 on 2.5. Mutually exclusive with image_url / last_frame_url / real_face_asset_id."),
reference_videos: z.array(z.object({ url: z.string().url().max(2048) })).min(1).max(3).optional().describe("ACCOUNT RAIL ONLY. Motion reference clips (1-3) on seedance-2.0 / 2.0-fast / 2.0-mini — NOT 2.5. Each clip is BILLED AT THE 15.2s CEILING whatever its real length, so one clip makes a 5s 720p render cost about 4x (seedance-2.0-mini ~$0.40 -> ~$1.61), and more at 480p."),
Expand Down Expand Up @@ -491,7 +491,7 @@ Returns a permanent blockrun-hosted video URL (the gateway mirrors the asset to
// then that frame seeds and references do not mix, and only then that
// the rail cannot serve it at all.
if (refs && !isApiKeyMode()) {
return reject(`Reference media (reference_image_urls / reference_videos / reference_audios) is served only by the BlockRun account rail (api.blockrun.ai). The Base and Solana gateways refuse it with a 400 before quoting. Set BLOCKRUN_API_KEY to use the account rail, or drop the reference fields — image_url / last_frame_url frame seeding works on every rail. No payment was taken.`);
return reject(`Reference media (reference_image_urls / reference_videos / reference_audios) is served only by the BlockRun account rail (api.blockrun.ai). The Base and Solana gateways refuse it with a 400 before quoting. Set BLOCKRUN_API_KEY to use the account rail, or drop the reference fields — image_url / last_frame_url frame seeding works on the wallet rails. No payment was taken.`);
}

// Second for the same reason: with references present, every
Expand Down Expand Up @@ -530,6 +530,15 @@ Returns a permanent blockrun-hosted video URL (the gateway mirrors the asset to
isError: true,
};
}
// sol.blockrun.ai is a separate deploy and has not taken 2.5 into
// its first-and-last-frame list: it answers 400 "does not support
// first-and-last-frame video" before quoting, where Base quotes the
// same body (both probed unsigned 2026-09-29; 2.0 / 2.0-fast /
// 2.0-mini / 1.5-pro quote on both). Refused here by name instead
// of surfacing as "the endpoint did not return a quote".
if (selectedModel === SEEDANCE_25 && !isApiKeyMode() && getChain() === "solana") {
return reject(`${SEEDANCE_25} first-and-last-frame (last_frame_url) is not served by the Solana gateway yet — it refuses the request before quoting. Use it on Base (blockrun_wallet action:"chain" chain:"base") or the account rail (BLOCKRUN_API_KEY), or pick ${[...FIRST_LAST_FRAME_MODELS].filter((m) => m !== SEEDANCE_25).join(", ")}, which support it on Solana. No payment was taken.`);
}
if (!image_url) {
return {
content: [{ type: "text", text: formatError("last_frame_url (first-and-last-frame interpolation) requires image_url as the first frame.") }],
Expand Down
33 changes: 33 additions & 0 deletions test/video-reference-media.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,36 @@ test("the reference_videos description gives the real multiple, not the old form
assert.doesNotMatch(d, /triples/);
assert.match(d, /about 4x/);
});

test("2.5 first-and-last-frame is refused by name on the Solana wallet rail, and only there", async () => {
// sol.blockrun.ai answers 400 "does not support first-and-last-frame video"
// for 2.5 before quoting; Base quotes it (unsigned probes, 2026-09-29).
const args = { prompt: "t", model: "bytedance/seedance-2.5", image_url: IMG, last_frame_url: IMG };
apiKeyMode = false;
activeChain = "solana";
try {
const text = await errorText(args);
assert.match(text, /not served by the Solana gateway/);
assert.match(text, /No payment was taken/);
// The other first-and-last-frame models are served on Solana: the guard
// must not catch them.
for (const model of ["bytedance/seedance-2.0", "bytedance/seedance-2.0-mini", "bytedance/seedance-1.5-pro"]) {
const res = await makeHarness().call({ ...args, model });
const t = res.content.map((c: any) => c.text).join("\n");
assert.doesNotMatch(t, /not served by the Solana gateway/, model);
}
} finally {
apiKeyMode = true;
activeChain = "base";
}
// Base and the account rail still forward it.
apiKeyMode = false;
try {
const body = await bodySentFor(args);
assert.equal(body.last_frame_url, IMG);
} finally {
apiKeyMode = true;
}
const body = await bodySentFor(args);
assert.equal(body.last_frame_url, IMG);
});
Loading