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
+
1
16
import asyncio
2
- import contextlib
3
17
import os
4
18
import time
5
19
9
23
from google .adk .agents .run_config import RunConfig
10
24
from google .adk .runners import Runner
11
25
from 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
+ )
13
31
from google .genai import types
14
32
15
33
from agents_mcp_usage .multi_mcp .mermaid_diagrams import invalid_mermaid_diagram_easy
24
42
logfire .instrument_mcp ()
25
43
26
44
27
- async def get_tools_async () -> tuple [list , contextlib . AsyncExitStack ]:
45
+ async def get_tools_async () -> tuple [list , list ]:
28
46
"""Initializes connections to MCP servers and returns their tools.
29
47
30
48
This function connects to the local example server and the mermaid
31
49
validator server, and returns the combined tools and a combined exit
32
50
stack for cleanup.
33
51
34
52
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 .
36
54
"""
37
55
print ("Connecting to MCP servers..." )
38
56
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 = []
41
59
42
60
# Set up MCP server connections
43
61
local_server = StdioServerParameters (
@@ -57,90 +75,97 @@ async def get_tools_async() -> tuple[list, contextlib.AsyncExitStack]:
57
75
],
58
76
)
59
77
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 )
65
83
print (f"Connected to local python MCP server. Found { len (local_tools )} tools." )
66
84
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 )
72
90
print (f"Connected to npx mermaid MCP server. Found { len (mermaid_tools )} tools." )
73
91
74
92
# Combine tools from both servers
75
93
all_tools = local_tools + mermaid_tools
76
94
print (f"Total tools available: { len (all_tools )} " )
77
95
78
- return all_tools , exit_stack
96
+ return all_tools , toolsets
79
97
80
98
81
99
async 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 .
83
101
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
85
103
the provided query and request limit. It also handles the cleanup of
86
104
the MCP server connections and Logfire.
87
105
88
106
Args:
89
107
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 .
91
109
"""
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." )
144
169
145
170
146
171
if __name__ == "__main__" :
0 commit comments