From 93e35cbe2faf50906d80e629efd83833f8d3993b Mon Sep 17 00:00:00 2001 From: Jeremiah Lowin <153965+jlowin@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:46:11 -0400 Subject: [PATCH] Fix AnyUrl content-type test idioms; xfail wire-unexpressible prompt/resource tasks and background sampling (sdk-feedback #3/#9) --- .../client/tasks/test_client_prompt_tasks.py | 6 +++ .../tasks/test_task_context_validation.py | 12 ++++++ .../client/tasks/test_task_result_caching.py | 14 +++++++ .../tasks/test_context_background_task.py | 8 ++++ .../tasks/test_server_tasks_parameter.py | 42 +++++++++++++++++++ tests/server/tasks/test_task_config.py | 12 ++++++ tests/server/tasks/test_task_dependencies.py | 12 ++++++ tests/server/tasks/test_task_mount.py | 36 ++++++++++++++++ tests/server/tasks/test_task_prompts.py | 12 ++++++ tests/server/tasks/test_task_proxy.py | 18 ++++++++ tests/server/tasks/test_task_resources.py | 18 ++++++++ tests/server/tasks/test_task_return_types.py | 7 +--- 12 files changed, 192 insertions(+), 5 deletions(-) diff --git a/tests/client/tasks/test_client_prompt_tasks.py b/tests/client/tasks/test_client_prompt_tasks.py index fd8445c40..57f55e0a3 100644 --- a/tests/client/tasks/test_client_prompt_tasks.py +++ b/tests/client/tasks/test_client_prompt_tasks.py @@ -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: diff --git a/tests/client/tasks/test_task_context_validation.py b/tests/client/tasks/test_task_context_validation.py index ea236f286..fb4765e52 100644 --- a/tests/client/tasks/test_task_context_validation.py +++ b/tests/client/tasks/test_task_context_validation.py @@ -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 diff --git a/tests/client/tasks/test_task_result_caching.py b/tests/client/tasks/test_task_result_caching.py index ae0ccd2da..fdb48e129 100644 --- a/tests/client/tasks/test_task_result_caching.py +++ b/tests/client/tasks/test_task_result_caching.py @@ -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 diff --git a/tests/server/tasks/test_context_background_task.py b/tests/server/tasks/test_context_background_task.py index e5a1e59f8..2089db41a 100644 --- a/tests/server/tasks/test_context_background_task.py +++ b/tests/server/tasks/test_context_background_task.py @@ -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") diff --git a/tests/server/tasks/test_server_tasks_parameter.py b/tests/server/tasks/test_server_tasks_parameter.py index 5e69c0c5a..acb30811e 100644 --- a/tests/server/tasks/test_server_tasks_parameter.py +++ b/tests/server/tasks/test_server_tasks_parameter.py @@ -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. diff --git a/tests/server/tasks/test_task_config.py b/tests/server/tasks/test_task_config.py index d4667a298..d6e095509 100644 --- a/tests/server/tasks/test_task_config.py +++ b/tests/server/tasks/test_task_config.py @@ -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: diff --git a/tests/server/tasks/test_task_dependencies.py b/tests/server/tasks/test_task_dependencies.py index 129f0908d..0aef545ee 100644 --- a/tests/server/tasks/test_task_dependencies.py +++ b/tests/server/tasks/test_task_dependencies.py @@ -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() diff --git a/tests/server/tasks/test_task_mount.py b/tests/server/tasks/test_task_mount.py index 06d9acb9d..0401259c1 100644 --- a/tests/server/tasks/test_task_mount.py +++ b/tests/server/tasks/test_task_mount.py @@ -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] = [] diff --git a/tests/server/tasks/test_task_prompts.py b/tests/server/tasks/test_task_prompts.py index a66167a7f..ca62a3a0f 100644 --- a/tests/server/tasks/test_task_prompts.py +++ b/tests/server/tasks/test_task_prompts.py @@ -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 diff --git a/tests/server/tasks/test_task_proxy.py b/tests/server/tasks/test_task_proxy.py index 582e5876e..c272a4444 100644 --- a/tests/server/tasks/test_task_proxy.py +++ b/tests/server/tasks/test_task_proxy.py @@ -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: diff --git a/tests/server/tasks/test_task_resources.py b/tests/server/tasks/test_task_resources.py index e8b1e289e..f7768adc7 100644 --- a/tests/server/tasks/test_task_resources.py +++ b/tests/server/tasks/test_task_resources.py @@ -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 diff --git a/tests/server/tasks/test_task_return_types.py b/tests/server/tasks/test_task_return_types.py index 325f7df2e..15452c79f 100644 --- a/tests/server/tasks/test_task_return_types.py +++ b/tests/server/tasks/test_task_return_types.py @@ -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)