mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 22:44:17 +02:00
TasksExtension serves io.modelcontextprotocol/tasks on the extension API: a decide-and-task tools/call interceptor (era-gated to modern connections), tasks/get with inlined results and inputRequests, tasks/update delivering poll-based in-task elicitation, tasks/cancel, durable creation, and auth-scoped task isolation. Wire models validate against the vendored ext-tasks schema. Worker-side Context hooks are refcounted so sibling servers cannot strand each other's workers. Co-Authored-By: Claude <noreply@anthropic.com>
53 lines
1.7 KiB
Python
53 lines
1.7 KiB
Python
"""Protocol-level task behavior for SEP-2663 tasks.
|
|
|
|
Generic protocol behaviors driven in-process via the task helpers: a submitted
|
|
task carries a server-generated id and a TTL, and a task whose tool raises
|
|
surfaces its error rather than a result.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from fastmcp import FastMCP
|
|
from fastmcp.exceptions import ToolError
|
|
from fastmcp_tasks import TasksExtension
|
|
from tests.tasks.task_helpers import (
|
|
run_task,
|
|
running_task_server,
|
|
submit_task,
|
|
)
|
|
|
|
|
|
def _task_server() -> FastMCP:
|
|
mcp = FastMCP("task-test-server")
|
|
mcp.add_extension(TasksExtension())
|
|
|
|
@mcp.tool(task=True)
|
|
async def simple_tool(message: str) -> str:
|
|
return f"Processed: {message}"
|
|
|
|
@mcp.tool(task=True)
|
|
async def failing_tool() -> str:
|
|
raise ToolError("This tool always fails")
|
|
|
|
return mcp
|
|
|
|
|
|
async def test_task_metadata_includes_task_id_and_ttl():
|
|
"""A submitted task carries a server-generated id and a positive TTL."""
|
|
mcp = _task_server()
|
|
async with running_task_server(mcp):
|
|
created = await submit_task(mcp, "simple_tool", {"message": "test"})
|
|
assert isinstance(created.task_id, str)
|
|
assert created.task_id
|
|
assert created.ttl_ms is not None and created.ttl_ms > 0
|
|
|
|
|
|
async def test_failed_task_stores_error():
|
|
"""A task whose tool raises reaches `failed` and stores the error."""
|
|
mcp = _task_server()
|
|
async with running_task_server(mcp):
|
|
final = await run_task(mcp, "failing_tool", {})
|
|
assert final.status == "failed"
|
|
assert final.error is not None
|
|
assert "This tool always fails" in final.error["message"]
|
|
assert final.result is None
|