mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-27 15:51:33 +02:00
Implement SEP-2663 tasks extension: TasksExtension, poll-based task lifecycle
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>
This commit is contained in:
parent
6fce4e538f
commit
5fa2883670
72 changed files with 3908 additions and 6512 deletions
|
|
@ -1,11 +1,11 @@
|
|||
from typing import Any
|
||||
|
||||
import pytest
|
||||
from mcp_types import Tool as MCPTool
|
||||
from mcp_types import ToolAnnotations, ToolExecution
|
||||
|
||||
from fastmcp import Client, FastMCP
|
||||
from fastmcp.tools.base import Tool
|
||||
from fastmcp_tasks import TasksExtension
|
||||
from tests.conftest import make_server_request_context
|
||||
|
||||
|
||||
|
|
@ -221,25 +221,25 @@ async def test_tool_functionality_with_annotations():
|
|||
assert result.data == {"name": "test_item", "value": 42}
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Phase 3: requires TasksExtension (SEP-2663 adapter)")
|
||||
async def test_task_execution_auto_populated_for_task_enabled_tool():
|
||||
"""Test that execution.task_support is automatically set when tool has task=True."""
|
||||
mcp = FastMCP("Test Server")
|
||||
mcp.add_extension(TasksExtension())
|
||||
|
||||
@mcp.tool(task=True)
|
||||
async def background_tool(data: str) -> str:
|
||||
"""A tool that runs in background."""
|
||||
return f"Processed: {data}"
|
||||
|
||||
# `execution.task_support` (SEP-1686) is advertised in the handshake-era
|
||||
# tool listing only; the modern listing omits it.
|
||||
async with Client(mcp, mode="legacy") as client:
|
||||
tools_result = await client.list_tools()
|
||||
assert len(tools_result) == 1
|
||||
assert tools_result[0].name == "background_tool"
|
||||
assert isinstance(tools_result[0], MCPTool)
|
||||
assert isinstance(tools_result[0].execution, ToolExecution)
|
||||
assert tools_result[0].execution.task_support == "optional"
|
||||
# The rendered tool descriptor auto-populates `execution.task_support` from
|
||||
# the tool's task config. (The modern wire drops the SEP-1686 `execution`
|
||||
# field, so this is asserted on the server-side render.)
|
||||
tool = await mcp.get_tool("background_tool")
|
||||
assert tool is not None
|
||||
mcp_tool = tool.to_mcp_tool()
|
||||
assert isinstance(mcp_tool, MCPTool)
|
||||
assert isinstance(mcp_tool.execution, ToolExecution)
|
||||
assert mcp_tool.execution.task_support == "optional"
|
||||
|
||||
|
||||
async def test_task_execution_omitted_for_task_disabled_tool():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue