Fix AnyUrl content-type test idioms; xfail wire-unexpressible prompt/resource tasks and background sampling (sdk-feedback #3/#9)

This commit is contained in:
Jeremiah Lowin 2026-07-05 22:46:11 -04:00
commit 93e35cbe2f
No known key found for this signature in database
12 changed files with 192 additions and 5 deletions

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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")

View file

@ -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.

View file

@ -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:

View file

@ -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()

View file

@ -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] = []

View file

@ -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

View file

@ -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:

View file

@ -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

View file

@ -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)