# `ConduitMcp.Tasks`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.10.1/lib/conduit_mcp/tasks.ex#L1)

Task management for long-running MCP operations (experimental).

Tasks provide a durable state machine for operations that may take time to
complete. Clients can poll for status, cancel in-flight operations, and
receive results asynchronously.

## Task Lifecycle

    working → completed
    working → failed
    working → cancelled
    working → input_required → working (after elicitation)

## Storage

Storage is delegated to a pluggable store module implementing
`ConduitMcp.Tasks.Store`. The default store is
`ConduitMcp.Tasks.EtsStore` (in-memory, single-node). For durable,
multi-node deployments — or to back tasks with a job queue like Oban —
implement the behaviour and configure it as the application's task store:

    config :conduit_mcp, :tasks_store, MyApp.MyTasksStore

The standard `tasks/get`, `tasks/cancel`, `tasks/result`, and
`tasks/list` JSON-RPC routes dispatch through this module, so swapping
the store requires no handler changes. See
`examples/oban_tasks_server/` for an Oban + SQLite implementation and
`examples/oban_task_store.ex` for a Postgres-flavored reference.

## Configuration

Enable tasks in your transport config:

    {ConduitMcp.Transport.StreamableHTTP,
      server_module: MyServer,
      tasks: [enabled: true]}

# `status`

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

# `task_id`

```elixir
@type task_id() :: String.t()
```

# `cancel`

Cancels a task. Dispatches to the configured store's `cancel/1` callback;
falls back to `update(task_id, %{"status" => "cancelled"})` for stores
that don't implement it.

# `cancel`

Cancels a task, scoped to `owner`.

Returns `{:error, :not_found}` (without cancelling) when the task is owned by
a different principal. See `get/2` for the scoping rules.

# `cleanup`

Prunes terminal-state tasks older than `ttl_ms`. Dispatches to the
configured store's `cleanup/1` callback; returns `0` for stores that
don't implement it (e.g., stores backed by native TTL like Redis).

# `create`

Creates a new task. See `ConduitMcp.Tasks.Store`.

Pass `owner` (typically `owner(conn)`) to stamp the task with a principal so
it can be owner-scoped via `get/2`, `cancel/2`, and `list/2`. When `owner` is
`nil` (the default) the task is left unowned and readable by anyone, which
preserves back-compatibility for apps that don't use scoping.

The owner is stored under the top-level `"owner"` key of the task metadata —
see the "Owner scoping" section of `ConduitMcp.Tasks.Store`.

# `delete`

Deletes a task by ID. See `ConduitMcp.Tasks.Store`.

# `generate_id`

Generates a unique task ID.

# `get`

Gets a task by ID. See `ConduitMcp.Tasks.Store`.

# `get`

Gets a task by ID, scoped to `owner`.

Returns `{:error, :not_found}` when the task is owned by a *different*
principal, so a task's existence is never leaked to a non-owner. A `nil`
`owner` (no principal) or an unowned task is always accessible — see the
"Owner scoping" section of `ConduitMcp.Tasks.Store`.

# `list`

Lists tasks, optionally filtered by `:status`. See `ConduitMcp.Tasks.Store`.

# `list`

Lists tasks scoped to `owner`, optionally filtered by `:status`.

Returns only tasks the caller may see: their own (matching `"owner"`) plus
any unowned tasks. A `nil` `owner` (no principal) returns everything, matching
`list/1`. See `get/2` for the scoping rules.

# `owner`

Extracts the owner principal from a `Plug.Conn` (or conn-like map).

Applies the configured `:task_owner_fun`
(`config :conduit_mcp, :task_owner_fun`), which defaults to
`conn.assigns[:current_user]`. Returns `nil` for `nil`/non-conn input or when
no principal is present — `nil` means "no scoping" throughout this module.

> #### Return a stable scalar {: .warning}
>
> Ownership is checked by **exact match** (`==`), so the extractor should
> return a stable, comparable identity — typically the user's `sub`/`id`
> scalar, not the whole `current_user` struct. A struct carrying any
> per-request volatile field would fail to match its own tasks on a later
> request, and an extractor that maps distinct users to equal terms would
> leak tasks between them. The default works when `current_user` is itself a
> stable id; map it to one otherwise, e.g.
> `task_owner_fun: &(&1.assigns[:current_user] && &1.assigns.current_user.id)`.

# `store`

Returns the configured task store module. Reads from
`Application.get_env(:conduit_mcp, :tasks_store)`, defaulting to
`ConduitMcp.Tasks.EtsStore`.

# `update`

Updates a task's status and/or metadata. See `ConduitMcp.Tasks.Store`.

# `valid_statuses`

Returns the list of valid task statuses.

# `valid_transition?`

Validates that a status transition is allowed.

---

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