Python MCP SDK 2.3 subscriptions=False: a Sume wrapper that pulls

MCPServer(subscriptions=False) turns off push subscriptions in the Python SDK. A wrapper over Sume jobs can do without them by waiting in bounded slices.

4 min readSume
All posts

You can run a Sume-backed MCP server with MCPServer(subscriptions=False) because the Sume Developer API has no streaming transport to forward to the client. The Python SDK 2.3.0 option disables subscriptions, and a wrapper can instead expose one tool that submits and one that waits in short slices.

The release notes list the option next to max_sse_event_size and a registration-time failure for tools with an invalid x-mcp-header.

What Sume provides to wait on

The Developer API has no SSE or WebSocket transport today. GET /v1/jobs/:id/events is a pull snapshot, not a stream. The two ways to learn an outcome are polling the job and a signed webhook to a public HTTPS URL.

Webhooks deliver terminal events only: job.completed, job.failed and job.canceled. There are no progress or partial deliveries. For an agent talking to your server over MCP, polling is the simpler path because no inbound endpoint is needed.

Two tools instead of a subscription

Keep each slice shorter than your MCP client's tool timeout. Sume's own hosted jobs_wait caps a slice at 55 seconds for the same reason.

Suggested wrapper tool surface (design suggestion, read 2026-10-03)
ToolWhat it doesReturns
submit_clipPOST the Sume generate route with mode async and an Idempotency-KeyThe job id and status_url
wait_clipGET the status route, then sleep up to a bounded sliceterminal flag, result_ready, next_poll_after_seconds
fetch_clipGET the result route once result_ready is trueSume media URLs

A bounded wait in Python

This function reads the status endpoint until the job is terminal or the slice ends. It uses only the standard library, and the environment variable holds the API key.

import json
import os
import time
import urllib.request

API = "https://api.sume.com/v1"


def get_status(job_id: str) -> dict:
    req = urllib.request.Request(
        f"{API}/jobs/{job_id}/status",
        headers={"Authorization": f"Bearer {os.environ['SUME_API_KEY']}"},
    )
    with urllib.request.urlopen(req, timeout=15) as resp:
        return json.load(resp)


def wait_slice(job_id: str, slice_seconds: float = 40.0) -> dict:
    deadline = time.monotonic() + slice_seconds
    delay = 2.0
    while True:
        status = get_status(job_id)
        if status.get("terminal"):
            return status
        pause = status.get("next_poll_after_seconds") or delay
        if time.monotonic() + pause >= deadline:
            return status
        time.sleep(pause)
        delay = min(delay * 2, 15.0)

Rules that keep it safe

  • If the slice ends, return the job id and status to the model and let it call the wait tool again. Never resubmit the paid request.
  • Send the same Idempotency-Key when you retry a submit after a network failure, so the retry returns the original job.
  • Honor next_poll_after_seconds when it is present; use backoff when it is not.
  • A client-side timeout does not cancel the job. It keeps running and billing until it finishes or is cancelled before it starts.

Sources

Related posts

More in Developers

All Developers posts

Written by Sume