# `Sake.Tasks`
[🔗](https://github.com/hattoriai/sake/blob/v0.4.3/lib/sake/tasks.ex#L1)

Tools that run as tasks: the `io.modelcontextprotocol/tasks` extension
of the modern protocol.

    defmodule MyApp.MCP.Export do
      use Sake.Tool, name: "export", task: :optional
      arg :project, :string, required: true

      @impl true
      def execute(%{project: project}, ctx) do
        files = Projects.files(project)

        exported =
          Enum.reduce_while(files, 0, fn file, n ->
            if Sake.Tasks.cancelled?(ctx) do
              {:halt, n}
            else
              Export.file(file)
              Sake.Progress.report(ctx, n + 1, length(files))
              {:cont, n + 1}
            end
          end)

        {:ok, "Exported #{exported} of #{length(files)} files."}
      end
    end

When a client declares the extension in a request's capabilities, a
call to a tool with `task: :optional` (or `true`) or `task: :required`
answers at once with a task, and `execute/2` runs in the background; the
client polls `tasks/get` until the task ends. Without the extension, an
optional task tool runs like any other tool, and a required one is
refused with `-32021`. Arguments and scopes are checked before a task
exists, and a tool's `prepare/2` runs first, so it can still ask the
client for input on the original request (see `Sake.Tool`).

Inside a task, `Sake.Elicit`, `Sake.Sampling` and `Sake.Roots` move the
task to `input_required` and wait for the client's `tasks/update`
(keys must be unique over the task's life: asking a key again returns
the first answer); `Sake.Progress.report/4` sets the task's
`statusMessage`; `cancelled?/1` tells whether the client cancelled.
Cancelling marks the task cancelled at once and stops its process when
it runs on the node that received `tasks/cancel`; elsewhere, the tool
sees the cancellation when it next waits for input (a cancelled
elicitation, `{:error, :cancelled}` from sampling and roots) or calls
`cancelled?/1`.

A task belongs to whoever created it, and to what they created it
through: the token's subject when the endpoint has authorization
(otherwise the host's scope, see `Sake.Plug`'s `scope:`), together with
the server, the token's OAuth client and its resource. Any other caller,
the same person through another client, resource or server included, is
told the task does not exist. Reading, answering, cancelling and
following a task also need the scopes its tool required when it started:
a token without them is refused with `insufficient_scope`, as the call
itself would be. Task IDs carry 144 random bits.

Tasks are configured on the `Sake` child:

    {Sake, secret_key_base: ..., tasks: [store: {Sake.Tasks.Store.Ecto, repo: MyApp.Repo}]}

Options: `store` (default `Sake.Tasks.Store.ETS`, which keeps tasks on
one node; use `Sake.Tasks.Store.Ecto` when the endpoint runs on
several), `ttl` (how long a task is kept after it was created, in ms;
default one hour), `poll_interval` (the `pollIntervalMs` suggested to
clients, and how often a waiting task checks the store; default one
second) and `max_per_principal` (how many live tasks one owner may
have at once, see below; default 100, or `:infinity`). `tasks: false`
turns tasks off: task tools then run like any other tool, and the
extension is not advertised.

An owner with `max_per_principal` tasks neither ended nor expired gets
no new one until one ends: a `task: :optional` tool then runs like any
other tool, in the request, and a `task: :required` tool (or a proxied
call the upstream made a task of, which is cancelled upstream) is
refused with `-32600`.

Telemetry: `[:sake, :task, :start | :stop | :exception]` around the
tool's run, with `task_id`, `tool` and `server` in the metadata, and the
final `status` on `:stop`. With OpenTelemetry, the run is an
`execute_tool` span, the child of the span of the `tools/call` that
started it (see `guides/observability.md`).

# `cancelled?`

```elixir
@spec cancelled?(Sake.Context.t()) :: boolean()
```

Whether the client asked to cancel the task the component runs in; always `false` outside a task.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
