Repository navigation
feat: MCP Tasks (2025-11-25) — server-receiver for tools/call (#98) - #155
Conversation
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Repository UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (5)
📝 WalkthroughWalkthroughThis 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
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches✨ Simplify code
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 11
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 708ac6f2-dae4-44bf-86d2-ccd4843fa505
📒 Files selected for processing (21)
lib/anubis/mcp/message.exlib/anubis/protocol/registry.exlib/anubis/protocol/v2025_11_25.exlib/anubis/server.exlib/anubis/server/component.exlib/anubis/server/component/tool.exlib/anubis/server/frame.exlib/anubis/server/handlers/tasks.exlib/anubis/server/handlers/tools.exlib/anubis/server/registry.exlib/anubis/server/session.exlib/anubis/server/supervisor.exlib/anubis/server/task.exlib/anubis/server/task_store.exlib/anubis/server/task_store/local.exlib/anubis/server/transport/sse.exlib/anubis/server/transport/streamable_http/plug.extest/anubis/protocol/registry_test.exstest/anubis/protocol_test.exstest/anubis/server/tasks_test.exstest/support/tasks_stub_server.ex
There was a problem hiding this comment.
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 valueP3 —
:task_storeschema is loose; bad opts produce a deferredKeyErrorat init.The schema
{:list, :any}accepts any list (e.g.[1,2,3]or[]), so a misconfigured supervisor only blows up later insidebuild_task_store/1viaKeyword.fetch!/2. A keyword schema would surface the misconfiguration atparse_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
📒 Files selected for processing (6)
lib/anubis/mcp/message.exlib/anubis/protocol/v2025_11_25.exlib/anubis/server/handlers/tasks.exlib/anubis/server/session.exlib/anubis/server/task_store.extest/anubis/server/tasks_test.exs
🚀 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).
…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.
🚀 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).
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/callonly. Implements:V2025_11_25(latest); registerstasks/get|result|cancel|list+notifications/tasks/status:taskscapability onuse Anubis.Server(list?,cancel?,requests:)task_support: :forbidden | :optional | :required; renders asexecution.taskSupportintools/list; enforced inHandlers.Tools(required→reject non-augmented, forbidden→reject augmented)Anubis.Server.Taskstruct +Anubis.Server.TaskStorebehaviour withLocaladapter; pluggable via:task_store {mod, opts}inAnubis.Server.Supervisor(mirrors:registry/:supervisor); supports:viafor distributed adapters via optionalresolve_name/2callbackFrame.task_idpropagated to worker callbacksparams.taskontools/call, persistsTask, spawns worker via existingTask.Supervisor, immediately returnsCreateTaskResultwith_meta[related-task]. Routestasks/get|cancel|result|list.tasks/resultblocks until terminal via per-task waiter list. TTL timer + cancel paths release waiters with proper errors. Terminate cleans up workers + waitersServer.send_task_status/1API → emitsnotifications/tasks/statusfrom session15 deterministic tests (signal-based stub server,
SyncHelpers.await_state— noProcess.sleep). 757 tests + 33 doctests pass; credo strict clean; dialyzer pass.Deferred to follow-up PRs
tools/call(Client.call_tool(task: [ttl: N]),get_task/task_result/cancel_taskhelpers; return shape decided when designed)sampling/createMessage+elicitation/createtasks/list— needs auth-context binding throughAnubis.Server.Contextfirst; Phase 1 returns-32601TaskStoreadapter — slots into the behaviour without API changeinput_requiredstatus flow — schema accepts it; Phase 1 worker path never produces itRationale
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. Strict2025-11-25gate keeps the spec contract clean — older protocol versions don't see Tasks. Signal-driven tests overProcess.sleepkeep the suite fast and deterministic, matching the existing async-dispatch pattern.