From fada4953d5c714659c5db465c260a3777e985991 Mon Sep 17 00:00:00 2001 From: Jose Alvarez Date: Tue, 27 Oct 2026 09:42:14 +0100 Subject: [PATCH] Python: Allow optional MCP task tools through plain calls --- .../0045-python-mcp-v2-client-lifecycle.md | 5 +++-- python/packages/core/AGENTS.md | 2 +- python/packages/core/agent_framework/_mcp.py | 6 +++--- python/packages/core/tests/core/test_mcp.py | 19 +++++++++---------- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/decisions/0045-python-mcp-v2-client-lifecycle.md b/docs/decisions/0045-python-mcp-v2-client-lifecycle.md index 29ad8ffd7df..57d389fc760 100644 --- a/docs/decisions/0045-python-mcp-v2-client-lifecycle.md +++ b/docs/decisions/0045-python-mcp-v2-client-lifecycle.md @@ -182,8 +182,9 @@ protocol-gated compatibility operations; they are not universal preflights. ### Tasks The supported MCP Python SDK 2.x runtime does not expose the current `io.modelcontextprotocol/tasks` extension. -Tools that may require Tasks are not presented as ordinary supported calls, and the removed long-running Tasks -sample remains deferred. An MRTR `InputRequiredResult` is not a Tasks `input_required` status. +Tools that require Tasks are not presented as ordinary supported calls. Tools with optional Tasks support continue +through ordinary `tools/call` without task augmentation, and the removed long-running Tasks sample remains deferred. +An MRTR `InputRequiredResult` is not a Tasks `input_required` status. ## Consequences diff --git a/python/packages/core/AGENTS.md b/python/packages/core/AGENTS.md index 895764eb93d..aaba1c8bc80 100644 --- a/python/packages/core/AGENTS.md +++ b/python/packages/core/AGENTS.md @@ -241,7 +241,7 @@ The vector store API is experimental under the shared `VECTOR_STORES` feature ID `cacheScope` hints. Legacy peers default to zero TTL, caller-supplied `ClientSession` connections stay uncached, and reconnect or effective header-identity changes replace a framework-owned Client's per-Client cache. Agent Framework does not expose shared-store or cache-mode configuration. -- **MCP Tasks extension** - MCP Python SDK 2.2 does not implement the 2026 `io.modelcontextprotocol/tasks` runtime. Tools that advertise `execution.taskSupport == "required"` are skipped with a warning; optional and task-free tools continue through ordinary `tools/call`. Restoration of the experimental Tasks API and removed sample is tracked in microsoft/agent-framework#8245. +- **MCP Tasks extension** - The current Agent Framework MCP client does not implement the `io.modelcontextprotocol/tasks` runtime. Tools that advertise `execution.taskSupport == "required"` are skipped with a warning; optional and task-free tools continue through ordinary `tools/call`. Tasks client support is tracked in microsoft/agent-framework#9215. ### File Access Harness (`_harness/_file_access.py`) diff --git a/python/packages/core/agent_framework/_mcp.py b/python/packages/core/agent_framework/_mcp.py index fc81ba455ba..37b4ef56cba 100644 --- a/python/packages/core/agent_framework/_mcp.py +++ b/python/packages/core/agent_framework/_mcp.py @@ -3112,10 +3112,10 @@ async def _load_tools_locked(self) -> None: for tool in tool_list.tools: task_support = getattr(getattr(tool, "execution", None), "task_support", None) - if task_support in ["required", "optional"]: + if task_support == "required": logger.warning( - "Skipping MCP tool %r because it (might) require the Tasks extension, " - "which MCP Python SDK 2.2 does not implement.", + "Skipping MCP tool %r because it requires the Tasks extension, " + "which this client does not support.", tool.name, ) continue diff --git a/python/packages/core/tests/core/test_mcp.py b/python/packages/core/tests/core/test_mcp.py index de02fda5ef5..3b814aa05d3 100644 --- a/python/packages/core/tests/core/test_mcp.py +++ b/python/packages/core/tests/core/test_mcp.py @@ -558,7 +558,7 @@ async def list_tools(params: types.PaginatedRequestParams | None = None) -> type assert tool._tool_param_names_by_name == ({} if empty_snapshot else {"keep": {"query"}}) -async def test_load_tools_skips_task_capable_tools(caplog: pytest.LogCaptureFixture) -> None: +async def test_load_tools_skips_only_task_required_tools(caplog: pytest.LogCaptureFixture) -> None: tool = MCPTool(name="docs") # type: ignore[abstract] # ty: ignore[call-non-callable] mock_session = AsyncMock() tool.session = mock_session @@ -593,19 +593,18 @@ async def test_load_tools_skips_task_capable_tools(caplog: pytest.LogCaptureFixt with caplog.at_level(logging.WARNING, logger=logger.name): await tool.load_tools() - assert [function.name for function in tool.functions] == ["forbidden", "ordinary"] - assert tool._tool_call_meta_by_name == {} - assert set(tool._tool_param_names_by_name) == {"forbidden", "ordinary"} - for tool_name in ("required", "optional"): - assert ( - f"Skipping MCP tool '{tool_name}' because it (might) require the Tasks extension, " - "which MCP Python SDK 2.2 does not implement." - ) in caplog.messages + assert [function.name for function in tool.functions] == ["optional", "forbidden", "ordinary"] + assert tool._tool_call_meta_by_name == {"optional": {"mode": "optional"}} + assert set(tool._tool_param_names_by_name) == {"optional", "forbidden", "ordinary"} + skip_messages = [message for message in caplog.messages if message.startswith("Skipping MCP tool")] + assert skip_messages == [ + "Skipping MCP tool 'required' because it requires the Tasks extension, which this client does not support." + ] for function in tool.functions: await function.invoke(arguments={}) - assert [call.args[0] for call in mock_session.call_tool.await_args_list] == ["forbidden", "ordinary"] + assert [call.args[0] for call in mock_session.call_tool.await_args_list] == ["optional", "forbidden", "ordinary"] async def test_tool_refresh_preserves_prompt_when_server_advertises_same_raw_name() -> None: