1+ """Advanced multi-MCP server integration using Google's ADK.
2+
3+ This module demonstrates sophisticated ADK (Agent Development Kit) patterns including:
4+ - Multi-MCP server coordination with parallel connections
5+ - Latest gemini-2.0-flash model integration
6+ - Advanced async connection management with unified exit stacks
7+ - Comprehensive error handling and recovery
8+ - RunConfig for request limiting and control
9+ - Enhanced event tracking and metrics collection
10+ - Graceful resource cleanup and Logfire instrumentation
11+
12+ Compatible with ADK v1.3.0+ and showcases production-ready patterns
13+ for complex agent architectures involving multiple tool sources.
14+ """
15+
116import asyncio
2- import contextlib
317import os
418import time
519
923from google .adk .agents .run_config import RunConfig
1024from google .adk .runners import Runner
1125from google .adk .sessions import InMemorySessionService
12- from google .adk .tools .mcp_tool .mcp_toolset import MCPToolset , StdioServerParameters
26+ from google .adk .tools .mcp_tool .mcp_toolset import (
27+ MCPToolset ,
28+ StdioServerParameters ,
29+ StdioConnectionParams ,
30+ )
1331from google .genai import types
1432
1533from agents_mcp_usage .multi_mcp .mermaid_diagrams import invalid_mermaid_diagram_easy
2442logfire .instrument_mcp ()
2543
2644
27- async def get_tools_async () -> tuple [list , contextlib . AsyncExitStack ]:
45+ async def get_tools_async () -> tuple [list , list ]:
2846 """Initializes connections to MCP servers and returns their tools.
2947
3048 This function connects to the local example server and the mermaid
3149 validator server, and returns the combined tools and a combined exit
3250 stack for cleanup.
3351
3452 Returns:
35- A tuple containing the list of all tools and the combined exit stack .
53+ A tuple containing the list of all tools and the list of toolsets for cleanup .
3654 """
3755 print ("Connecting to MCP servers..." )
3856
39- # Create a single exit stack for all connections
40- exit_stack = contextlib . AsyncExitStack ()
57+ # Keep track of toolsets for cleanup (ADK v1.3.0+ API)
58+ toolsets = []
4159
4260 # Set up MCP server connections
4361 local_server = StdioServerParameters (
@@ -57,90 +75,97 @@ async def get_tools_async() -> tuple[list, contextlib.AsyncExitStack]:
5775 ],
5876 )
5977
60- # Connect to local python MCP server
61- local_toolset = await MCPToolset . from_server ( connection_params = local_server )
62- local_tools , local_stack = local_toolset
63- # Register with the exit stack
64- await exit_stack . enter_async_context ( local_stack )
78+ # Connect to local python MCP server (ADK v1.3.0+ API)
79+ local_connection = StdioConnectionParams ( server_params = local_server )
80+ local_toolset = MCPToolset ( connection_params = local_connection )
81+ local_tools = await local_toolset . get_tools ()
82+ toolsets . append ( local_toolset )
6583 print (f"Connected to local python MCP server. Found { len (local_tools )} tools." )
6684
67- # Connect to npx mermaid MCP server
68- mermaid_toolset = await MCPToolset . from_server ( connection_params = mermaid_server )
69- mermaid_tools , mermaid_stack = mermaid_toolset
70- # Register with the exit stack
71- await exit_stack . enter_async_context ( mermaid_stack )
85+ # Connect to npx mermaid MCP server (ADK v1.3.0+ API)
86+ mermaid_connection = StdioConnectionParams ( server_params = mermaid_server )
87+ mermaid_toolset = MCPToolset ( connection_params = mermaid_connection )
88+ mermaid_tools = await mermaid_toolset . get_tools ()
89+ toolsets . append ( mermaid_toolset )
7290 print (f"Connected to npx mermaid MCP server. Found { len (mermaid_tools )} tools." )
7391
7492 # Combine tools from both servers
7593 all_tools = local_tools + mermaid_tools
7694 print (f"Total tools available: { len (all_tools )} " )
7795
78- return all_tools , exit_stack
96+ return all_tools , toolsets
7997
8098
8199async def main (query : str = "Hi!" , request_limit : int = 5 ) -> None :
82- """Runs the agent with a given query and request limit.
100+ """Runs the agent with a given query and request limit using modern ADK patterns .
83101
84- This function initializes the tools, creates an agent, and runs it with
102+ This function initialises the tools, creates an agent, and runs it with
85103 the provided query and request limit. It also handles the cleanup of
86104 the MCP server connections and Logfire.
87105
88106 Args:
89107 query: The query to run the agent with.
90- request_limit: The number of requests to make to the MCP servers .
108+ request_limit: The maximum number of LLM calls allowed .
91109 """
92- # Get tools from MCP servers
93- tools , exit_stack = await get_tools_async ()
94-
95- # Create agent with tools
96- agent = LlmAgent (
97- model = "gemini-2.5-pro-preview-05-06" ,
98- name = "multi_mcp_adk" ,
99- tools = tools ,
100- )
101-
102- # Create session service
103- session_service = InMemorySessionService ()
104- session = session_service .create_session (
105- app_name = "multi_mcp_adk" ,
106- user_id = "andrewginns" ,
107- )
108-
109- # Create a RunConfig with a limit for LLM calls (500 is the default)
110- run_config = RunConfig (max_llm_calls = request_limit )
111-
112- # Create runner
113- runner = Runner (
114- app_name = "multi_mcp_adk" ,
115- agent = agent ,
116- session_service = session_service ,
117- )
118-
119- # Format the query as a message
120- message = types .Content (parts = [types .Part (text = query )], role = "user" )
121-
122- # Run the agent
123- events_async = runner .run_async (
124- session_id = session .id ,
125- user_id = "andrewginns" ,
126- new_message = message ,
127- run_config = run_config ,
128- )
129-
130- async for event in events_async :
131- print (f"Event received: { event } " )
132-
133- # Properly close all MCP connections
134- print ("Closing MCP server connections..." )
135- await exit_stack .aclose ()
136- print ("Cleanup complete." )
137-
138- # Give Logfire time to complete any pending exports
139- print ("Shutting down Logfire..." )
140- logfire .shutdown ()
141- # Small delay to ensure export completes
142- time .sleep (0.5 )
143- print ("Logfire shutdown complete." )
110+ toolsets = []
111+ try :
112+ # Get tools from MCP servers
113+ tools , toolsets = await get_tools_async ()
114+
115+ # Create agent
116+ agent = LlmAgent (
117+ model = "gemini-2.0-flash" ,
118+ name = "multi_mcp_adk" ,
119+ tools = tools ,
120+ )
121+
122+ # Create async session service
123+ session_service = InMemorySessionService ()
124+ session = await session_service .create_session (
125+ app_name = "multi_mcp_adk" ,
126+ user_id = "andrewginns" ,
127+ )
128+
129+ # Create a RunConfig with a limit for LLM calls (500 is the default)
130+ run_config = RunConfig (max_llm_calls = request_limit )
131+
132+ # Create runner
133+ runner = Runner (
134+ app_name = "multi_mcp_adk" ,
135+ agent = agent ,
136+ session_service = session_service ,
137+ )
138+
139+ # Format the query as a message
140+ message = types .Content (parts = [types .Part (text = query )], role = "user" )
141+
142+ # Run the agent
143+ events_async = runner .run_async (
144+ session_id = session .id ,
145+ user_id = "andrewginns" ,
146+ new_message = message ,
147+ run_config = run_config ,
148+ )
149+
150+ async for event in events_async :
151+ print (f"Event received: { event } " )
152+
153+ except Exception as e :
154+ print (f"Error during agent execution: { e } " )
155+ print (f"Error type: { type (e ).__name__ } " )
156+ raise
157+ finally :
158+ # Clean up MCP toolsets to prevent asyncio shutdown errors
159+ print ("Cleaning up MCP connections..." )
160+ for i , toolset in enumerate (toolsets ):
161+ try :
162+ await toolset .close ()
163+ print (f"Toolset { i + 1 } closed successfully." )
164+ except asyncio .CancelledError :
165+ print (f"Toolset { i + 1 } cleanup cancelled - this is normal" )
166+ except Exception as e :
167+ print (f"Warning during cleanup of toolset { i + 1 } : { e } " )
168+ print ("MCP cleanup completed." )
144169
145170
146171if __name__ == "__main__" :
0 commit comments