Snodo.Extensions.Tasks (snodo_tasks v0.3.2)

Copy Markdown View Source

SEP-2663 Tasks extension for MCP 2026-07-28.

Tasks remains outside the core protocol catalog. The extension owns its three top-level methods and uses the generic extension middleware seam to augment selected tools/call operations. Store and runner processes are supplied by the application through extension options and are never started implicitly.

A runtime installs the extension with application policy, for example:

extensions: [
  {Snodo.Extensions.Tasks,
   store: {Snodo.Extensions.Tasks.Store.Memory, MyApp.TaskStore},
   runner: MyApp.TaskRunner,
   task_support: %{"slow_job" => :optional, "durable_job" => :required}}
]

:optional tools execute as tasks when the client declares the extension and fall back to ordinary synchronous execution otherwise. :required tools return -32021 when the per-request capability is absent. A policy value may also be an arity-2 function receiving (params, context), allowing a server to decide per invocation.

Finish ordinary MRTR exchanges before selecting asynchronous execution, or use await_input/3 while a task is running. A worker cannot complete with an ordinary input-required result or another task handle; these are failed as protocol errors instead of storing a continuation as a terminal result.

Limits

A task's work input is at most :max_work_input_bytes of JSON, default 262,144 (256 KiB), or :infinity; a larger one is refused with -32602 before anything is stored. A taskId is at most 256 bytes and subscriptions/listen takes at most 100 taskIds; beyond that the request is refused with -32602.

When the runner is at its :max_jobs, or the store at its :max_tasks or :max_active_tasks_per_scope, task creation is refused with a -32603 error whose data is %{"retryable" => true, "limit" => name}. Nothing is stored, and the same call can succeed once running tasks finish or expired tasks are reaped.

Summary

Functions

Suspends the current Tasks worker until a matching tasks/update arrives.

Returns the stable idempotency key for the current task execution.

Generates an unguessable, URL-safe task identifier.

Returns input responses supplied on a pre-task MRTR retry.

Returns true when this request negotiated the Tasks extension.

Returns the full request params visible to a task-aware tool.

Builds a protocol-neutral notifications/tasks status event for an application source.

Functions

await_input(context, key, request)

@spec await_input(Snodo.Context.t(), String.t(), map()) ::
  {:ok, map()} | {:error, term()}

Suspends the current Tasks worker until a matching tasks/update arrives.

The key must be unique for the lifetime of the task. The request is exposed verbatim in tasks/get.inputRequests; callers receive the matching response.

execution_id(context)

@spec execution_id(Snodo.Context.t()) :: String.t() | nil

Returns the stable idempotency key for the current task execution.

generate_id()

@spec generate_id() :: String.t()

Generates an unguessable, URL-safe task identifier.

input_responses(context)

@spec input_responses(Snodo.Context.t()) :: map()

Returns input responses supplied on a pre-task MRTR retry.

negotiated?(context)

@spec negotiated?(Snodo.Context.t()) :: boolean()

Returns true when this request negotiated the Tasks extension.

request_params(context)

@spec request_params(Snodo.Context.t()) :: map()

Returns the full request params visible to a task-aware tool.

status_event(task, opts \\ [])

Builds a protocol-neutral notifications/tasks status event for an application source.