Skip to content

feat: MCP Tasks (2025-11-25) — server-receiver for tools/call (#98) - #155

Merged
zoedsoupe merged 5 commits into
mainfrom
feat/mcp-tasks-server-receiver
May 9, 2026
Merged

zoedsoupe merged 5 commits into
mainfrom
feat/mcp-tasks-server-receiver

Conversation

@zoedsoupe

Copy link
Copy Markdown
Owner

Problem

Closes #98. MCP 2025-11-25 introduces Tasks — durable state machines wrapping long-running JSON-RPC requests so requestors can poll instead of relying on timeouts or progress notifications.

Solution

Phase 1 — server-receiver for tools/call only. Implements:

  • New protocol module V2025_11_25 (latest); registers tasks/get|result|cancel|list + notifications/tasks/status
  • :tasks capability on use Anubis.Server (list?, cancel?, requests:)
  • Tool-level task_support: :forbidden | :optional | :required; renders as execution.taskSupport in tools/list; enforced in Handlers.Tools (required→reject non-augmented, forbidden→reject augmented)
  • Anubis.Server.Task struct + Anubis.Server.TaskStore behaviour with Local adapter; pluggable via :task_store {mod, opts} in Anubis.Server.Supervisor (mirrors :registry/:supervisor); supports :via for distributed adapters via optional resolve_name/2 callback
  • Frame.task_id propagated to worker callbacks
  • Session: detects params.task on tools/call, persists Task, spawns worker via existing Task.Supervisor, immediately returns CreateTaskResult with _meta[related-task]. Routes tasks/get|cancel|result|list. tasks/result blocks until terminal via per-task waiter list. TTL timer + cancel paths release waiters with proper errors. Terminate cleans up workers + waiters
  • Server.send_task_status/1 API → emits notifications/tasks/status from session

15 deterministic tests (signal-based stub server, SyncHelpers.await_state — no Process.sleep). 757 tests + 33 doctests pass; credo strict clean; dialyzer pass.

Deferred to follow-up PRs

  • Phase 2 — client-as-requestor for tools/call (Client.call_tool(task: [ttl: N]), get_task/task_result/cancel_task helpers; return shape decided when designed)
  • Phase 3 — server-as-requestor for sampling/createMessage + elicitation/create
  • tasks/list — needs auth-context binding through Anubis.Server.Context first; Phase 1 returns -32601
  • Redis TaskStore adapter — slots into the behaviour without API change
  • input_required status flow — schema accepts it; Phase 1 worker path never produces it

Rationale

Phase 1 ships the highest-value slice (server author exposes a long-running tool with a single task_support: :optional). Pluggable storage day-one means distributed adapters land later as drop-ins. Strict 2025-11-25 gate keeps the spec contract clean — older protocol versions don't see Tasks. Signal-driven tests over Process.sleep keep the suite fast and deterministic, matching the existing async-dispatch pattern.

@coderabbitai

coderabbitai Bot commented May 9, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@zoedsoupe has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 18 minutes and 41 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: e88f9efe-f6d6-480a-88dc-a642f9b45d74

📥 Commits

Reviewing files that changed from the base of the PR and between e7f3d8b and 7abfa08.

📒 Files selected for processing (5)
  • lib/anubis/mcp/message.ex
  • lib/anubis/protocol/v2025_11_25.ex
  • lib/anubis/server.ex
  • lib/anubis/server/session.ex
  • test/anubis/server/tasks_test.exs
📝 Walkthrough

Walkthrough

This PR adds complete MCP durable task support to Anubis. The changes span protocol schemas (tasks/get, tasks/result, tasks/cancel, tasks/list, notifications/tasks/status), a new protocol version (2025-11-25), core task data model with task ID generation and state transitions, pluggable storage (TaskStore behaviour + Local in-memory adapter), tool task-support policies (forbidden|optional|required), and a major session refactor integrating task worker lifecycle management, TTL expiry, result waiters, and notification emission. Supervisor and transports wire task stores through the session. Comprehensive tests validate task-augmented tool calls, all task operations, error cases, and TTL behavior.

Possibly related PRs

  • zoedsoupe/anubis-mcp#153: Both modify Session request dispatch and task supervision patterns; #153's per-request task spawning complements task worker tracking here.
  • zoedsoupe/anubis-mcp#96: Both refactor Frame/Server/Component internals that this PR extends with task_support plumbing.
  • zoedsoupe/anubis-mcp#139: Also modifies Anubis.MCP.Message to add request/notification methods and schemas; closely related at the message/schema registration layer.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 52.94% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed Title directly describes the main change: implementing MCP Tasks (2025-11-25) as a server-receiver for tool calls, matching the core functionality.
Description check ✅ Passed Description comprehensively covers Problem, Solution, and Rationale sections with clear phase delineation, deferred work, and implementation justification.
Linked Issues check ✅ Passed PR fully implements Phase 1 server-receiver Tasks support (#98): protocol V2025_11_25, task storage/lifecycle, tool augmentation, session routing, and deterministic test coverage.
Out of Scope Changes check ✅ Passed All changes align with Phase 1 Tasks scope: protocol, storage, tool integration, session handling, handlers, and support utilities. Deferred items (Phase 2/3, Redis adapter, tasks/list auth) are explicitly documented.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch feat/mcp-tasks-server-receiver

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 708ac6f2-dae4-44bf-86d2-ccd4843fa505

📥 Commits

Reviewing files that changed from the base of the PR and between cd33058 and 23508fc.

📒 Files selected for processing (21)
  • lib/anubis/mcp/message.ex
  • lib/anubis/protocol/registry.ex
  • lib/anubis/protocol/v2025_11_25.ex
  • lib/anubis/server.ex
  • lib/anubis/server/component.ex
  • lib/anubis/server/component/tool.ex
  • lib/anubis/server/frame.ex
  • lib/anubis/server/handlers/tasks.ex
  • lib/anubis/server/handlers/tools.ex
  • lib/anubis/server/registry.ex
  • lib/anubis/server/session.ex
  • lib/anubis/server/supervisor.ex
  • lib/anubis/server/task.ex
  • lib/anubis/server/task_store.ex
  • lib/anubis/server/task_store/local.ex
  • lib/anubis/server/transport/sse.ex
  • lib/anubis/server/transport/streamable_http/plug.ex
  • test/anubis/protocol/registry_test.exs
  • test/anubis/protocol_test.exs
  • test/anubis/server/tasks_test.exs
  • test/support/tasks_stub_server.ex

Comment thread lib/anubis/protocol/v2025_11_25.ex
Comment thread lib/anubis/server/handlers/tasks.ex Outdated
Comment thread lib/anubis/server/session.ex
Comment thread lib/anubis/server/session.ex
Comment thread lib/anubis/server/session.ex
Comment thread lib/anubis/server/session.ex
Comment thread lib/anubis/server/session.ex
Comment thread lib/anubis/server/task_store.ex
Comment thread test/anubis/protocol_test.exs
Comment thread test/anubis/server/tasks_test.exs Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
lib/anubis/server/session.ex (1)

95-105: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

P3 — :task_store schema is loose; bad opts produce a deferred KeyError at init.

The schema {:list, :any} accepts any list (e.g. [1,2,3] or []), so a misconfigured supervisor only blows up later inside build_task_store/1 via Keyword.fetch!/2. A keyword schema would surface the misconfiguration at parse_options!/1.

♻️ Tighter schema
-    {:task_store, {{:list, :any}, {:default, nil}}}
+    {:task_store, {[adapter: {:required, :atom}, name: {:required, :any}], {:default, nil}}}
Peri 0.8.4 keyword list schema validation syntax

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2460bdee-070c-42ee-b71e-41d8fa7035fe

📥 Commits

Reviewing files that changed from the base of the PR and between 23508fc and e7f3d8b.

📒 Files selected for processing (6)
  • lib/anubis/mcp/message.ex
  • lib/anubis/protocol/v2025_11_25.ex
  • lib/anubis/server/handlers/tasks.ex
  • lib/anubis/server/session.ex
  • lib/anubis/server/task_store.ex
  • test/anubis/server/tasks_test.exs

Comment thread lib/anubis/mcp/message.ex
Comment thread lib/anubis/protocol/v2025_11_25.ex
Comment thread lib/anubis/server/session.ex Outdated
Comment thread lib/anubis/server/session.ex
@zoedsoupe
zoedsoupe merged commit 51348f1 into main May 9, 2026
9 checks passed
@zoedsoupe
zoedsoupe deleted the feat/mcp-tasks-server-receiver branch May 9, 2026 15:06
@zoedsoupe zoedsoupe mentioned this pull request May 9, 2026
zoedsoupe added a commit that referenced this pull request May 9, 2026
🚀 Want to release this?
---


##
[1.5.0](v1.4.0...v1.5.0)
(2026-05-09)


### Features

* MCP Tasks (2025-11-25) — server-receiver for tools/call
([#98](#98))
([#155](#155))
([51348f1](51348f1))


### Bug Fixes

* drop compile-connected deps from component/1 macro
([#154](#154))
([1e368b9](1e368b9))


### Continuous Integration

* fix flaky test
([939fd76](939fd76))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
zoedsoupe added a commit that referenced this pull request Jul 16, 2026
…155)

## Problem

Closes #98. MCP 2025-11-25 introduces Tasks — durable state machines
wrapping long-running JSON-RPC requests so requestors can poll instead
of relying on timeouts or progress notifications.

## Solution

**Phase 1 — server-receiver for `tools/call` only.** Implements:

- New protocol module `V2025_11_25` (latest); registers
`tasks/get|result|cancel|list` + `notifications/tasks/status`
- `:tasks` capability on `use Anubis.Server` (`list?`, `cancel?`,
`requests:`)
- Tool-level `task_support: :forbidden | :optional | :required`; renders
as `execution.taskSupport` in `tools/list`; enforced in `Handlers.Tools`
(required→reject non-augmented, forbidden→reject augmented)
- `Anubis.Server.Task` struct + `Anubis.Server.TaskStore` behaviour with
`Local` adapter; pluggable via `:task_store {mod, opts}` in
`Anubis.Server.Supervisor` (mirrors `:registry`/`:supervisor`); supports
`:via` for distributed adapters via optional `resolve_name/2` callback
- `Frame.task_id` propagated to worker callbacks
- Session: detects `params.task` on `tools/call`, persists `Task`,
spawns worker via existing `Task.Supervisor`, immediately returns
`CreateTaskResult` with `_meta[related-task]`. Routes
`tasks/get|cancel|result|list`. `tasks/result` blocks until terminal via
per-task waiter list. TTL timer + cancel paths release waiters with
proper errors. Terminate cleans up workers + waiters
- `Server.send_task_status/1` API → emits `notifications/tasks/status`
from session

15 deterministic tests (signal-based stub server,
`SyncHelpers.await_state` — no `Process.sleep`). 757 tests + 33 doctests
pass; credo strict clean; dialyzer pass.

## Deferred to follow-up PRs

- **Phase 2** — client-as-requestor for `tools/call`
(`Client.call_tool(task: [ttl: N])`, `get_task/task_result/cancel_task`
helpers; return shape decided when designed)
- **Phase 3** — server-as-requestor for `sampling/createMessage` +
`elicitation/create`
- **`tasks/list`** — needs auth-context binding through
`Anubis.Server.Context` first; Phase 1 returns `-32601`
- **Redis `TaskStore` adapter** — slots into the behaviour without API
change
- **`input_required` status flow** — schema accepts it; Phase 1 worker
path never produces it

## Rationale

Phase 1 ships the highest-value slice (server author exposes a
long-running tool with a single `task_support: :optional`). Pluggable
storage day-one means distributed adapters land later as drop-ins.
Strict `2025-11-25` gate keeps the spec contract clean — older protocol
versions don't see Tasks. Signal-driven tests over `Process.sleep` keep
the suite fast and deterministic, matching the existing async-dispatch
pattern.
zoedsoupe added a commit that referenced this pull request Jul 16, 2026
🚀 Want to release this?
---


##
[1.5.0](v1.4.0...v1.5.0)
(2026-05-09)


### Features

* MCP Tasks (2025-11-25) — server-receiver for tools/call
([#98](#98))
([#155](#155))
([a1c5cfc](a1c5cfc))


### Bug Fixes

* drop compile-connected deps from component/1 macro
([#154](#154))
([f79949e](f79949e))


### Continuous Integration

* fix flaky test
([2f9dce3](2f9dce3))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).
This was referenced Jul 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support MCP Tasks

1 participant