Skip to content

Let Builtin shortcuts hand URL inputs to API2 as input_url - #607

Merged
kvz merged 9 commits into
mainfrom
builtin-input-url
Oct 22, 2026
Merged

kvz merged 9 commits into
mainfrom
builtin-input-url

Conversation

@kvz

@kvz kvz commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

Why

When a user gives ChatGPT, Claude or Codex a URL for a Builtin shortcut (caption this video, transcribe this talk), the MCP server downloads the file and tus-uploads it before an Assembly exists. A 239 MB video blocked past the host's tool-call deadline. #603 (and #602) bounded that with a 45 second wait budget and mcp_preparation_timeout, but the download still happens in the wrong layer: large URLs simply fail.

api2#10292 adds a top-level input_url Assembly parameter: for an eligible locked Builtin, API2 replaces the :original upload input with its own /http/import Step and keeps template_id, field validation and provenance. With it, the Assembly exists on the first response, nothing is downloaded by the MCP server, and nothing blocks on file size.

Sequencing: release after api2#10292 is fleet-wide, no transition probe

api2#10292 is rolling out to every API2 host (build 39701275724). @transloadit/mcp-server with this change is released and deployed only after that rollout completes. The hosted MCP server is the consumer that matters and it only talks to our API2, so the shortcut assumes input_url support and does not detect older hosts.

Earlier rounds of this PR carried a transition probe (status reads after creation, a fallback to download when an old host completed the Builtin without input files, a fallback on INVALID_FORM_DATA naming input_url, and an mcp_input_url_unconfirmed warning). That was removed in 3318f9d: it caused most review findings, added status reads to every call, and would have left an empty Assembly in a customer account for each call that hit an old host. If API2 refuses input_url for a request, its error now reaches the model like any other API2 error, without a silent second Assembly.

What changes

  • Shortcuts send input_url. When every file and attachment of a shortcut call is an http(s) URL, the Builtin is eligible and API2's literal-URL rules hold (absolute http(s), at most 4096 characters, no whitespace, no ${, no |, 1 to 10 URLs, exactly one for caption-video), the shortcut creates { template_id, input_url, fields } with no uploads. Mixed inputs (URL plus base64, sandbox expected_uploads beyond the URLs), upload_behavior: 'none' (which asks for reserved upload URLs), inputs given a file name, such as host attachments (API2 names imports after the URL, its redirects or Content-Disposition, so the given name and the extension processing may depend on would be lost), ineligible Builtins and URLs API2 would refuse keep today's download path.
  • Eligibility source. API2 opts Builtins in with allowInputImport but does not publish it in the catalog, so mcpBuiltinInputUrlMaxCounts sits next to mcpBuiltinTemplateManifest in the alphalib Builtin manifest (@transloadit/zod/v4/builtinTemplates), keyed by the approved versions and mirroring api2's builtinInputImportTemplateNames. A test pins the list and checks every entry is an approved shortcut. capture-webpage stays out, as in API2.
  • Typing. input_url (string or 1 to 10 strings) is added to alphalib's assemblyInstructionsSchema, so createAssembly({ params: { template_id, input_url } }) type-checks in @transloadit/node. It stays out of the shared shape that Template content uses, like in API2.
  • README section and changesets (mcp-server minor; node, transloadit, types and zod minor for the contract, mcp-server patch alongside).

Review history

  • Council round 1: four findings, all fixed test first (8d0faf7): a slow old host could outlast the two probes, upload_behavior: 'none' lost its reservations, given file names were dropped, and probing ignored a shorter wait_timeout_ms.
  • Council round 2: two findings, fixed test first (f084045): a percent-encoded URL name matched the given name only after decoding, and a slow status read could outlast the budget.
  • Council round 3: two findings, fixed test first (a57f242): fractional probe timeouts made AbortSignal.timeout() throw, and a URL's own name does not guarantee the imported name, so any input with a given file name now keeps the download path.
  • Council round 4: two P3s, answered with comments (c4a830c).
  • Council round 5: one finding, fixed test first (cb899ca): failed status reads were retried and an unconfirmed import reported.
  • Council round 6: one P2, fixed test first (fea73a0): probes, reads included, capped at three seconds.
  • Kevin's decision after round 6 (3318f9d): remove the transition probe entirely, see Sequencing. The probe findings above (rounds 1, 2, 4, 5, 6 and part of 3) no longer apply; the upload-reservation and named-input fixes stay.
  • Council round 7 (after the removal): one P2, fixed in 3e50953: the e2e skipped whenever API2 imported nothing, which also hid errors. It now fails on any error or non-completed Assembly and skips only an old host's clean completion without input files.
  • Council round 8: one P3: that skip still cannot tell an old host from a regression that ignores input_url. Kept on purpose for the rollout window, as the test comment says; drop the skip once api2#10292 is fleet-wide. The loop ended at the two-run cap without two consecutive clean rounds (round 8 had no P0-P2 findings, round 7 had one).

Tests

  • test/unit/builtin-input-url.test.ts: request shape (template_id, input_url, fields, no files/uploads/expected uploads, nothing downloaded, no status reads) for files and attachments; eligibility pinning and the URL rules; caption-video takes one URL and downloads two; mixed inputs, sandbox uploads, upload_behavior: 'none', named inputs and ineligible Builtins keep the download path; an Assembly that ran without input files is returned as API2 reported it; an API2 refusal of input_url surfaces as a tool error with no download and no second Assembly.
  • test/e2e/url-import.test.ts: first creates a builtin/prepare-image Assembly with input_url directly against API2 and fails on any error. If API2 imported the URL, transloadit_prepare_image with a URL must complete with one input file, no uploads, and results. If the host completed without input files (no input_url support yet), the test is skipped with the API2 build in the reason. It passed (not skipped) against production on 2026-10-22, build 39701275724. Re-run the e2e after the rollout completes to confirm on the full fleet before releasing.
  • yarn check, yarn lint:changesets, yarn lint:transloadit-sync, mcp-server lint:ts, e2e suite.

🤖 Claimed by kvz on framework1/node-sdk since 2026-10-21 · resume: claude --resume 26eaa88e-b3fa-4cc1-bcce-0c7ff5b6bab7

kvz and others added 9 commits October 21, 2026 19:32
When every input of a Builtin shortcut call is a URL and the Builtin accepts
input_url (api2#10292), send the URLs with template_id instead of downloading
and tus-uploading them here. API2 imports them itself, so the Assembly exists
at once and large files no longer run into the wait budget.

API2 hosts without input_url support ignore the parameter and complete the
Builtin without input files within milliseconds. The shortcut reads the
status once or twice to detect that, and falls back to the download path,
as it does when API2 refuses input_url for the request.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Recognize a host that runs input_url by its `imported` job and keep reading
  a slow host's status for up to 2.5 seconds before taking it as importing.
- Keep the download path when a URL input was given a name the URL does not
  carry, and for upload_behavior none, which asks for reserved upload URLs.
- Stop probing at the call's own wait deadline.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
API2 keeps percent escapes in imported file names, so a given name only
matches the raw last path segment. Each probe's status read now ends with
the call's remaining wait budget instead of after five seconds.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
API2 can rename an imported file after redirects or Content-Disposition, so
only an upload keeps a given file name: any named URL input now downloads.
AbortSignal.timeout() refuses fractional delays, which made every probe below
a five second budget throw and skip the fallback.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…sembly

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A failed status read ended probing, so an old host's empty Assembly could be
returned without the fallback. Reads are now retried within the budget, and a
call that still cannot tell carries an mcp_input_url_unconfirmed warning.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Retried status reads could each wait five seconds, adding up to 22 seconds
to a call and eating into the time a fallback download needs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The MCP server only talks to our API2, and this release ships after
api2#10292 is on every host, so detecting old hosts only added status
reads, empty fallback Assemblies and a warning. API2's refusals now
surface like any other error, and the e2e checks the import itself.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Errors and anything other than a clean completion without input files
now fail, so only the rollout gap is skipped.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@kvz
kvz merged commit bcbd703 into main Oct 22, 2026
17 checks passed
@kvz
kvz deleted the builtin-input-url branch October 22, 2026 14:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant