Agents

Programmatic tool calling on MCP: how Sume's script_run works

script_run is programmatic tool calling on Sume's hosted MCP: a short JavaScript program calls tools in loops or in parallel under call and paid budgets.

5 min readSume
All posts

Programmatic tool calling on Sume's hosted MCP server (https://mcp.sume.com/mcp) is the script_run tool: the agent sends a short JavaScript program that runs on the Sume side, calls the server's other tools in a loop, in parallel, or conditionally, and returns one value. Intermediate tool results stay out of the conversation. The response is that value plus a journal of every call and the child job ids.

The facts below come from MCP tools and gates and Usage, read on 2026-09-26, and from the script_run description the hosted server currently returns to MCP clients. The basics page says the CLI and hosted MCP still work but are not part of the primary integration path today; backends call the Format API or the Developer API over HTTP. The gates each call keeps are covered in Safe automation for AI agents that call paid APIs.

When should an agent use script_run?

When a turn needs three or more independent calls of the same shape: one tts_create per sentence, one generate_image per scene. The tool description adds video_frames_create at many timestamps, and jobs_wait then jobs_result over a wave. Skip it for:

  • One or two calls. Call those tools directly.
  • Discovery. tools_list, tools_schema, mcp_health, and script_run itself are refused inside a script.
  • Anything that must outlive one request. The whole run is bounded by timeout_seconds, so submit the creates, return their job ids, and call jobs_wait outside the script.

How do I write a script?

The script field is the body of async (sume, args, console) => { … } in plain JavaScript: at most 64 KB, with no imports, no network, and no timers. args is a JSON object the script reads as args. label, max_calls, max_paid_calls, and timeout_seconds are optional. Inside the script:

  • await sume.call(name, arguments) runs any listed tool with that tool's own input object and the same gates, redaction, and errors as a direct call.
  • sume.tools.<name>(arguments) is the same call by name, with hyphens written as underscores.
  • sume.jobs.wait(ids, { timeout_seconds }), sume.jobs.result(ids), and sume.jobs.status(id) cover the job lifecycle.
  • Paid creates still need their own distinct idempotency_key, for example "tts-" + i.
  • console.log lines come back in logs.
{
  "label": "wave results",
  "max_calls": 4,
  "args": { "job_ids": ["job_123", "job_124", "job_125"] },
  "script": "await sume.jobs.wait(args.job_ids, { timeout_seconds: 40 }); return await sume.jobs.result(args.job_ids);"
}

What budgets bound a script run?

Every run has a wall clock, a call budget, and a separate budget for paid creates. A wait inside a script, like the one in the example above, is currently clamped to the time the run has left, and each wait or result call takes up to 20 ids, the same ceiling as jobs_wait and jobs_result.

From MCP tools and gates and the current script_run tool description, read 2026-09-26.
BudgetValue
timeout_seconds5–55 seconds for the whole run, default 45
max_callsDefault 32, ceiling 64
max_paid_callsDefault 16, ceiling 32
Calls in flight4
Call starts8 per second
Guest memory64 MB
Script size64 KB

What happens when a call fails or a budget runs out?

Failures are values the script can handle, and budget stops are reported, not silent:

  • A failed call throws SumeToolError { code, message, data }. Catch it to retry or skip; uncaught, it fails the run.
  • A budget stop ends the run with error.code script_timeout, script_call_budget_exceeded, script_paid_budget_exceeded, or script_tool_forbidden.
  • Either way, tools[] (calls per tool and model), calls[], and jobs[] are complete.
  • Next, read ok, result, and jobs[], then jobs_wait on jobs[] (up to 20 ids per call) and jobs_result. Never resubmit a create that already registered a job. Waiting is covered in MCP tool call timeouts on long-running video jobs.

How do I see what a script run cost?

Script calls are attributed in the usage ledger. GET /v1/usage with thread_id, run_id, or job_id adds a summary folded over every ledger row that scope caused:

  • includes counts rows by kind, including script_run_children: the jobs a script_run call dispatched.
  • script_runs lists the script_run calls inside the scope, each with its rows and money.
  • Rows carry script_run_id and script_run_call_index when a script_run dispatched them.
  • Quote debited_usd_micros, what the wallet deducted. Never sum rows yourself: a refunded row keeps its hold amount in billable_amount_usd_micros.

Sources

Related posts

Written by Sume