google / google/adk-python

Expose a public message_handler seam for MCP ClientSession (consume server notifications e.g. tools/list_changed)

Abierto
#6,385 3 comentarios 0 reacciones 1 asignado Reclamado por @sanketpatil06 Ver en GitHub
mcp needs review spam
Lenguaje dominante
Python
Estrellas
21.5k
Forks
4k
Merge medio
1 d 14 h
PR fusionados (30 d)
37

Descripción

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

There is no supported way to have `McpToolset` / `MCPSessionManager` react to **server-initiated MCP notifications** — in particular `notifications/tools/list_changed`.

The underlying `mcp` SDK `ClientSession` accepts a `message_handler` constructor parameter, and invokes it for every incoming message (`_handle_incoming` → `await self._message_handler(req)`). But in ADK the `ClientSession` is constructed internally inside `SessionContext._run` (`google/adk/tools/mcp_tool/session_context.py`) with only `sampling_callback` / `sampling_capabilities` forwarded — no `message_handler`. So the SDK falls back to `_default_message_handler`, which silently discards all server notifications, including `ToolListChangedNotification`.

Neither `MCPSessionManager.__init__`, `SessionContext.__init__`, nor `McpToolset.__init__` exposes a `message_handler`, so there is no public hook.

Observed on `google-adk==2.3.0` (with `mcp==1.27.2`).

### Describe the solution you'd like

Forward an optional `message_handler: MessageHandlerFnT | None` through the same path as `sampling_callback`:

- `McpToolset(..., message_handler=...)` →
- `MCPSessionManager(..., message_handler=...)` →
- `SessionContext(..., message_handler=...)` →
- passed into the `ClientSession(...)` constructor.

This would let toolsets consume `tools/list_changed` (e.g. to invalidate a cached tool list) and other server notifications through a supported API.

### Describe alternatives you've considered

Today the only working hook is to assign the **private** attribute after `create_session()`:

```python
session = await super().create_session(headers=headers)
target = getattr(session, "_session", session)
if hasattr(target, "_message_handler"):
target._message_handler = my_handler
```

This works (the SDK reads `_message_handler` dynamically per message, so post-construction assignment takes effect on the running receive loop), but it depends on a private attribute that any ADK/SDK change could rename or remove. A public parameter would remove the need for this workaround.

### Additional context

Use case: a gateway that implements the MCP `tools/list_changed` capability emits the notification on the standalone GET SSE stream; the client wants to invalidate its cached `tools/list` result on receipt. The GET stream itself opens fine once the server returns an `mcp-session-id`; the only missing piece on the ADK side is delivery of the notification to a caller-supplied handler.

Guía de contribución

Abrir la guía de contribución

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.