Skip to content

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

Description

@JesusCaseroDiazCano

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_incomingawait 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():

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions