diff --git a/CHANGELOG.md b/CHANGELOG.md index 42a0666..fe0c0e9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index b62032e..91c7bab 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/VERSION b/VERSION index 7f422a1..3f99a6d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.53.0 +0.53.1 diff --git a/docs/mcp-schema-overhead.md b/docs/mcp-schema-overhead.md index 86b3cf2..d60cb2d 100644 --- a/docs/mcp-schema-overhead.md +++ b/docs/mcp-schema-overhead.md @@ -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 | diff --git a/docs/seedance-capabilities.md b/docs/seedance-capabilities.md index 1141716..f12e8f4 100644 --- a/docs/seedance-capabilities.md +++ b/docs/seedance-capabilities.md @@ -23,7 +23,8 @@ 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 @@ -31,7 +32,13 @@ lookup is spent on the reference URLs. Frame seeding (`image_url`, | --- | --- | --- | --- | | 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 diff --git a/package.json b/package.json index 23d178d..ea27121 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/src/tools/video.ts b/src/tools/video.ts index a8f1763..7392dcb 100644 --- a/src/tools/video.ts +++ b/src/tools/video.ts @@ -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."), @@ -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 @@ -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.") }], diff --git a/test/video-reference-media.test.ts b/test/video-reference-media.test.ts index d7f899b..0be8f15 100644 --- a/test/video-reference-media.test.ts +++ b/test/video-reference-media.test.ts @@ -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); +});