Technology

MCP Tasks for Long-Running Video Renders

How the MCP Tasks extension handles a tool call that runs for minutes, how it maps onto a render job ID, and what to build before your client supports it.

Phil Duong

Founder

MCP Tasks for Long-Running Video Renders

A video render takes longer than an MCP tool call should wait. The 2026-07-28 MCP specification answers this with the Tasks extension: the server returns a durable taskId at once, and the client polls tasks/get until the work is done. Renderly's MCP connector does not use the extension yet. It uses the older pattern that Tasks formalizes: renderly_create_render returns a jobId, and the agent polls renderly_get_render_status or waits for a webhook. This post explains both, maps one onto the other, and says what to build now.

Key Takeaways

  • In the 2026-07-28 MCP spec, Tasks moved out of the experimental core into an official extension, io.modelcontextprotocol/tasks.
  • A task handle carries taskId, status, ttlMs and pollIntervalMs. The client polls tasks/get, answers questions with tasks/update and can ask for tasks/cancel.
  • The spec names "external job systems" as a use case. A render queue with job IDs is exactly that.
  • Renderly's connector returns a jobId as a normal tool result today. It works in every client, with or without extension support.
  • A server must never return a task to a client that did not declare support, so you need the job-ID path anyway. Build on it now and add Tasks as a layer.

Why does a video render not fit in one tool call?

The MCP Tasks documentation states the problem directly: blocking ties up a connection, and "Many clients and transport intermediaries impose timeouts that make this impractical beyond a few seconds" (Model Context Protocol, Tasks, 2026). A render is queued work. It waits for a worker, renders frame by frame, encodes and uploads the MP4.

The Renderly MCP endpoint caps each request at 60 seconds. So the render tool does not wait. It checks the arguments, charges the credits, puts the job on the queue and returns. The job then moves through four states: PENDING, PROCESSING, COMPLETED and FAILED. The agent needs a way to follow that job after the tool call has ended. That is the whole problem Tasks solves.

What does the Tasks extension change?

The 2026-07-28 changelog lists this change: "Move experimental tasks out of the core protocol and into an official extension (io.modelcontextprotocol/tasks)". The redesign "replaces the blocking tasks/result method with polling via tasks/get and a new tasks/update for client-to-server input, removes tasks/list, and allows servers to return task handles unsolicited without per-request opt-in" (MCP Key Changes, 2026-07-28). "Without per-request opt-in" does not mean without opt-in. The client declares the extension once in its capabilities, and the server then decides per request whether to return a task, with no per-call flag.

The extension's own repository lists its 2026-07-28 schema as Stable (modelcontextprotocol/ext-tasks).

The flow has six parts:

  1. Opt in. The client puts io.modelcontextprotocol/tasks in the client capabilities that it sends in each request's _meta. The server lists the same extension in its server/discover response.
  2. Get a handle. For a long request such as tools/call, the server can return a CreateTaskResult with resultType: "task" in place of the normal result. The spec says the task must be stored durably before the server sends that response.
  3. Poll. The client calls tasks/get with the taskId and honors pollIntervalMs.
  4. Answer questions. If the status is input_required, the response holds an inputRequests map. The client answers with tasks/update.
  5. Read the result. On completed, the result field holds what the tool call would have returned. On failed, the error field holds a JSON-RPC error.
  6. Cancel, maybe. The client can send tasks/cancel. Cancellation is cooperative, so the task can still end as completed or failed.

This is the shape of a task handle in the 2026-07-28 schema. The values are an example from a hypothetical server, not a Renderly response:

{
  "resultType": "task",
  "taskId": "render-7f3a",
  "status": "working",
  "statusMessage": "Rendering frame 312 of 900",
  "createdAt": "2026-10-12T03:14:07Z",
  "lastUpdatedAt": "2026-10-12T03:14:07Z",
  "ttlMs": 86400000,
  "pollIntervalMs": 5000
}
StatusMeaningTerminal
workingThe work is in progressNo
input_requiredThe server needs an answer, listed in inputRequestsNo
completedresult holds the tool resultYes
failederror holds the JSON-RPC errorYes
cancelledThe work stopped after tasks/cancelYes

Polling is the default. A server can also push the full task state as notifications/tasks to clients that opt in through subscriptions/listen, which saves the extra tasks/get round trip.

How does Renderly handle a long render today?

The same changelog removes protocol-level sessions and says that "Servers that need cross-call state use explicit, server-minted handles passed as ordinary tool arguments" (MCP Key Changes, 2026-07-28). That sentence describes the Renderly connector. The handle is a jobId, and it moves between two tools.

First the agent calls renderly_create_render with a project or template and its replacements:

{
  "templateId": "<template id>",
  "replacements": { "headline": "Spring sale ends Friday" },
  "webhookUrl": "https://example.com/hooks/renderly"
}

The tool returns at once with the handle and the charge:

{
  "jobId": "<job id>",
  "projectId": "<project id>",
  "status": "PROCESSING",
  "creditsUsed": 0.5,
  "estimatedDurationMinutes": 0.5,
  "mode": "template"
}

estimatedDurationMinutes is the length of the video, which sets the price. It is not a forecast of the render time. Here a 30-second 1080p video costs 0.5 credits at the default rate of 1 credit per 1080p minute, rounded up to the nearest half credit. Some templates set a higher per-minute rate.

Then the agent calls renderly_get_render_status with the jobId. The result carries status, progressPercentage, estimatedTimeRemaining in seconds, and, once the status is COMPLETED, the outputUrl of the MP4. A FAILED job carries an errorMessage.

The webhookUrl is the push path. When the render ends, Renderly POSTs a render.completed or render.failed event to that URL, so a backend agent does not have to poll at all. The webhook guide shows how to receive and verify the event.

Renderly today (job handle)MCP Tasks extensionAgentRenderlyrenderly_create_renderjobId, PROCESSINGrenderly_get_render_statusCOMPLETED, outputUrlrender.completed (webhook)Plain tool results. Works in every MCP client.ClientServertools/call + tasks captaskId, workingtasks/getcompleted, resultnotifications/tasksBoth sides must declare the extension.Solid purple: request. Dashed blue: response. Green: push. Sources: lib/mcp/tools.ts; MCP Tasks, 2026-07-28.

How do render job fields map to Task fields?

The Tasks documentation lists this exact case as a fit: "If your server wraps an API that already uses job IDs (cloud deployments, async APIs, queued work), return a task when you create the job and resolve it when the job completes" (Model Context Protocol, Tasks, 2026). Most of the mapping is direct. Two rows have no Renderly equivalent today, and the table says so.

Task field or methodRenderly today
taskIdjobId from renderly_create_render
status: workingPENDING or PROCESSING
status: completed + resultCOMPLETED + outputUrl
status: failed + errorFAILED + errorMessage
statusMessageprogressPercentage and estimatedTimeRemaining
createdAt / lastUpdatedAtcreatedAt / updatedAt
tasks/getrenderly_get_render_status
notifications/taskswebhookUrl, with render.completed and render.failed
tasks/cancelNone. A render runs to the end once it starts
input_requiredNone inside a job. The tool description tells the agent to confirm values with the user before it calls the render

The input_required row matters more than it looks. A render costs credits, and the credits are charged when the job is created. The useful question to ask a person is "are these the right values?", and the answer has to come before the charge, not halfway through the render. That is why the connector has a free preview tool that takes the same arguments as the render and reports wrong variable names and clipped text. A render that fails gets its credits back.

Should you wait for client support before you build?

No. The spec tells servers to "verify that the client included the extension in its per-request capabilities. Never return a task to a client that did not declare support" (Model Context Protocol, Tasks, 2026). So every server that adopts Tasks still needs a plain-result path for the clients that have not. For a render, that plain path is the job handle.

Client support is still thin. On 12 October 2026, the extension support matrix listed four extensions - MCP Apps, OAuth Client Credentials, Enterprise-Managed Authorization and Skills - and did not list Tasks. The matrix is maintained by the community, so check it again before you plan around it.

The protocol is also not finished with long-running work. The roadmap, last updated 2026-08-22, makes server-initiated events a priority because "we need extensions that let servers tell clients when work has finished, without relying purely on expensive client-side polling", and it expects work on Tasks to continue "toward eventual inclusion of the extension in the core protocol" (MCP Roadmap, 2026). Push delivery for MCP is being designed now. A webhook works today.

So the order is clear. Build on job handles and webhooks, which every client and every backend can use. When the clients you care about declare the Tasks extension, a server can return a task to them and keep the job handle for everyone else. The jobId becomes the taskId, and nothing on the render side changes.

How should an agent follow a render today?

The Tasks client guide gives the rules for any polled job: "Continue polling until the task reaches a terminal status" and "Store task IDs durably so polling can resume after a client crash or restart" (Model Context Protocol, Tasks, 2026). They apply to a jobId just as well:

  • Preview first. Call renderly_preview_render with the same arguments. It costs nothing and catches the mistakes that would waste a paid render.
  • Keep the jobId. Write it to your database or the conversation before you poll. A job does not depend on the MCP connection, so a new session can pick it up with renderly_get_render_status.
  • Stop at a terminal status. COMPLETED and FAILED do not change. PENDING and PROCESSING mean poll again later.
  • Space the polls. Use estimatedTimeRemaining to choose the next check instead of calling in a tight loop. Each poll is a tool call, and in a chat client each one uses the model's context.
  • Use the webhook for batches. If an agent starts fifty renders, one webhookUrl is better than fifty polling loops. The batch rendering guide shows the pattern at scale.

The full tool list, setup for Claude, Cursor and Claude Code, and the connector's current limits are in the Claude MCP connector guide and the MCP docs.

The short version

MCP Tasks let a server answer a long tool call with a durable taskId that the client polls with tasks/get. In the 2026-07-28 spec, Tasks became an official extension, and the spec names job-ID systems as a use case. Renderly's connector uses the same idea without the extension: renderly_create_render returns a jobId, and renderly_get_render_status or a webhook reports the finished MP4.

Build on the job handle now. It works in every MCP client, and it is the path a Tasks server must keep for clients that do not declare the extension. Add the connector at https://renderly.video/api/mcp, pick a template from the template gallery, and preview it for free before the first paid render.

Frequently asked

What are MCP Tasks?
Tasks are an official extension to the Model Context Protocol, io.modelcontextprotocol/tasks. When a request will run for a long time, the server returns a durable task handle instead of the result. The client polls tasks/get with the taskId until the status is completed, failed or cancelled, and then reads the result from the same response.
Does the Renderly MCP connector use Tasks?
Not today. renderly_create_render returns a jobId as an ordinary tool result, and the agent polls renderly_get_render_status until the status is COMPLETED or FAILED. You can also pass a webhookUrl and get a POST when the render ends. This pattern works in every MCP client, including clients that do not support any extensions.
Why can't an MCP tool just wait until the video is rendered?
Because the request would hold a connection open for the whole render. The MCP Tasks documentation says that many clients and intermediaries impose timeouts that make this impractical beyond a few seconds. The Renderly MCP endpoint also caps each request at 60 seconds, so the render tool returns a job handle at once.
Which MCP clients support the Tasks extension?
Check the extension support matrix on modelcontextprotocol.io before you depend on it. On 12 October 2026 the matrix listed four extensions - MCP Apps, OAuth Client Credentials, Enterprise-Managed Authorization and Skills - and Tasks was not one of them. The spec also says a server must never return a task to a client that did not declare support.
Can I cancel a Renderly render from an agent?
No. Renderly has no cancel endpoint or tool today, so a render runs to the end once it starts. Credits are charged when the job is created, and a render that fails gets its credits back. Use renderly_preview_render first: it takes the same arguments, costs nothing and reports wrong variable names before you pay.