diff --git a/fastmcp_tasks/fastmcp_tasks/models.py b/fastmcp_tasks/fastmcp_tasks/models.py index d6b3a9875..1ce1cc07a 100644 --- a/fastmcp_tasks/fastmcp_tasks/models.py +++ b/fastmcp_tasks/fastmcp_tasks/models.py @@ -45,9 +45,7 @@ __all__ = [ #: the tasks extension in for the request. MISSING_REQUIRED_CLIENT_CAPABILITY = -32003 -TaskStatus = Literal[ - "working", "input_required", "completed", "failed", "cancelled" -] +TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"] class _TaskFields(BaseModel): @@ -69,7 +67,9 @@ class _TaskFields(BaseModel): created_at: str = Field(serialization_alias="createdAt") last_updated_at: str = Field(serialization_alias="lastUpdatedAt") ttl_ms: float | None = Field(serialization_alias="ttlMs") - status_message: str | None = Field(default=None, serialization_alias="statusMessage") + status_message: str | None = Field( + default=None, serialization_alias="statusMessage" + ) poll_interval_ms: float | None = Field( default=None, serialization_alias="pollIntervalMs" ) diff --git a/pyproject.toml b/pyproject.toml index e4deaae1e..d34a8fc68 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -155,10 +155,9 @@ exclude = [ "examples/providers/sqlite", # needs aiosqlite "examples/memory.py", # needs asyncpg, numpy, pydantic_ai, pgvector "examples/get_file.py", # needs aiohttp - # The moved task tests pass at runtime but carry ty diagnostics (mostly - # None-narrowing on optional result fields); a follow-up commit fixes them - # and removes this exclusion. - "tests/tasks", + # Skipped pending client task support; rewritten in the client-task follow-up. + "tests/tasks/client/test_task_context_validation.py", + "tests/tasks/client/test_task_result_caching.py", ] [tool.ty.environment] diff --git a/tests/tasks/server/test_concurrent_dependencies.py b/tests/tasks/server/test_concurrent_dependencies.py index 009723d25..4cc33e5dc 100644 --- a/tests/tasks/server/test_concurrent_dependencies.py +++ b/tests/tasks/server/test_concurrent_dependencies.py @@ -99,6 +99,7 @@ async def test_concurrent_background_tasks_with_context(): assert len(finals) == 4 for final in finals: assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"]["result"].startswith("bg:") @@ -134,6 +135,7 @@ async def test_concurrent_background_tasks_with_progress(): assert len(finals) == 4 for final in finals: assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"]["result"].startswith("bg:") @@ -202,6 +204,7 @@ async def test_sync_context_functions_work_in_background_without_deps(): final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"has_headers": "False"} @@ -225,4 +228,5 @@ async def test_sync_context_functions_work_in_background_with_context(): final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"]["is_background"] == "True" diff --git a/tests/tasks/server/test_context_background_task.py b/tests/tasks/server/test_context_background_task.py index 6cbc79e3f..0bf470f6b 100644 --- a/tests/tasks/server/test_context_background_task.py +++ b/tests/tasks/server/test_context_background_task.py @@ -356,6 +356,7 @@ class TestBackgroundTaskIntegration: final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "done"} async def test_context_wiring_in_background_task(self): @@ -382,6 +383,7 @@ class TestBackgroundTaskIntegration: final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == { "task_id_set": True, "is_background": True, @@ -406,6 +408,7 @@ class TestBackgroundTaskIntegration: parked = await wait_for_task( mcp, created.task_id, target_states=frozenset({"input_required"}) ) + assert parked.input_requests is not None key = next(iter(parked.input_requests)) await update_task( mcp, @@ -415,6 +418,7 @@ class TestBackgroundTaskIntegration: final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "Hello, Bob!"} async def test_elicit_decline_flow(self): @@ -436,11 +440,13 @@ class TestBackgroundTaskIntegration: parked = await wait_for_task( mcp, created.task_id, target_states=frozenset({"input_required"}) ) + assert parked.input_requests is not None key = next(iter(parked.input_requests)) await update_task(mcp, created.task_id, {key: {"action": "decline"}}) final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "User declined"} async def test_elicit_with_pydantic_model(self): @@ -466,6 +472,7 @@ class TestBackgroundTaskIntegration: parked = await wait_for_task( mcp, created.task_id, target_states=frozenset({"input_required"}) ) + assert parked.input_requests is not None key = next(iter(parked.input_requests)) await update_task( mcp, @@ -475,6 +482,7 @@ class TestBackgroundTaskIntegration: final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "Alice is 30"} @@ -511,6 +519,7 @@ class TestAccessTokenInBackgroundTasks: final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == { "result": "roundtrip-jwt|test-client" } @@ -530,6 +539,7 @@ class TestAccessTokenInBackgroundTasks: final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "no-token"} diff --git a/tests/tasks/server/test_custom_subclass_tasks.py b/tests/tasks/server/test_custom_subclass_tasks.py index c9b41fafc..6c45baafb 100644 --- a/tests/tasks/server/test_custom_subclass_tasks.py +++ b/tests/tasks/server/test_custom_subclass_tasks.py @@ -92,6 +92,7 @@ async def test_custom_tool_background_execution(custom_tool_server): final = await run_task(custom_tool_server, "custom_tool", {}) assert final.status == "completed" + assert final.result is not None assert "Custom tool executed" in final.result["content"][0]["text"] @@ -101,6 +102,7 @@ async def test_custom_tool_with_arguments(custom_tool_server): final = await run_task(custom_tool_server, "custom_logic", {"duration": 1}) assert final.status == "completed" + assert final.result is not None assert "Completed after 1 units" in final.result["content"][0]["text"] diff --git a/tests/tasks/server/test_extension.py b/tests/tasks/server/test_extension.py index 64eb9ad5a..21fd2b7fd 100644 --- a/tests/tasks/server/test_extension.py +++ b/tests/tasks/server/test_extension.py @@ -12,6 +12,7 @@ from __future__ import annotations import asyncio from contextlib import AsyncExitStack from types import SimpleNamespace +from typing import cast import pytest from fastmcp_tasks.models import ( @@ -19,6 +20,7 @@ from fastmcp_tasks.models import ( CreateTaskResult, ) from mcp.server.context import ServerRequestContext +from mcp.server.session import ServerSession from mcp.shared.exceptions import MCPError from fastmcp import FastMCP @@ -140,6 +142,7 @@ async def test_required_tool_tasks_when_opted_in(): created = await submit_task(mcp, "must_task", {"n": 10}) final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"]["result"] == 11 @@ -197,6 +200,7 @@ async def test_task_arguments_are_coerced_like_sync_path(): async with running_task_server(mcp): # "6" coerces to int 6 exactly as the synchronous path would. final = await run_task(mcp, "square", {"n": "6"}) + assert final.result is not None assert final.result["structuredContent"]["result"] == 36 @@ -308,7 +312,7 @@ async def test_legacy_era_opt_in_is_ignored(): mcp = _tasks_server() async with running_task_server(mcp): srctx = ServerRequestContext( - session=SimpleNamespace(), + session=cast(ServerSession, SimpleNamespace()), lifespan_context={}, protocol_version="2025-06-18", method="tools/call", @@ -324,7 +328,7 @@ async def test_legacy_era_required_tool_raises_missing_capability(): mcp = _tasks_server() async with running_task_server(mcp): srctx = ServerRequestContext( - session=SimpleNamespace(), + session=cast(ServerSession, SimpleNamespace()), lifespan_context={}, protocol_version="2025-06-18", method="tools/call", diff --git a/tests/tasks/server/test_progress_dependency.py b/tests/tasks/server/test_progress_dependency.py index 98401d9d2..d948ed251 100644 --- a/tests/tasks/server/test_progress_dependency.py +++ b/tests/tasks/server/test_progress_dependency.py @@ -48,6 +48,7 @@ async def test_progress_in_background_task(): created = await submit_task(mcp, "test_task", {}) final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "done"} @@ -97,6 +98,7 @@ async def test_progress_status_message_in_background_task(): release.set() final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "done"} diff --git a/tests/tasks/server/test_server_tasks_parameter.py b/tests/tasks/server/test_server_tasks_parameter.py index 6ebb6d121..e815e79c0 100644 --- a/tests/tasks/server/test_server_tasks_parameter.py +++ b/tests/tasks/server/test_server_tasks_parameter.py @@ -130,6 +130,7 @@ async def test_task_with_custom_tool_name(): async with running_task_server(mcp): final = await run_task(mcp, "custom-tool-name") assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == { "result": "result from custom-named tool" } diff --git a/tests/tasks/server/test_snapshot_restore.py b/tests/tasks/server/test_snapshot_restore.py index 532ed7cbb..d69ff89df 100644 --- a/tests/tasks/server/test_snapshot_restore.py +++ b/tests/tasks/server/test_snapshot_restore.py @@ -48,6 +48,7 @@ async def test_snapshot_restored_before_user_code_runs(): final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": True} @@ -75,6 +76,7 @@ async def test_get_access_token_in_bg_task_without_context_dep(): final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "jwt-3897"} @@ -99,6 +101,7 @@ async def test_restore_failure_is_nonfatal(): final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": False} diff --git a/tests/tasks/server/test_task_config.py b/tests/tasks/server/test_task_config.py index 2f687da77..dc214acb5 100644 --- a/tests/tasks/server/test_task_config.py +++ b/tests/tasks/server/test_task_config.py @@ -180,6 +180,7 @@ class TestToolExecutionMetadata: return "ok" tool = await mcp.get_tool("my_tool") + assert tool is not None execution = tool.to_mcp_tool().execution assert isinstance(execution, ToolExecution) assert execution.task_support == "optional" @@ -194,6 +195,7 @@ class TestToolExecutionMetadata: return "ok" tool = await mcp.get_tool("my_tool") + assert tool is not None execution = tool.to_mcp_tool().execution assert isinstance(execution, ToolExecution) assert execution.task_support == "required" @@ -208,6 +210,7 @@ class TestToolExecutionMetadata: return "ok" tool = await mcp.get_tool("my_tool") + assert tool is not None assert tool.to_mcp_tool().execution is None diff --git a/tests/tasks/server/test_task_dependencies.py b/tests/tasks/server/test_task_dependencies.py index e40770d46..d00a4dab4 100644 --- a/tests/tasks/server/test_task_dependencies.py +++ b/tests/tasks/server/test_task_dependencies.py @@ -73,6 +73,7 @@ async def test_background_tool_receives_docket_dependency(dependency_server): final = await run_task(dependency_server, "tool_with_docket_dependency", {}) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "Docket: True"} assert len(dependency_server._injected_values) == 1 dep_type, dep_value = dependency_server._injected_values[0] @@ -88,6 +89,7 @@ async def test_background_tool_receives_server_dependency(dependency_server): final = await run_task(dependency_server, "tool_with_server_dependency", {}) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == { "result": f"Server: {dependency_server.name}" } @@ -107,6 +109,7 @@ async def test_background_tool_receives_custom_depends(dependency_server): ) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": 50} # 5 * 10 assert len(dependency_server._injected_values) == 1 dep_type, dep_value = dependency_server._injected_values[0] @@ -124,6 +127,7 @@ async def test_background_tool_with_multiple_dependencies(dependency_server): ) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == { "result": f"test on {dependency_server.name}" } @@ -180,6 +184,7 @@ async def test_dependency_context_managers_cleaned_up_in_background(): final = await run_task(mcp, "use_connection", {"name": "test"}) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "Used: connection"} assert cleanup_called == ["enter", "exit"] diff --git a/tests/tasks/server/test_task_elicitation_relay.py b/tests/tasks/server/test_task_elicitation_relay.py index a36e202fe..ab1096bbd 100644 --- a/tests/tasks/server/test_task_elicitation_relay.py +++ b/tests/tasks/server/test_task_elicitation_relay.py @@ -62,6 +62,7 @@ async def _drive(server: FastMCP, name: str, answers: list[dict[str, Any]]) -> s await update_task(server, created.task_id, {key: answer}) final = await wait_for_task(server, created.task_id) assert final.status == "completed", final.error + assert final.result is not None return final.result["content"][0]["text"] @@ -214,4 +215,5 @@ async def test_unanswered_input_times_out_to_cancel(monkeypatch): # Never answer; the worker's bounded wait resolves to cancel. final = await wait_for_task(mcp, created.task_id, timeout=10.0) assert final.status == "completed" + assert final.result is not None assert final.result["content"][0]["text"] == "Cancelled as expected" diff --git a/tests/tasks/server/test_task_methods.py b/tests/tasks/server/test_task_methods.py index 3558ba8f8..d7bc39731 100644 --- a/tests/tasks/server/test_task_methods.py +++ b/tests/tasks/server/test_task_methods.py @@ -53,7 +53,9 @@ async def test_tasks_get_returns_status_and_inlined_result(): final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": 42} + assert final.result is not None assert final.result["isError"] is False diff --git a/tests/tasks/server/test_task_mount.py b/tests/tasks/server/test_task_mount.py index 97408d1d0..15dfbee4d 100644 --- a/tests/tasks/server/test_task_mount.py +++ b/tests/tasks/server/test_task_mount.py @@ -20,6 +20,7 @@ Two architectural notes vs. SEP-1686: from __future__ import annotations import asyncio +from typing import cast import mcp_types as mt import pytest @@ -31,7 +32,7 @@ from mcp_types import ToolExecution from fastmcp import Context, FastMCP from fastmcp.server.dependencies import CurrentFastMCP from fastmcp.server.middleware import CallNext, Middleware, MiddlewareContext -from fastmcp.server.providers.proxy import ProxyTool +from fastmcp.server.providers.proxy import ClientFactoryT, ProxyTool from fastmcp.tools.base import ToolResult from fastmcp.utilities.tasks import TaskConfig from fastmcp_tasks import TasksExtension @@ -90,6 +91,7 @@ class TestMountedToolTasks: assert created.status == "working" final = await wait_for_task(parent_server, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"]["result"] == 72 async def test_mounted_and_parent_tasks_both_work(self, parent_server): @@ -102,7 +104,9 @@ class TestMountedToolTasks: ) parent_final = await wait_for_task(parent_server, parent_created.task_id) child_final = await wait_for_task(parent_server, child_created.task_id) + assert parent_final.result is not None assert parent_final.result["structuredContent"]["result"] == 50 + assert child_final.result is not None assert child_final.result["structuredContent"]["result"] == 6 async def test_sync_only_mounted_tool_runs_synchronously(self, parent_server): @@ -129,6 +133,7 @@ class TestMountedToolTasksNoPrefix: parent, (await submit_task(parent, "multiply", {"a": 5, "b": 6})).task_id, ) + assert final.result is not None assert final.result["structuredContent"]["result"] == 30 @@ -137,7 +142,7 @@ class TestMountedTaskDependencies: child = FastMCP("dep-child") @child.tool(task=True) - async def tool_with_docket(docket: CurrentDocket = CurrentDocket()) -> str: # type: ignore[assignment,valid-type] + async def tool_with_docket(docket: Docket = CurrentDocket()) -> str: return f"docket available: {docket is not None}" parent = FastMCP("dep-parent") @@ -149,6 +154,7 @@ class TestMountedTaskDependencies: parent, (await submit_task(parent, "child_tool_with_docket", {})).task_id, ) + assert final.result is not None assert "docket available: True" in final.result["content"][0]["text"] @@ -157,7 +163,7 @@ class TestMountedTaskServerContext: child = FastMCP("child") @child.tool(task=True) - async def whoami(server: CurrentFastMCP = CurrentFastMCP()) -> str: # type: ignore[assignment,valid-type] + async def whoami(server: FastMCP = CurrentFastMCP()) -> str: return f"server name: {server.name}" parent = FastMCP("parent") @@ -168,6 +174,7 @@ class TestMountedTaskServerContext: final = await wait_for_task( parent, (await submit_task(parent, "child_whoami", {})).task_id ) + assert final.result is not None assert "server name: child" in final.result["content"][0]["text"] async def test_context_fastmcp_resolves_to_child_server(self): @@ -185,6 +192,7 @@ class TestMountedTaskServerContext: final = await wait_for_task( parent, (await submit_task(parent, "child_whoami_ctx", {})).task_id ) + assert final.result is not None assert "context server: child" in final.result["content"][0]["text"] @@ -217,7 +225,9 @@ class TestMultipleMounts: await submit_task(parent, "math2_subtract", {"a": 10, "b": 5}) ).task_id, ) + assert r1.result is not None assert r1.result["structuredContent"]["result"] == 15 + assert r2.result is not None assert r2.result["structuredContent"]["result"] == 5 async def test_same_function_names_do_not_collide(self): @@ -246,7 +256,9 @@ class TestMultipleMounts: parent, (await submit_task(parent, "c2_process", {"value": 10})).task_id, ) + assert r1.result is not None assert r1.result["structuredContent"]["result"] == 20 + assert r2.result is not None assert r2.result["structuredContent"]["result"] == 30 async def test_nested_mount_prefix_accumulation(self): @@ -267,6 +279,7 @@ class TestMultipleMounts: parent, (await submit_task(parent, "child_gc_deep_tool", {})).task_id, ) + assert final.result is not None assert final.result["structuredContent"]["result"] == "deep" @@ -286,6 +299,8 @@ class TestMountedTaskMetadata: child_mcp = child_tool.to_mcp_tool(name=child_tool.name) parent_mcp = parent_tool.to_mcp_tool(name=parent_tool.name) + assert child_mcp.execution is not None + assert parent_mcp.execution is not None assert child_mcp.execution.task_support == "optional" assert parent_mcp.execution.task_support == "optional" @@ -296,7 +311,7 @@ class TestMountedTaskMetadata: input_schema={"type": "object", "properties": {}}, execution=ToolExecution(task_support="optional"), ) - proxy = ProxyTool.from_mcp_tool(lambda: None, mcp_tool) # type: ignore[arg-type] + proxy = ProxyTool.from_mcp_tool(cast(ClientFactoryT, lambda: None), mcp_tool) result = proxy.to_mcp_tool(name=proxy.name) assert result.execution is not None assert result.execution.task_support == "optional" @@ -339,6 +354,7 @@ class TestMountedTaskConfigModes: await submit_task(parent_with_modes, "child_optional_tool", {}) ).task_id, ) + assert final.result is not None assert final.result["structuredContent"]["result"] == "optional result" async def test_required_mode_with_task_through_mount(self, parent_with_modes): @@ -349,6 +365,7 @@ class TestMountedTaskConfigModes: await submit_task(parent_with_modes, "child_required_tool", {}) ).task_id, ) + assert final.result is not None assert final.result["structuredContent"]["result"] == "required result" async def test_required_mode_without_task_through_mount(self, parent_with_modes): @@ -417,6 +434,7 @@ class TestMiddlewareWithMountedTasks: async with running_task_server(parent): created = await submit_task(parent, "c_gc_compute", {"x": 5}) final = await wait_for_task(parent, created.task_id) + assert final.result is not None assert final.result["structuredContent"]["result"] == 10 assert calls == ["parent:before", "parent:after", "grandchild:tool"] diff --git a/tests/tasks/server/test_task_security.py b/tests/tasks/server/test_task_security.py index 088f52153..f20ea606c 100644 --- a/tests/tasks/server/test_task_security.py +++ b/tests/tasks/server/test_task_security.py @@ -47,7 +47,9 @@ async def test_same_client_can_access_all_its_tasks(task_server: FastMCP): second = await run_task( task_server, "secret_tool", {"data": "second"}, access_token=token ) + assert first.result is not None assert "first" in first.result["content"][0]["text"] + assert second.result is not None assert "second" in second.result["content"][0]["text"] @@ -55,6 +57,7 @@ async def test_unauthenticated_client_can_access_its_tasks(task_server: FastMCP) """An anonymous caller can resolve tasks in the anonymous keyspace.""" async with running_task_server(task_server): final = await run_task(task_server, "secret_tool", {"data": "hello"}) + assert final.result is not None assert "hello" in final.result["content"][0]["text"] diff --git a/tests/tasks/server/test_task_tools.py b/tests/tasks/server/test_task_tools.py index 31aa1666f..2ffd0f192 100644 --- a/tests/tasks/server/test_task_tools.py +++ b/tests/tasks/server/test_task_tools.py @@ -84,6 +84,7 @@ async def test_task_tool_coerces_model_arguments(): final = await run_task(mcp, "inspect_items", arguments) assert sync_result.structured_content == expected + assert final.result is not None assert final.result["structuredContent"] == expected @@ -99,6 +100,7 @@ async def test_task_arguments_are_coerced_like_sync_path(): async with running_task_server(mcp): final = await run_task(mcp, "square", {"n": "1"}) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": 1} @@ -137,6 +139,7 @@ async def test_valid_argument_submits_under_strict_validation(): async with running_task_server(mcp): final = await run_task(mcp, "square", {"n": 4}) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": 16} @@ -202,6 +205,7 @@ async def test_tool_task_executes_in_background(): finish.set() final = await wait_for_task(mcp, created.task_id) assert final.status == "completed" + assert final.result is not None assert final.result["structuredContent"] == {"result": "completed"} diff --git a/tests/tasks/server/test_wire_models.py b/tests/tasks/server/test_wire_models.py index b36486e97..76419e0fe 100644 --- a/tests/tasks/server/test_wire_models.py +++ b/tests/tasks/server/test_wire_models.py @@ -22,6 +22,7 @@ from fastmcp_tasks.models import ( CancelTaskResult, CreateTaskResult, GetTaskResult, + TaskStatus, UpdateTaskResult, ) from jsonschema import Draft202012Validator @@ -72,10 +73,10 @@ def test_create_task_result_matches_schema(): ("cancelled", {}), ], ) -def test_get_task_result_matches_schema(status: str, payload: dict[str, Any]): +def test_get_task_result_matches_schema(status: TaskStatus, payload: dict[str, Any]): result = GetTaskResult( task_id="t1", - status=status, # type: ignore[arg-type] + status=status, created_at=_ISO, last_updated_at=_ISO, ttl_ms=900000, @@ -111,9 +112,7 @@ def test_null_ttl_is_permitted_by_schema(): ) dumped = result.model_dump(by_alias=True, mode="json", exclude_none=False) # Drop the other None optionals the runner would also drop, keeping ttlMs=null. - dumped = { - k: v for k, v in dumped.items() if v is not None or k == "ttlMs" - } + dumped = {k: v for k, v in dumped.items() if v is not None or k == "ttlMs"} _validate("CreateTaskResult", dumped) diff --git a/tests/tasks/task_helpers.py b/tests/tasks/task_helpers.py index 3f8563b12..405996ada 100644 --- a/tests/tasks/task_helpers.py +++ b/tests/tasks/task_helpers.py @@ -25,7 +25,7 @@ from __future__ import annotations import asyncio import contextlib from types import SimpleNamespace -from typing import Any +from typing import Any, cast from fastmcp_tasks.handlers import tasks_cancel, tasks_get, tasks_update from fastmcp_tasks.models import ( @@ -37,6 +37,7 @@ from fastmcp_tasks.models import ( from mcp.server.auth.middleware.auth_context import auth_context_var from mcp.server.auth.middleware.bearer_auth import AuthenticatedUser from mcp.server.context import ServerRequestContext +from mcp.server.session import ServerSession from mcp_types import CLIENT_CAPABILITIES_META_KEY from fastmcp.server.auth import AccessToken @@ -91,7 +92,7 @@ def _opted_in_request( "_meta": opt_in_meta(settings), } srctx = ServerRequestContext( - session=SimpleNamespace(), + session=cast(ServerSession, SimpleNamespace()), lifespan_context={}, protocol_version="2026-07-28", method="tools/call", @@ -202,9 +203,7 @@ async def run_task( timeout: float = 5.0, ) -> GetTaskResult: """Submit a task and wait for it to reach a terminal state.""" - created = await submit_task( - server, name, arguments, access_token=access_token - ) + created = await submit_task(server, name, arguments, access_token=access_token) return await wait_for_task( server, created.task_id, access_token=access_token, timeout=timeout )