Skip to content

Commit fa5c546

Browse files
Jacksunweicopybara-github
authored andcommitted
fix(tools): Add proper cleanup for AgentTool to prevent MCP session errors
Merge #3411 ## Summary Fixes AgentTool cleanup to prevent MCP session errors by calling `runner.close()` after sub-agent execution. ## Problem When using AgentTool with MCP tools, the runner cleanup happened during garbage collection in a different async task context, causing: ``` RuntimeError: Attempted to exit cancel scope in a different task than it was entered in ``` ## Solution - Call `await runner.close()` immediately after sub-agent execution in AgentTool - This ensures MCP sessions and other resources are cleaned up in the correct async task context - Updated test mock to include the close() method ## Demo Agents Added two comprehensive demo agents showing how to use AgentTool with MCP tools: ### mcp_in_agent_tool_remote (SSE mode) - Uses HTTP/SSE connection to remote MCP server - Zero-installation setup with `uvx` - Demonstrates server-side MCP deployment pattern ### mcp_in_agent_tool_stdio (stdio mode) - Uses subprocess connection with automatic server launch - Fully automatic setup with `uvx` subdirectory syntax - Demonstrates embedded MCP deployment pattern Both demos: - Use Gemini 2.5 Flash - Include example prompts for JSON Schema exploration - Have comprehensive READMEs with architecture diagrams - Follow ADK agent structure conventions ## Testing - ✅ All existing unit tests pass - ✅ Manual testing with both SSE and stdio modes - ✅ Verified cleanup happens in correct async context - ✅ No more cancel scope errors with MCP tools ## Related - Fixes #1112 - Related to #929 Co-authored-by: Wei Sun (Jack) <[email protected]> COPYBARA_INTEGRATE_REVIEW=#3411 from google:fix/agent-tool-mcp-cleanup 9ae753b PiperOrigin-RevId: 828651896
1 parent ee8106b commit fa5c546

File tree

8 files changed

+333
-0
lines changed

8 files changed

+333
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# AgentTool with MCP Demo (SSE Mode)
2+
3+
This demo shows how `AgentTool` works with MCP (Model Context Protocol) toolsets using **SSE mode**.
4+
5+
## SSE vs Stdio Mode
6+
7+
This demo uses **SSE (Server-Sent Events) mode** where the MCP server runs as a separate HTTP server:
8+
9+
- **Remote connection** - Connects to server via HTTP
10+
- **Separate process** - Server must be started manually
11+
- **Network communication** - Uses HTTP/SSE for messaging
12+
13+
For the **stdio (subprocess) version**, see [mcp_in_agent_tool_stdio](../mcp_in_agent_tool_stdio/).
14+
15+
## Setup
16+
17+
**Start the MCP simple-tool server in SSE mode** (in a separate terminal):
18+
19+
```bash
20+
# Run the server using uvx (no installation needed)
21+
# Port 3000 avoids conflict with adk web (which uses 8000)
22+
uvx --from 'git+https://github.com/modelcontextprotocol/python-sdk.git#subdirectory=examples/servers/simple-tool' \
23+
mcp-simple-tool --transport sse --port 3000
24+
```
25+
26+
The server should be accessible at `http://localhost:3000/sse`.
27+
28+
## Running the Demo
29+
30+
```bash
31+
adk web contributing/samples
32+
```
33+
34+
Then select **mcp_in_agent_tool_remote** from the list and interact with the agent.
35+
36+
## Try These Prompts
37+
38+
This demo uses **Gemini 2.5 Flash** as the model. Try these prompts:
39+
40+
1. **Check available tools:**
41+
42+
```
43+
What tools do you have access to?
44+
```
45+
46+
2. **Fetch and summarize JSON Schema specification:**
47+
48+
```
49+
Use the mcp_helper to fetch https://json-schema.org/specification and summarize the key features of JSON Schema
50+
```
51+
52+
## Architecture
53+
54+
```
55+
main_agent (root_agent)
56+
57+
└── AgentTool wrapping:
58+
59+
└── mcp_helper (sub_agent)
60+
61+
└── McpToolset (SSE connection)
62+
63+
└── http://localhost:3000/sse
64+
65+
└── MCP simple-tool server
66+
67+
└── Website Fetcher Tool
68+
```
69+
70+
## Related
71+
72+
- **Issue:** [#1112 - Using agent as tool outside of adk web doesn't exit cleanly](https://github.com/google/adk-python/issues/1112)
73+
- **Related Issue:** [#929 - LiteLLM giving error with OpenAI models and Grafana's MCP server](https://github.com/google/adk-python/issues/929)
74+
- **Stdio Version:** [mcp_in_agent_tool_stdio](../mcp_in_agent_tool_stdio/) - Uses local subprocess connection
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from google.adk.agents import Agent
16+
from google.adk.tools import AgentTool
17+
from google.adk.tools.mcp_tool import McpToolset
18+
from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams
19+
20+
# Create MCP toolset
21+
# This uses the simple-tool MCP server via SSE
22+
# You need to start the MCP server separately (see README.md)
23+
mcp_toolset = McpToolset(
24+
connection_params=SseConnectionParams(
25+
url="http://localhost:3000/sse",
26+
timeout=10.0,
27+
sse_read_timeout=300.0,
28+
)
29+
)
30+
31+
# Create sub-agent with MCP tools
32+
# This agent has direct access to MCP tools
33+
sub_agent = Agent(
34+
name="mcp_helper",
35+
model="gemini-2.5-flash",
36+
description=(
37+
"A helpful assistant with access to MCP tools for fetching websites."
38+
),
39+
instruction="""You are a helpful assistant with access to MCP tools.
40+
41+
When the user asks for help:
42+
1. Explain what tools you have available (website fetching)
43+
2. Use the appropriate tool if needed
44+
3. Provide clear and helpful responses
45+
46+
You have access to a website fetcher tool via MCP. Use it to fetch and return website content.""",
47+
tools=[mcp_toolset],
48+
)
49+
50+
# Wrap sub-agent as an AgentTool
51+
# This allows the main agent to delegate tasks to the sub-agent
52+
# The sub-agent has access to MCP tools for fetching websites
53+
mcp_agent_tool = AgentTool(agent=sub_agent)
54+
55+
# Create main agent
56+
# This agent can delegate to the sub-agent via AgentTool
57+
root_agent = Agent(
58+
name="main_agent",
59+
model="gemini-2.5-flash",
60+
description="Main agent that can delegate to a sub-agent with MCP tools.",
61+
instruction="""You are a helpful assistant. You have access to a sub-agent (mcp_helper)
62+
that has MCP tools for fetching websites.
63+
64+
When the user asks for help:
65+
- If they need to fetch a website, call the mcp_helper tool
66+
- Otherwise, respond directly
67+
68+
Always be helpful and explain what you're doing.""",
69+
tools=[mcp_agent_tool],
70+
)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# AgentTool with MCP Demo (Stdio Mode)
2+
3+
This demo shows how `AgentTool` works with MCP (Model Context Protocol) toolsets using **stdio mode**.
4+
5+
## Stdio vs SSE Mode
6+
7+
This demo uses **stdio mode** where the MCP server runs as a subprocess:
8+
9+
- **Simpler setup** - No need to start a separate server
10+
- **Auto-launched** - Server starts automatically when agent runs
11+
- **Local process** - Uses stdin/stdout for communication
12+
13+
For the **SSE (remote server) version**, see [mcp_in_agent_tool_remote](../mcp_in_agent_tool_remote/).
14+
15+
## Setup
16+
17+
**No installation required!** The MCP server will be launched automatically using `uvx` when you run the agent.
18+
19+
The demo uses `uvx` to fetch and run the MCP simple-tool server directly from the GitHub repository's subdirectory:
20+
21+
```bash
22+
uvx --from 'git+https://github.com/modelcontextprotocol/python-sdk.git#subdirectory=examples/servers/simple-tool' \
23+
mcp-simple-tool
24+
```
25+
26+
This happens automatically via the stdio connection when the agent starts.
27+
28+
## Running the Demo
29+
30+
```bash
31+
adk web contributing/samples
32+
```
33+
34+
Then select **mcp_in_agent_tool_stdio** from the list and interact with the agent.
35+
36+
## Try These Prompts
37+
38+
This demo uses **Gemini 2.5 Flash** as the model. Try these prompts:
39+
40+
1. **Check available tools:**
41+
42+
```
43+
What tools do you have access to?
44+
```
45+
46+
2. **Fetch and summarize JSON Schema specification:**
47+
48+
```
49+
Use the mcp_helper to fetch https://json-schema.org/specification and summarize the key features of JSON Schema
50+
```
51+
52+
## Architecture
53+
54+
```
55+
main_agent (root_agent)
56+
57+
└── AgentTool wrapping:
58+
59+
└── mcp_helper (sub_agent)
60+
61+
└── McpToolset (stdio connection)
62+
63+
└── MCP Server (subprocess via uvx)
64+
65+
└── uvx --from git+...#subdirectory=... mcp-simple-tool
66+
67+
└── Website Fetcher Tool
68+
```
69+
70+
## Related
71+
72+
- **Issue:** [#1112 - Using agent as tool outside of adk web doesn't exit cleanly](https://github.com/google/adk-python/issues/1112)
73+
- **Related Issue:** [#929 - LiteLLM giving error with OpenAI models and Grafana's MCP server](https://github.com/google/adk-python/issues/929)
74+
- **SSE Version:** [mcp_in_agent_tool_remote](../mcp_in_agent_tool_remote/) - Uses remote server connection
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from . import agent
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from google.adk.agents import Agent
16+
from google.adk.tools import AgentTool
17+
from google.adk.tools.mcp_tool import McpToolset
18+
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
19+
from mcp import StdioServerParameters
20+
21+
# Create MCP toolset
22+
# This uses the simple-tool MCP server via stdio
23+
# The server will be launched automatically using uvx from the subdirectory
24+
mcp_toolset = McpToolset(
25+
connection_params=StdioConnectionParams(
26+
server_params=StdioServerParameters(
27+
command="uvx",
28+
args=[
29+
"--from",
30+
"git+https://github.com/modelcontextprotocol/python-sdk.git#subdirectory=examples/servers/simple-tool",
31+
"mcp-simple-tool",
32+
],
33+
),
34+
timeout=10.0,
35+
)
36+
)
37+
38+
# Create sub-agent with MCP tools
39+
# This agent has direct access to MCP tools
40+
sub_agent = Agent(
41+
name="mcp_helper",
42+
model="gemini-2.5-flash",
43+
description=(
44+
"A helpful assistant with access to MCP tools for fetching websites."
45+
),
46+
instruction="""You are a helpful assistant with access to MCP tools.
47+
48+
When the user asks for help:
49+
1. Explain what tools you have available (website fetching)
50+
2. Use the appropriate tool if needed
51+
3. Provide clear and helpful responses
52+
53+
You have access to a website fetcher tool via MCP. Use it to fetch and return website content.""",
54+
tools=[mcp_toolset],
55+
)
56+
57+
# Wrap sub-agent as an AgentTool
58+
# This allows the main agent to delegate tasks to the sub-agent
59+
# The sub-agent has access to MCP tools for fetching websites
60+
mcp_agent_tool = AgentTool(agent=sub_agent)
61+
62+
# Create main agent
63+
# This agent can delegate to the sub-agent via AgentTool
64+
root_agent = Agent(
65+
name="main_agent",
66+
model="gemini-2.5-flash",
67+
description="Main agent that can delegate to a sub-agent with MCP tools.",
68+
instruction="""You are a helpful assistant. You have access to a sub-agent (mcp_helper)
69+
that has MCP tools for fetching websites.
70+
71+
When the user asks for help:
72+
- If they need to fetch a website, call the mcp_helper tool
73+
- Otherwise, respond directly
74+
75+
Always be helpful and explain what you're doing.""",
76+
tools=[mcp_agent_tool],
77+
)

src/google/adk/tools/agent_tool.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ async def run_async(
164164
if event.content:
165165
last_content = event.content
166166

167+
# Clean up runner resources (especially MCP sessions)
168+
# to avoid "Attempted to exit cancel scope in a different task" errors
169+
await runner.close()
170+
167171
if not last_content:
168172
return ''
169173
merged_text = '\n'.join(p.text for p in last_content.parts if p.text)

tests/unittests/tools/test_agent_tool.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ def run_async(
131131
)
132132
return _empty_async_generator()
133133

134+
async def close(self):
135+
"""Mock close method."""
136+
pass
137+
134138
monkeypatch.setattr('google.adk.runners.Runner', StubRunner)
135139

136140
tool_agent = Agent(

0 commit comments

Comments
 (0)