Skip to content

feat(mcp): add auto_initialize parameter to StreamableHTTPConnectionParams - #2259

Closed
abprime wants to merge 3 commits into
google:mainfrom
abprime:main
Closed

abprime wants to merge 3 commits into
google:mainfrom
abprime:main

Conversation

@abprime

@abprime abprime commented Jul 30, 2025

Copy link
Copy Markdown

Fix MCP Session Persistence Across StreamableHTTP Sessions

Summary

This PR fixes issue #2059 by adding an auto_initialize parameter to StreamableHTTPConnectionParams that controls MCP session initialization and enables session persistence across tool invocations.

Problem

When using MCPToolset with StreamableHTTPConnectionParams, the ADK was not persisting the Mcp-Session-Id header returned by the MCP server on the first Initialize handshake. This caused every new tool invocation to re-trigger the authentication flow and return a fresh login URL instead of reusing the existing session.

Solution

The fix introduces an auto_initialize parameter to StreamableHTTPConnectionParams that:

  • Default value: True (maintains backward compatibility)
  • When True: Automatically initializes the session and re-initializes across tool invocations, if there is a disconnection (adk web guaranteed to happen because of agent reloading)
  • When False: Skips automatic initialization, allowing manual control over session lifecycle. Pass custom mcp-session-id (initialised outside the agent context)

This approach is similar to the LangGraph MCP implementation and follows the MCP Specification for Session Management.

Changes Made

1. Enhanced StreamableHTTPConnectionParams

  • Added auto_initialize: bool = True parameter to control session initialization
  • Updated docstring to document the new parameter

2. Updated Session Initialization Logic

  • Modified MCPSessionManager.create_session() to check the auto_initialize parameter
  • Only calls session.initialize() when auto_initialize=True for StreamableHTTP connections
  • Maintains existing behavior for other connection types

Usage Example

from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StreamableHTTPConnectionParams

# Enable session persistence (default behavior)
toolset = MCPToolset(
    connection_params=StreamableHTTPConnectionParams(
        url="http://mcp.fi.money:8080/mcp/stream",
        auto_initialize=True,  # Session will be auto-initialized every tool invocation
    ),
)

# Disable auto-initialization for manual control
toolset = MCPToolset(
    connection_params=StreamableHTTPConnectionParams(
        url="http://mcp.fi.money:8080/mcp/stream",
        headers={
          "mcp-session-id": "<custom-session-id>",
          },
        auto_initialize=False,  # Manual initialization required but session is persistent
    ),
)

Testing

  • ✅ All existing tests pass
  • ✅ Integration tests for session creation logic

Backward Compatibility

This change is fully backward compatible:

  • auto_initialize=True is the default value
  • Existing code continues to work without modification
  • No breaking changes to the public API

Related Links

Checklist

  • Code follows the project's style guidelines
  • Self-review of code completed
  • Code is documented with appropriate docstrings
  • Tests are added and pass
  • Changelog is updated
  • Demo sample is provided
  • Backward compatibility is maintained

@abprime

abprime commented Aug 1, 2025

Copy link
Copy Markdown
Author

@hangfei @seanzhou1023
please review and suggest changes if any.

)
await session.initialize()

initialize = True

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does skipping initialize preserve the header ?
when codes reach here, user should already been authenticated, you won't be able to skip the auth by adding logic here if there were additional auth required.

client = self._create_client(merged_headers)

above should contain the auth token in merged_headers

@seanzhou1023

Copy link
Copy Markdown
Collaborator

@abprime thank you so much for the fix :) Could you explain or add comments how your fix preserve the Mcp-Session-Id header ? you mentioned that

This caused every new tool invocation to re-trigger the authentication flow and return a fresh login URL instead of reusing the existing session.

However the codes you modified is already after the authentication flow got triggered, how does that prevent triggering the authentication flow ?

BTW the authentication flow is triggered in BaseAuthenticatedTool ( which MCPTool inherits from ) , by the time below codes are invoked :

    # Get the session from the session manager
    session = await self._mcp_session_manager.create_session(headers=headers)

the credentials are already there and access tokens are put into the headers , which means the auth flow is already done.

@yoshinoriishii

Copy link
Copy Markdown

Hi @seanzhou1023 — I’m trying to confirm whether the team is aware of (and planning to address) a session-continuity issue I’m observing with ADK + Streamable HTTP.

Summary

  • With ADK’s McpSessionManager and streamablehttp_client, the transport/stream is rebuilt between user turns.
  • When that happens, Mcp-Session-Id is not carried over to the new transport, so the MCP server treats it as a new logical session.
  • Within a single user turn (multiple tool calls) things look fine; across turns, server-side state tied to the session is lost.

Observed behavior

  • On EOF in ClientSession’s receive loop, ADK cleans up and calls create_session() again.
  • The new client is created without reusing a previously issued Mcp-Session-Id (there doesn’t appear to be a place where ADK stores and re-injects it on subsequent requests).
  • Result: the server starts a brand-new session on the next user message.

Expected (per Streamable HTTP semantics)

  • The client should keep the logical session stable across transport restarts by including the same Mcp-Session-Id header on every subsequent request, and only re-initialize when the server returns “unknown/expired session” (e.g., 404).
  • In other words: recreate the transport, not the session.

Why this matters

  • Many MCP servers/tools cache per-session state (dataset IDs, progress tokens, etc.). Losing the session between user messages is functionally breaking in those scenarios.

What would help

  • Clarification on ADK’s intended behavior here.

  • If this is acknowledged as a gap, guidance on the preferred approach:

    • Maintain a session map keyed by logical user/auth headers (excluding Mcp-Session-Id),
    • Save the Mcp-Session-Id returned on initialize,
    • Re-attach it on every subsequent transport/RPC,
    • Fall back to re-initialize only when the server rejects the ID.

If I’ve misunderstood the intended model or if there’s an existing mechanism to access and persist Mcp-Session-Id that I should use, please point me to it. Otherwise I’m happy to help test a fix. Thanks!

@wukath

wukath commented Nov 14, 2025

Copy link
Copy Markdown
Collaborator

closing since @abprime hasn't responded to sean's comment since august -- feel free to reopen if you have a fix!

@flybae thank you for reporting this issue as well, pls track #2979 for updates

@wukath wukath closed this Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Persist Mcp-Session-Id Across Streamable HTTP Sessions in MCPToolset

4 participants