google / google/adk-python

Support the MCP Tasks extension (SEP-2663) in McpToolset: expose the mcp 2.x client extension seam

オープン
#6,826 コメント 3 件 リアクション 0 件 担当者 1 名 @sanketpatil06 が担当を希望しています GitHub で見る
mcp
主要言語
Python
スター
21.5k
フォーク
4k
平均マージ
1日 14時間
マージ済み PR(30日)
37

説明

** Please make sure you read the contribution guide and file the issues in the right place. **
[Contribution guide.](https://google.github.io/adk-docs/contributing-guide/)

## 🔴 Required Information

### Is your feature request related to a specific problem?

We run an MCP server that fronts physical hardware. A reboot, a firmware flash or an instrumented run takes minutes, not seconds — and the call is behind an HTTP gateway with its own write timeout, so "just hold the connection open" is not available to us.

Today `McpToolset` has exactly one shape for a tool call: `session.call_tool(...)` awaited to a `CallToolResult` (`mcp_tool.py:470-475`, verified at `main@1cd6f464`). The only relief valves are `sse_read_timeout` (default 300s, `mcp_session_manager.py:218`) and progress notifications (#3398), and neither gives a durable handle — a dropped connection loses the operation outright.

So servers like ours split one operation into `start_x` / `x_status` / `x_result` tools and let the model drive the polling loop. That is unreliable (it depends on prompt engineering), it burns context, and it is the exact anti-pattern the MCP spec set out to remove.

MCP has a standard answer now: the **Tasks extension**, identifier `io.modelcontextprotocol/tasks`, specified in [SEP-2663](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2663) (merged 2026-05-15) and maintained at [modelcontextprotocol/ext-tasks](https://github.com/modelcontextprotocol/ext-tasks). The server decides per request that the work is long-running and answers `tools/call` with `resultType: "task"` plus a `taskId`; the client polls `tasks/get` at the server's `pollIntervalMs` until a terminal status and reads the final `result`. One tool declaration, no extra tools in the model's context, and the handle survives a reconnect.

`McpToolset` has no path to this. There is no protocol-level `task` anywhere under `src/google/adk/tools/mcp_tool/`, and #3449 has no entry for it.

**To be clear about what this is not:** this is not the experimental `2025-11-25` tasks (SEP-1686). SEP-2663 removed those from the core spec, and in the mcp 1.x SDK both `ClientSession.experimental` and `mcp.client.experimental.task_handlers` are deprecated for removal in 2.0 (`mcp/client/session.py:25-28` in `mcp==1.29.0`). Nothing here asks for that design — in particular not the `Tool.execution.taskSupport` / `tools/list` warmup handshake, which SEP-2663 deliberately dropped in favour of a single capability-level opt-in.

### Describe the Solution You'd Like

Two steps. The first is small and is the one we actually need.

**1. Pass the mcp 2.x client extension seam through `McpToolset`.**

`mcp==2.0.0` gives `ClientSession.__init__` three new keyword arguments — `extensions`, `result_claims`, `notification_bindings` (`mcp/client/session.py:385-387`) — which are precisely the machinery an extension needs: `extensions` advertises the identifier in per-request client capabilities, and a `ResultClaim` keyed on `resultType` folds a non-core result shape into `tools/call` parsing.

ADK already threads `sampling_callback` / `sampling_capabilities` / `elicitation_callback` down exactly the chain those arguments would follow:

- `McpToolset.__init__` — `mcp_toolset.py:171-173`
- → `MCPSessionManager` — `mcp_toolset.py:256-262`, `mcp_session_manager.py:543-545`
- → `SessionContext` — `mcp_session_manager.py:1011-1019`
- → `ClientSession` — `session_context.py:347-369`

Adding the three extension kwargs to that same chain is the same shape of change as the elicitation plumbing in #6422, and it lets Tasks — or any other official extension — be implemented outside ADK without forking the toolset.

This depends on the mcp 2.x bump tracked in #6532 / #6537 and only makes sense on top of it.

**2. Ship Tasks natively in the toolset (follow-up).**

Opt-in, off by default. When it is on and the server advertises `io.modelcontextprotocol/tasks`, `McpToolset` advertises it too; on a `resultType: "task"` response it polls `tasks/get` at the server's `pollIntervalMs` until terminal, returns the `result` as the tool's `CallToolResult`, and sends `tasks/cancel` if the invocation is cancelled. The agent sees the same tool and the same result it sees today — only the wire changes.

For callers that want the handle rather than a blocking poll, `BaseTool.is_long_running` (`base_tool.py:59`) is the natural mapping, but that can wait; blocking-with-polling already solves the timeout and reconnect problems.

### Impact on your work

This is blocking for us. Every hardware operation either has to fit inside an HTTP write timeout or be re-expressed as a start/status/result triplet with the model as the scheduler. Step 1 alone unblocks us, because we can then carry the extension ourselves.

No timeline pressure beyond #6532 — we are not asking for anything before the mcp 2.x bump lands.

### Willingness to contribute

Yes. Happy to send the step-1 pass-through PR on top of #6537, and to prototype step 2 against a Tasks conformance server.

---

## 🟡 Recommended Information

### Describe Alternatives You've Considered

- **start / status / result tool triplets.** What we do today. Model-driven polling, so it depends on prompt engineering to happen at all, and it costs a model turn per poll. This is the motivating problem in SEP-1686 and SEP-2663.
- **Raising `sse_read_timeout` and blocking longer.** Intermediaries cap the request duration regardless, and a blocked call has no crash resilience: if the connection drops, the result is gone with no handle to resume from.
- **Progress notifications (#3398).** They give visibility into a call that is still open. They do not give a durable handle, and the call still has to stay open.
- **Subclassing `McpTool` to poll ourselves.** Two blockers: extension negotiation happens on the `ClientSession`, which the toolset owns and does not expose; and the toolset constructs the `McpTool` instances itself (`mcp_toolset.py:518-527`), so there is no supported injection point.

### Proposed API / Implementation

Step 1, as a pass-through:

```python
# google/adk/tools/mcp_tool/mcp_toolset.py
toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(url=...),
# forwarded verbatim to mcp.ClientSession, like sampling_callback today
extensions={"io.modelcontextprotocol/tasks": {}},
result_claims={"io.modelcontextprotocol/tasks": [tasks_claim]},
)
```

Step 2, as an opt-in on the same object:

```python
toolset = McpToolset(
connection_params=...,
# advertise the extension, and resolve `resultType: "task"` by polling
# tasks/get until terminal before returning the CallToolResult
enable_tasks=True,
)
```

### Additional Context

- Extension identifier and negotiation:
- SEP-2663 (merged 2026-05-15):
- Extension spec repository: (self-described as experimental; the negotiation mechanism it relies on is stable in `2026-07-28`)
- The Tasks extension is not yet listed in the [extension client matrix](https://modelcontextprotocol.io/extensions/client-matrix), so as far as we can tell no major host ships it yet. Step 1 is what lets ADK users move without waiting on that.

コントリビューションガイド

コントリビューションガイドを開く

評価

この issue はまだ評価されていません。

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。