mirror of
https://github.com/PrefectHQ/fastmcp.git
synced 2026-08-24 06:24:18 +02:00
Fix AnyUrl content-type test idioms; xfail wire-unexpressible prompt/resource tasks and background sampling (sdk-feedback #3/#9)
This commit is contained in:
parent
f12ecc7e24
commit
93e35cbe2f
12 changed files with 192 additions and 5 deletions
|
|
@ -54,6 +54,12 @@ async def test_prompt_task_server_generated_id(prompt_server):
|
|||
assert "-" in task.task_id
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_prompt_task_result_returns_get_prompt_result(prompt_server):
|
||||
"""PromptTask.result() returns GetPromptResult."""
|
||||
async with Client(prompt_server) as client:
|
||||
|
|
|
|||
|
|
@ -100,6 +100,12 @@ async def test_cached_tool_task_accessible_outside_context(task_server):
|
|||
assert result2.data == "Result: test"
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_cached_prompt_task_accessible_outside_context(task_server):
|
||||
"""Prompt tasks with cached results work outside context."""
|
||||
task = None
|
||||
|
|
@ -120,6 +126,12 @@ async def test_cached_prompt_task_accessible_outside_context(task_server):
|
|||
assert result2.description == "Prompt that runs in background."
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_cached_resource_task_accessible_outside_context(task_server):
|
||||
"""Resource tasks with cached results work outside context."""
|
||||
task = None
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ Verifies that Task.result() and await task cache results properly to avoid
|
|||
redundant server calls and ensure consistent object identity.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from fastmcp import FastMCP
|
||||
from fastmcp.client import Client
|
||||
|
||||
|
|
@ -159,6 +161,12 @@ async def test_forbidden_mode_tool_caches_error_result():
|
|||
assert result1 is result2 is result3
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_forbidden_mode_prompt_raises_error():
|
||||
"""Prompts with task=False (mode=forbidden) raise error."""
|
||||
import pytest
|
||||
|
|
@ -176,6 +184,12 @@ async def test_forbidden_mode_prompt_raises_error():
|
|||
await client.get_prompt("non_task_prompt", task=True)
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_forbidden_mode_resource_raises_error():
|
||||
"""Resources with task=False (mode=forbidden) raise error."""
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -284,6 +284,14 @@ class TestBackgroundTaskIntegration:
|
|||
result = await task.result()
|
||||
assert result.data == "ok"
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="Background-task sampling has no back-channel under SDK v2: the "
|
||||
"per-request ServerSession that would carry sampling/createMessage is "
|
||||
"gone once the submitting request completes, so ctx.sample() from a "
|
||||
"worker raises NoBackChannelError. Needs a relay like elicit() "
|
||||
"(context.py TODO); tracked in sdk-feedback.",
|
||||
strict=True,
|
||||
)
|
||||
async def test_sample_uses_origin_request_id_in_background_task(self):
|
||||
"""E2E: ctx.sample() works in a task without an active request context."""
|
||||
mcp = FastMCP("sample-background-test")
|
||||
|
|
|
|||
|
|
@ -13,6 +13,12 @@ from fastmcp.client import Client
|
|||
|
||||
|
||||
@pytest.mark.timeout(10)
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_server_tasks_true_defaults_all_components():
|
||||
"""Server with tasks=True makes all components default to supporting tasks."""
|
||||
mcp = FastMCP("test", tasks=True)
|
||||
|
|
@ -51,6 +57,12 @@ async def test_server_tasks_true_defaults_all_components():
|
|||
assert not resource_task.returned_immediately
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_server_tasks_false_defaults_all_components():
|
||||
"""Server with tasks=False makes all components default to mode=forbidden."""
|
||||
import pytest
|
||||
|
|
@ -171,6 +183,12 @@ async def test_component_explicit_true_overrides_server_false():
|
|||
assert result.is_error
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_mixed_explicit_and_inherited():
|
||||
"""Mix of explicit True/False/None on different components."""
|
||||
import pytest
|
||||
|
|
@ -286,6 +304,12 @@ async def test_server_tasks_parameter_sets_component_defaults():
|
|||
assert result.is_error
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_template_inherits_server_tasks_default():
|
||||
"""Resource templates inherit server tasks default."""
|
||||
mcp = FastMCP("test", tasks=True)
|
||||
|
|
@ -300,6 +324,12 @@ async def test_resource_template_inherits_server_tasks_default():
|
|||
assert not resource_task.returned_immediately
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_multiple_components_same_name_different_tasks():
|
||||
"""Different component types with same name can have different task settings."""
|
||||
import pytest
|
||||
|
|
@ -351,6 +381,12 @@ async def test_task_with_custom_tool_name():
|
|||
assert result.data == "result from custom-named tool"
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_task_with_custom_resource_name():
|
||||
"""Resources with custom names work correctly as tasks.
|
||||
|
||||
|
|
@ -375,6 +411,12 @@ async def test_task_with_custom_resource_name():
|
|||
assert result[0].text == "result from custom-named resource"
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_task_with_custom_template_name():
|
||||
"""Resource templates with custom names work correctly as tasks.
|
||||
|
||||
|
|
|
|||
|
|
@ -195,6 +195,12 @@ class TestResourceModeEnforcement:
|
|||
assert exc_info.value.error.code == METHOD_NOT_FOUND
|
||||
assert "requires task-augmented execution" in exc_info.value.error.message
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_required_resource_with_task_succeeds(self, server):
|
||||
"""Required mode succeeds when read with task metadata."""
|
||||
async with Client(server) as client:
|
||||
|
|
@ -247,6 +253,12 @@ class TestPromptModeEnforcement:
|
|||
assert exc_info.value.error.code == METHOD_NOT_FOUND
|
||||
assert "requires task-augmented execution" in exc_info.value.error.message
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_required_prompt_with_task_succeeds(self, server):
|
||||
"""Required mode succeeds when called with task metadata."""
|
||||
async with Client(server) as client:
|
||||
|
|
|
|||
|
|
@ -160,6 +160,12 @@ async def test_background_tool_with_multiple_dependencies(dependency_server):
|
|||
assert server_dep is dependency_server
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_background_prompt_receives_dependencies(dependency_server):
|
||||
"""Background prompts can use dependency injection."""
|
||||
dependency_server._injected_values.clear()
|
||||
|
|
@ -180,6 +186,12 @@ async def test_background_prompt_receives_dependencies(dependency_server):
|
|||
assert dep_value is dependency_server
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_background_resource_receives_dependencies(dependency_server):
|
||||
"""Background resources can use dependency injection."""
|
||||
dependency_server._injected_values.clear()
|
||||
|
|
|
|||
|
|
@ -237,6 +237,12 @@ class TestMountedPromptTasks:
|
|||
assert hasattr(task, "task_id")
|
||||
assert isinstance(task.task_id, str)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_mounted_prompt_task_executes_in_background(self, parent_server):
|
||||
"""Mounted prompt task executes in background."""
|
||||
async with Client(parent_server) as client:
|
||||
|
|
@ -273,6 +279,12 @@ class TestMountedResourceTasks:
|
|||
assert hasattr(task, "task_id")
|
||||
assert isinstance(task.task_id, str)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_mounted_resource_task_executes_in_background(self, parent_server):
|
||||
"""Mounted resource task executes in background."""
|
||||
async with Client(parent_server) as client:
|
||||
|
|
@ -289,6 +301,12 @@ class TestMountedResourceTasks:
|
|||
assert len(result) > 0
|
||||
assert "Data from child server" in result[0].text
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_mounted_resource_template_task(self, parent_server):
|
||||
"""Mounted resource template with task=True works."""
|
||||
async with Client(parent_server) as client:
|
||||
|
|
@ -786,6 +804,12 @@ class TestMiddlewareWithMountedTasks:
|
|||
"grandchild:tool", # Executes in Docket after middleware completes
|
||||
]
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_middleware_runs_with_background_task(self):
|
||||
"""Middleware runs at parent, child, and grandchild levels for resource tasks."""
|
||||
calls: list[str] = []
|
||||
|
|
@ -823,6 +847,12 @@ class TestMiddlewareWithMountedTasks:
|
|||
"grandchild:resource",
|
||||
]
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_prompt_middleware_runs_with_background_task(self):
|
||||
"""Middleware runs at parent, child, and grandchild levels for prompt tasks."""
|
||||
calls: list[str] = []
|
||||
|
|
@ -860,6 +890,12 @@ class TestMiddlewareWithMountedTasks:
|
|||
"grandchild:prompt",
|
||||
]
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_template_middleware_runs_with_background_task(self):
|
||||
"""Middleware runs at all levels for resource template tasks."""
|
||||
calls: list[str] = []
|
||||
|
|
|
|||
|
|
@ -51,6 +51,12 @@ async def test_prompt_with_task_metadata_returns_immediately(prompt_server):
|
|||
assert len(task.task_id) > 0
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_prompt_task_executes_in_background(prompt_server):
|
||||
"""Prompt task executes via Docket in background."""
|
||||
async with Client(prompt_server) as client:
|
||||
|
|
@ -68,6 +74,12 @@ async def test_prompt_task_executes_in_background(prompt_server):
|
|||
assert "comprehensive" in result.messages[0].content.text.lower()
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_forbidden_mode_prompt_rejects_task_calls(prompt_server):
|
||||
"""Prompts with task=False (mode=forbidden) reject task-augmented calls."""
|
||||
from mcp.shared.exceptions import MCPError
|
||||
|
|
|
|||
|
|
@ -127,6 +127,12 @@ class TestProxyPromptsSyncExecution:
|
|||
class TestProxyPromptsTaskForbidden:
|
||||
"""Test that prompts with task=True are forbidden through proxy."""
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_prompt_task_raises_mcp_error(self, proxy_server: FastMCP):
|
||||
"""Prompt called with task=True through proxy raises MCPError."""
|
||||
async with Client(proxy_server) as client:
|
||||
|
|
@ -157,6 +163,12 @@ class TestProxyResourcesSyncExecution:
|
|||
class TestProxyResourcesTaskForbidden:
|
||||
"""Test that resources with task=True are forbidden through proxy."""
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_task_raises_mcp_error(self, proxy_server: FastMCP):
|
||||
"""Resource read with task=True through proxy raises MCPError."""
|
||||
async with Client(proxy_server) as client:
|
||||
|
|
@ -165,6 +177,12 @@ class TestProxyResourcesTaskForbidden:
|
|||
|
||||
assert "does not support task-augmented execution" in str(exc_info.value)
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_template_task_raises_mcp_error(self, proxy_server: FastMCP):
|
||||
"""Resource template with task=True through proxy raises MCPError."""
|
||||
async with Client(proxy_server) as client:
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ async def test_resource_with_task_metadata_returns_immediately(resource_server):
|
|||
assert len(task.task_id) > 0
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_task_executes_in_background(resource_server):
|
||||
"""Resource task executes via Docket in background."""
|
||||
async with Client(resource_server) as client:
|
||||
|
|
@ -70,6 +76,12 @@ async def test_resource_task_executes_in_background(resource_server):
|
|||
assert result[0].text == "Large file content that takes time to load"
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_resource_template_with_task(resource_server):
|
||||
"""Resource templates with task=True execute in background."""
|
||||
async with Client(resource_server) as client:
|
||||
|
|
@ -83,6 +95,12 @@ async def test_resource_template_with_task(resource_server):
|
|||
assert '"userId": "123"' in result[0].text
|
||||
|
||||
|
||||
@pytest.mark.xfail(
|
||||
reason="SDK v2 has no `task` field on GetPromptRequestParams / "
|
||||
"ReadResourceRequestParams; prompt/resource task submission is not "
|
||||
"wire-expressible and always graceful-degrades (sdk-feedback #3).",
|
||||
strict=True,
|
||||
)
|
||||
async def test_forbidden_mode_resource_rejects_task_calls(resource_server):
|
||||
"""Resources with task=False (mode=forbidden) reject task-augmented calls."""
|
||||
import pytest
|
||||
|
|
|
|||
|
|
@ -565,7 +565,6 @@ async def mcp_content_server(tmp_path):
|
|||
TextContent,
|
||||
TextResourceContents,
|
||||
)
|
||||
from pydantic import AnyUrl
|
||||
|
||||
mcp = FastMCP("content-test")
|
||||
|
||||
|
|
@ -588,15 +587,13 @@ async def mcp_content_server(tmp_path):
|
|||
async def return_embedded_resource() -> EmbeddedResource:
|
||||
return EmbeddedResource(
|
||||
type="resource",
|
||||
resource=TextResourceContents(
|
||||
uri=AnyUrl("test://resource"), text="embedded"
|
||||
),
|
||||
resource=TextResourceContents(uri="test://resource", text="embedded"),
|
||||
)
|
||||
|
||||
@mcp.tool(task=True)
|
||||
async def return_resource_link() -> ResourceLink:
|
||||
return ResourceLink(
|
||||
type="resource_link", uri=AnyUrl("test://linked"), name="Test Resource"
|
||||
type="resource_link", uri="test://linked", name="Test Resource"
|
||||
)
|
||||
|
||||
@mcp.tool(task=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue