Address review feedback on SEP-2663 tasks

- Client task support is opt-in via importing fastmcp_tasks (drop the core
  auto-load of companion packages); a plain Client never advertises tasks.
- A worker restores the submitting caller's auth token and headers from the
  task snapshot into the standard ambient context, so get_access_token() /
  get_http_headers() work in a distributed worker with no new core hooks.
- worker_cli validates the loaded extension's resolved backend, not env defaults,
  so a constructor-configured Redis worker starts.
- Thread the per-call read timeout through task polling; bound ToolTask.wait by
  its deadline; set_elicitation_callback rebuilds internal extensions so a
  later-set handler answers in-task input.
- README imports TaskConfig from fastmcp.utilities.tasks.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Jeremiah Lowin 2026-07-22 19:14:16 -04:00
commit 19c5c507cc
No known key found for this signature in database
11 changed files with 259 additions and 58 deletions

View file

@ -1,27 +1,48 @@
"""Tests for the fastmcp tasks CLI."""
import pytest
from fastmcp_tasks.settings import docket_settings
from fastmcp_tasks.worker_cli import check_distributed_backend, tasks_app
from fastmcp_tasks.settings import DocketSettings
from fastmcp_tasks.worker_cli import (
check_distributed_backend,
resolve_docket_settings,
tasks_app,
)
from fastmcp import FastMCP
from fastmcp_tasks import TasksExtension
class TestResolveDocketSettings:
"""`resolve_docket_settings` reads the server's *registered* extension."""
def test_reads_the_registered_extensions_settings(self):
"""The constructor-configured URL is visible without any env var."""
mcp = FastMCP("t")
mcp.add_extension(TasksExtension(url="redis://example:6379/0"))
settings = resolve_docket_settings(mcp)
assert settings.url == "redis://example:6379/0"
def test_exits_when_no_tasks_extension_registered(self):
"""A server with no TasksExtension has nothing for the CLI to serve."""
mcp = FastMCP("t")
with pytest.raises(SystemExit) as exc_info:
resolve_docket_settings(mcp)
assert exc_info.value.code == 1
class TestCheckDistributedBackend:
"""Test the distributed backend checker function."""
def test_succeeds_with_redis_url(self, monkeypatch: pytest.MonkeyPatch):
def test_succeeds_with_redis_url(self):
"""Test that it succeeds with Redis URL."""
# Docket settings moved to `fastmcp_tasks.settings.DocketSettings`
# (env prefix `FASTMCP_DOCKET_`), so patch the settings object directly.
monkeypatch.setattr(docket_settings, "url", "redis://localhost:6379/0")
check_distributed_backend()
settings = DocketSettings(url="redis://localhost:6379/0")
check_distributed_backend(settings)
def test_exits_with_helpful_error_for_memory_url(
self, monkeypatch: pytest.MonkeyPatch
):
def test_exits_with_helpful_error_for_memory_url(self):
"""Test that it exits with helpful error for memory:// URLs."""
monkeypatch.setattr(docket_settings, "url", "memory://test-123")
settings = DocketSettings(url="memory://test-123")
with pytest.raises(SystemExit) as exc_info:
check_distributed_backend()
check_distributed_backend(settings)
assert isinstance(exc_info.value, SystemExit)
assert exc_info.value.code == 1