-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: Dynamic authentication handling in MCPToolset from ADK Readonly… #2208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
…Context.state
This PR implements dynamic authentication handling in MCPToolset by passing
authentication information (such as user-specific tokens) directly from the
ADK session.state. With this approach, user tokens can be injected and sent
from the ADK Context state to MCP, supporting user-specific authentication flows.
Key Improvements:
1. Dynamic Token Management:
- MCPToolset now reads authentication information from the ADK ReadonlyContext.state
at runtime, enabling dynamic, user-specific token management.
- STDIO Transport: Authentication information is passed to MCP via environment variables.
- Other Transports (SSE and HTTP Stream): MCPToolset now receives authentication
details from the ADK ReadonlyContext.state, making them available for further
transmission, enabling a unified authentication mechanism across all transports.
2. Improved Unit Tests: Enhancements for better readability and maintainability.
3. New Sample: Demonstrates passing user tokens to MCP via environment variables,
showcasing the dynamic flow from session.state.
4. Flexible & Secure Integration: Allows for more flexible and secure integration
scenarios, supporting distinct authentication credentials per session or user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull Request Overview
This PR implements dynamic authentication handling in MCPToolset by passing authentication information directly from the ADK ReadonlyContext.state at runtime, enabling user-specific authentication flows and environment variable injection for MCP servers.
Key changes:
- Added dynamic token management through environment variable injection for STDIO transport and context-based authentication extraction for all transports
- Enhanced unit test coverage with comprehensive test suites for authentication and environment variable functionality
- Updated MCP dependency version to support new features
Reviewed Changes
Copilot reviewed 11 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
src/google/adk/tools/mcp_tool/mcp_toolset.py |
Added auth extraction from context, environment variable injection, and callback support for dynamic authentication |
src/google/adk/tools/mcp_tool/mcp_session_manager.py |
Added connection parameter update functionality to support dynamic environment variable injection |
tests/unittests/tools/mcp_tool/test_mcp_toolset_env.py |
Comprehensive unit tests for environment variable extraction and injection functionality |
tests/unittests/tools/mcp_tool/test_mcp_toolset_auth.py |
Extensive unit tests for authentication extraction with error handling and validation |
tests/unittests/tools/mcp_tool/test_mcp_tool_auth.py |
Unit tests for MCPTool authentication functionality |
tests/integration/utils/test_runner.py |
Updated TestRunner with async support and session management improvements |
tests/integration/test_mcp_env_integration.py |
Integration tests demonstrating end-to-end environment variable and authentication flows |
pyproject.toml |
Updated MCP dependency version from 1.8.0 to 1.9.4 |
contributing/samples/mcp_stdio_user_auth_passing_sample/ |
Sample implementation showing user token passing through environment variables |
Comments suppressed due to low confidence (2)
tests/integration/test_mcp_env_integration.py:63
- Accessing private attributes like
_context_to_env_mapper_callbackin tests creates tight coupling and makes the code brittle to internal changes. Consider adding a public method or property to check if the callback is configured.
""",
pyproject.toml:41
- MCP version 1.9.4 may not exist. Please verify that this version is available before updating the dependency requirement.
"mcp>=1.9.4;python_version>='3.10'", # For MCP Toolset
| # Import after mocking to avoid MCP dependency issues | ||
| from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset | ||
|
|
||
|
|
Copilot
AI
Jul 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import should be placed at the top of the file with other imports, not after mocking fixtures. Consider restructuring to avoid circular import issues while maintaining clean import organization.
| # Import after mocking to avoid MCP dependency issues | |
| from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset |
| # Import after mocking to avoid MCP dependency issues | ||
| from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset | ||
|
|
||
|
|
Copilot
AI
Jul 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import should be placed at the top of the file with other imports, not after mocking fixtures. Consider restructuring to avoid circular import issues while maintaining clean import organization.
| # Import after mocking to avoid MCP dependency issues | |
| from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset |
| # Import after mocking to avoid MCP dependency issues | ||
| from google.adk.tools.mcp_tool.mcp_tool import MCPTool | ||
|
|
||
|
|
Copilot
AI
Jul 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Import should be placed at the top of the file with other imports, not after mocking fixtures. Consider restructuring to avoid circular import issues while maintaining clean import organization.
| # Import after mocking to avoid MCP dependency issues | |
| from google.adk.tools.mcp_tool.mcp_tool import MCPTool |
| self.current_session_id = None | ||
| self._session_initialized = False | ||
|
|
||
| def new_session(self, session_id: Optional[str] = None) -> None: | ||
| self.current_session_id = self.session_service.create_session( | ||
| async def _ensure_session(self) -> str: | ||
| """Ensure a session is created and return the session ID.""" | ||
| if not self._session_initialized: | ||
| session = await self.session_service.create_session( | ||
| app_name=self.app_name, user_id=self.user_id | ||
| ) | ||
| self.current_session_id = session.id | ||
| self._session_initialized = True | ||
| return self.current_session_id | ||
|
|
||
| async def new_session_async(self, session_id: Optional[str] = None) -> None: | ||
| session = await self.session_service.create_session( | ||
| app_name=self.app_name, user_id=self.user_id, session_id=session_id | ||
| ).id | ||
| ) | ||
| self.current_session_id = session.id | ||
| self._session_initialized = True |
Copilot
AI
Jul 28, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[nitpick] The initialization of current_session_id to None followed by session creation logic adds complexity. Consider initializing the session in __init__ or using a property to encapsulate the session creation logic more cleanly.
|
Hi @hangfei, I've made the requested changes. Apologies for creating a new branch and pull request—I ran into some issues with the commit history on the previous one. This pull request contains the latest code along with all the fixes. |
|
Hi @pandasanjay , Thank you for your contribution through this pull request! I appreciate your patience with the delay. This PR has merge conflicts that require changes from your end. Could you please rebase your branch with the latest main branch to address these? Once this is complete, please let us know so we can proceed with the review. |
Resolved merge conflicts in mcp_toolset.py and pyproject.toml. Merged main branch changes (McpToolset rename, retry_on_errors decorator, header_provider, from_config method) with dynamic MCP auth feature.
The method is already defined in BaseToolset and handles all cases.
|
Hi @ryanaiagent, I’ve updated the code and would like you to review it. I believe we now have a Additionally, this PR also includes another fix: passing an environment variable from the context state to the STDIO MCP. |
|
/gemini review |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code Review
This PR introduces a powerful feature for dynamic authentication by allowing auth details and environment variables to be passed from the ADK session state to MCP. The implementation in MCPToolset is well-structured, with new callbacks for extracting auth and environment data. The addition of comprehensive unit tests for the new auth logic is also a great improvement.
However, there are several critical issues that need addressing. The new integration tests and the new sample code are broken due to the refactoring that moved logic from MCPSessionManager to MCPToolset. They are calling non-existent methods and will fail. There's also a bug in MCPSessionManager.update_connection_params where it fails to clear the session cache. Additionally, there's a performance concern in MCPToolset.get_tools where a new session manager (and thus a new process) is created on every call with dynamic env vars. Finally, the new sample's README contains a hardcoded path.
I've provided detailed comments and suggestions for each of these points. Once these are addressed, this will be a solid contribution.
| mcp_session_manager = mcp_toolset._mcp_session_manager | ||
|
|
||
| # Extract environment variables using the callback (without connecting to MCP) | ||
| if mcp_session_manager._context_to_env_mapper_callback: | ||
| print("✓ Context-to-env mapper callback is configured") | ||
|
|
||
| # Simulate what happens during MCP session creation | ||
| env_vars = mcp_session_manager._extract_env_from_context(readonly_context) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The sample code attempts to access _mcp_session_manager._context_to_env_mapper_callback and call _mcp_session_manager._extract_env_from_context. These attributes and methods have been removed from MCPSessionManager and the logic moved to MCPToolset in this PR. As a result, the sample is broken and will not demonstrate the intended feature.
The sample should be updated to use the public APIs and reflect the new implementation. A better sample would perform an actual tool call to show the end-to-end flow, rather than relying on private members which can change.
| async def test_session_manager_direct_env_injection(self, llm_backend): | ||
| """Test MCPSessionManager environment variable injection directly.""" | ||
| connection_params = StdioServerParameters( | ||
| command='npx', | ||
| args=['-y', '@modelcontextprotocol/server-filesystem'], | ||
| env={'EXISTING_VAR': 'existing_value'}, | ||
| ) | ||
|
|
||
| def test_env_callback(state_dict: Dict[str, Any]) -> Dict[str, str]: | ||
| return {'NEW_VAR': 'new_value', 'API_KEY': 'secret123'} | ||
|
|
||
| session_manager = MCPSessionManager( | ||
| connection_params=connection_params, | ||
| get_env_from_context_fn=test_env_callback, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test, and others in this file, appear to be broken after the refactoring. MCPSessionManager is initialized with get_env_from_context_fn, which is no longer a valid argument. Furthermore, the test attempts to call _extract_env_from_context and _inject_env_vars on the session_manager instance, but these methods do not exist on MCPSessionManager anymore; this logic has been moved to MCPToolset.
The entire test suite in this file needs to be updated to reflect the new architecture where MCPToolset is responsible for environment variable handling.
| # Clear existing sessions since connection params changed | ||
| # Sessions will be recreated on next request | ||
| # Note: We don't close sessions here to avoid blocking, | ||
| # they will be cleaned up when detected as disconnected |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The docstring for update_connection_params states that it "invalidates existing sessions". However, the implementation only updates self._connection_params. The session cache self._sessions is not actually cleared, which means existing sessions with outdated connection parameters might be reused. This is a bug.
To fix this, you should clear the session cache.
| # Clear existing sessions since connection params changed | |
| # Sessions will be recreated on next request | |
| # Note: We don't close sessions here to avoid blocking, | |
| # they will be cleaned up when detected as disconnected | |
| # Clear existing sessions since connection params changed | |
| self._sessions.clear() | |
| # Sessions will be recreated on next request | |
| # Note: We don't close sessions here to avoid blocking, | |
| # they will be cleaned up when detected as disconnected |
| cd /home/sanjay-dev/Workspace/adk-python | ||
| python -m contributing.samples.stdio_mcp_user_auth_passing_sample.main |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command to run the sample includes a hardcoded, user-specific path (/home/sanjay-dev/Workspace/adk-python). This will not work for other users and will cause confusion. The instructions should be generic.
| cd /home/sanjay-dev/Workspace/adk-python | |
| python -m contributing.samples.stdio_mcp_user_auth_passing_sample.main | |
| cd <path_to_adk-python_repo_root> | |
| python -m contributing.samples.stdio_mcp_user_auth_passing_sample.main |
| instruction=f""" | ||
| You are an agent that calls an internal MCP server which requires a user token for internal API calls. | ||
| The user token is available in your session state and must be passed to the MCP process as an environment variable. | ||
| Test directory: {temp_dir} | ||
| """, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The agent's instruction string includes Test directory: {temp_dir}, but the StdioServerParameters for the filesystem server is configured with _allowed_path (on line 51). The temp_dir variable is created but not actually used by the MCP tool. This is misleading for a sample and could confuse users about how to configure the tool's working directory.
To make the sample clearer, I recommend using temp_dir as the path for StdioServerParameters to align the instruction with the implementation.
| # If we have env vars to inject for STDIO, create a temporary session manager | ||
| if env_vars and isinstance(self._connection_params, StdioConnectionParams): | ||
| updated_connection_params = self._create_updated_connection_params( | ||
| env_vars | ||
| ) | ||
| session_manager = MCPSessionManager( | ||
| connection_params=updated_connection_params, | ||
| errlog=self._errlog, | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_tools method creates a new, temporary MCPSessionManager instance every time it's called with dynamic environment variables for an Stdio connection. This is inefficient because it will likely spawn a new MCP process on each call, instead of reusing an existing one. This could lead to significant performance overhead, especially if get_tools is called frequently.
Consider refactoring to avoid creating a new MCPSessionManager. One approach could be to enhance MCPSessionManager to manage sessions with different environment variables within the same manager instance, for example by including a hash of the environment variables in the session_key for stdio connections.
…Context.state
This PR implements dynamic authentication handling in MCPToolset by passing authentication information (such as user-specific tokens) directly from the ADK session.state. With this approach, user tokens can be injected and sent from the ADK Context state to MCP, supporting user-specific authentication flows.
Key Improvements:
Dynamic Token Management:
Improved Unit Tests: Enhancements for better readability and maintainability.
New Sample: Demonstrates passing user tokens to MCP via environment variables, showcasing the dynamic flow from session.state.
Flexible & Secure Integration: Allows for more flexible and secure integration scenarios, supporting distinct authentication credentials per session or user.