# `Phantom.Tasks`
[🔗](https://github.com/dbernheisel/phantom_mcp/blob/main/lib/phantom/tasks.ex#L1)

A task from the MCP Tasks extension (`io.modelcontextprotocol/tasks`),
available under MCP `2026-07-28`.

A tool handler may answer a call with a task instead of a result, and the
client polls for the outcome. Return one from the handler and Phantom
responds with a `CreateTaskResult`:

    def export_report(params, session) do
      task = MyApp.MCPTasks.create!(session, params)
      {:reply, Phantom.Tasks.new(id: task.id, status: :working, created_at: task.inserted_at), session}
    end

Phantom does not store tasks. You store them, run the work, and move them
through their statuses; Phantom asks your router for them when the client
polls. Check `Phantom.Session.tasks_supported?/1` before returning a task,
since a client that did not declare the extension cannot receive one.

Return the task from the handler. `Phantom.Session.respond/2` does not
accept one, since the task is already the asynchronous answer. Only tools
answer with tasks.

## Fields

  - `:id` - (required) The task ID. It acts as a bearer token, so it must be
    unguessable; see `generate_id/0`.
  - `:status` - (required) One of `:working`, `:input_required`,
    `:completed`, `:failed`, or `:cancelled`. Strings are accepted.
  - `:created_at` - (required) A `DateTime`.
  - `:last_updated_at` - A `DateTime`. Defaults to `:created_at`.
  - `:status_message` - An optional message describing the current status.
  - `:ttl_ms` - Milliseconds from creation that the task is kept, or `nil`
    for unlimited.
  - `:poll_interval_ms` - Suggested milliseconds between client polls.
  - `:input_requests` - Required when `:input_required`. A map of keys to
    `Phantom.Elicit` structs or embedded request maps. Keys must not be
    reused over the task's lifetime.
  - `:result` - Required when `:completed`. The final `CallToolResult`, such
    as `Phantom.Tool.response(Phantom.Tool.text("Done"))`. A tool error is
    still `:completed`.
  - `:error` - Required when `:failed`. A JSON-RPC error with `:code` and
    `:message`, such as `Phantom.Request.internal_error("Timed out")`.

# `status`

```elixir
@type status() :: :working | :input_required | :completed | :failed | :cancelled
```

# `t`

```elixir
@type t() :: %Phantom.Tasks{
  created_at: DateTime.t(),
  error: map() | nil,
  id: String.t(),
  input_requests: %{required(String.t()) =&gt; map()},
  last_updated_at: DateTime.t(),
  poll_interval_ms: pos_integer() | nil,
  result: map() | nil,
  status: status(),
  status_message: String.t() | nil,
  ttl_ms: non_neg_integer() | nil
}
```

# `generate_id`

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

Generate an unguessable, URL-safe task ID with 256 bits of entropy.

# `new`

```elixir
@spec new(map() | Keyword.t()) :: t()
```

Build a task. Raises `ArgumentError` when the fields do not describe a
valid task for its status.

---

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