21 KiB
Model Context Protocol Tools
This guide walks you through two ways of integrating Model Context Protocol (MCP) with ADK.
What is Model Context Protocol (MCP)?
The Model Context Protocol (MCP) is an open standard designed to standardize how Large Language Models (LLMs) like Gemini and Claude communicate with external applications, data sources, and tools. Think of it as a universal connection mechanism that simplifies how LLMs obtain context, execute actions, and interact with various systems.
MCP follows a client-server architecture, defining how data (resources), interactive templates (prompts), and actionable functions (tools) are exposed by an MCP server and consumed by an MCP client (which could be an LLM host application or an AI agent).
This guide covers two primary integration patterns:
- Using Existing MCP Servers within ADK: An ADK agent acts as an MCP client, leveraging tools provided by external MCP servers.
- Exposing ADK Tools via an MCP Server: Building an MCP server that wraps ADK tools, making them accessible to any MCP client.
Prerequisites
Before you begin, ensure you have the following set up:
- Set up ADK: Follow the standard ADK setup instructions in the quickstart.
- Install/update Python/Java: MCP requires Python version of 3.9 or higher for Python or Java 17+.
- Setup Node.js and npx: (Python only) Many community MCP servers are distributed as Node.js packages and run using
npx. Install Node.js (which includes npx) if you haven't already. For details, see https://nodejs.org/en. - Verify Installations: (Python only) Confirm
adkandnpxare in your PATH within the activated virtual environment:
# Both commands should print the path to the executables.
which adk
which npx
1. Using MCP servers with ADK agents (ADK as an MCP client)
This section demonstrates how to integrate tools from external MCP servers into your ADK agents. This is the most common integration pattern when your ADK agent needs to use capabilities provided by an existing service that exposes an MCP interface.
MCPToolset class
The MCPToolset class is ADK's primary mechanism for integrating tools from an MCP server. When you include an MCPToolset instance in your agent's tools list, it automatically handles the interaction with the specified MCP server. Here's how it works:
- Connection Management: On initialization,
MCPToolsetestablishes and manages the connection to the MCP server. This can be a local server process (usingStdioConnectionParamsfor communication over standard input/output) or a remote server (usingStreamableHTTPConnectionParamsfor HTTP-based communication). The toolset also handles the graceful shutdown of this connection when the agent or application terminates. - Tool Discovery & Adaptation: Once connected,
MCPToolsetqueries the MCP server for its available tools (via thelist_toolsMCP method). It then converts the schemas of these discovered MCP tools into ADK-compatibleBaseToolinstances. - Exposure to Agent: These adapted tools are then made available to your
LlmAgentas if they were native ADK tools. - Proxying Tool Calls: When your
LlmAgentdecides to use one of these tools,MCPToolsettransparently proxies the call (using thecall_toolMCP method) to the MCP server, sends the necessary arguments, and returns the server's response back to the agent. - Filtering (Optional): You can use the
tool_filterparameter when creating anMCPToolsetto select a specific subset of tools from the MCP server, rather than exposing all of them to your agent.
Connecting to Authenticated Remote Servers
For production use cases, MCP servers are often remote and secured. MCPToolset integrates with ADK's standard authentication framework to handle these scenarios.
!!! success "New Guide Available" To learn how to connect to a remote MCP server that requires authentication (e.g., with a Bearer token), see the new comprehensive guide: Authenticating with Remote MCP Servers
Example 1: Local File System MCP Server
This example demonstrates connecting to a local MCP server that provides file system operations. This pattern is ideal for development and testing.
Step 1: Define your Agent with MCPToolset
Create an agent.py file. The MCPToolset is instantiated directly within the tools list of your LlmAgent.
# ./adk_agent_samples/mcp_agent/agent.py
import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioConnectionParams
# IMPORTANT: Replace this with an actual absolute path on your system.
TARGET_FOLDER_PATH = "/path/to/your/folder"
root_agent = LlmAgent(
model='gemini-2.0-flash',
name='filesystem_assistant_agent',
instruction='Help the user manage their files. You can list files, read files, etc.',
tools=[
MCPToolset(
connection_params=StdioConnectionParams(
command='npx',
args=[
"-y", # Argument for npx to auto-confirm install
"@modelcontextprotocol/server-filesystem",
# This MUST be an ABSOLUTE path to a folder the
# npx process can access.
os.path.abspath(TARGET_FOLDER_PATH),
],
),
# Optional: Filter which tools from the MCP server are exposed
# tool_filter=['list_directory', 'read_file']
)
],
)
Step 2: Create an __init__.py file
Ensure you have an __init__.py in the same directory as agent.py to make it a discoverable Python package for ADK.
# ./adk_agent_samples/mcp_agent/__init__.py
from . import agent
Step 3: Run adk web and Interact
Navigate to the parent directory of mcp_agent and run adk web. Once the UI loads, select the filesystem_assistant_agent and try prompts like "List files in the current directory."
!!!info "Note for Windows users"
When hitting the _make_subprocess_transport NotImplementedError, consider using adk web --no-reload instead.
Example 2: Google Maps MCP Server
This example demonstrates connecting to the Google Maps MCP server, which requires an API key passed as an environment variable to the subprocess.
Step 1: Get API Key and Enable APIs
Follow the official Google Maps Platform documentation to get an API key and enable the necessary APIs (Directions API, Routes API).
Step 2: Define your Agent
Modify your agent.py file:
# ./adk_agent_samples/mcp_agent/agent.py
import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioConnectionParams
# It is a best practice to load secrets from environment variables.
google_maps_api_key = os.environ.get("GOOGLE_MAPS_API_KEY")
if not google_maps_api_key:
raise ValueError("GOOGLE_MAPS_API_KEY environment variable not set.")
root_agent = LlmAgent(
model='gemini-2.0-flash',
name='maps_assistant_agent',
instruction='Help the user with mapping, directions, and finding places.',
tools=[
MCPToolset(
connection_params=StdioConnectionParams(
command='npx',
args=[
"-y",
"@modelcontextprotocol/server-google-maps",
],
# Pass the API key as an environment variable to the npx process.
env={
"GOOGLE_MAPS_API_KEY": google_maps_api_key
}
),
)
],
)
Step 3: Run adk web and Interact
- Set Environment Variable: Before running
adk web, set your API key in your terminal:export GOOGLE_MAPS_API_KEY="YOUR_ACTUAL_GOOGLE_MAPS_API_KEY" - Run
adk web: Start the development server. - Interact: Select the
maps_assistant_agentand try prompts like: "Get directions from GooglePlex to SFO."
2. Building an MCP server with ADK tools (MCP server exposing ADK)
This pattern allows you to wrap existing ADK tools and make them available to any standard MCP client application. The example in this section exposes the ADK load_web_page tool through a custom-built MCP server.
Summary of steps
You will create a standard Python MCP server application using the mcp library. Within this server, you will:
- Instantiate the ADK tool(s) you want to expose (e.g.,
FunctionTool(load_web_page)). - Implement the MCP server's
@app.list_tools()handler to advertise the ADK tool(s). This involves converting the ADK tool definition to the MCP schema using theadk_to_mcp_tool_typeutility fromgoogle.adk.tools.mcp_tool.conversion_utils. - Implement the MCP server's
@app.call_tool()handler. This handler will:- Receive tool call requests from MCP clients.
- Identify if the request targets one of your wrapped ADK tools.
- Execute the ADK tool's
.run_async()method. - Format the ADK tool's result into an MCP-compliant response (e.g.,
mcp.types.TextContent).
Prerequisites
Install the MCP server library in the same Python environment as your ADK installation:
pip install mcp
Step 1: Create the MCP Server Script
Create a new Python file for your MCP server, for example, my_adk_mcp_server.py.
Step 2: Implement the Server Logic
Add the following code to my_adk_mcp_server.py. This script sets up an MCP server that exposes the ADK load_web_page tool.
# my_adk_mcp_server.py
import asyncio
import json
import os
from dotenv import load_dotenv
# MCP Server Imports
from mcp import types as mcp_types # Use alias to avoid conflict
from mcp.server.lowlevel import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio # For running as a stdio server
# ADK Tool Imports
from google.adk.tools.function_tool import FunctionTool
from google.adk.tools.load_web_page import load_web_page # Example ADK tool
# ADK <-> MCP Conversion Utility
from google.adk.tools.mcp_tool.conversion_utils import adk_to_mcp_tool_type
# --- Load Environment Variables (If ADK tools need them, e.g., API keys) ---
load_dotenv() # Create a .env file in the same directory if needed
# --- Prepare the ADK Tool ---
# Instantiate the ADK tool you want to expose.
# This tool will be wrapped and called by the MCP server.
print("Initializing ADK load_web_page tool...")
adk_tool_to_expose = FunctionTool(load_web_page)
print(f"ADK tool '{adk_tool_to_expose.name}' initialized and ready to be exposed via MCP.")
# --- End ADK Tool Prep ---
# --- MCP Server Setup ---
print("Creating MCP Server instance...")
# Create a named MCP Server instance using the mcp.server library
app = Server("adk-tool-exposing-mcp-server")
# Implement the MCP server's handler to list available tools
@app.list_tools()
async def list_mcp_tools() -> list[mcp_types.Tool]:
"""MCP handler to list tools this server exposes."""
print("MCP Server: Received list_tools request.")
# Convert the ADK tool's definition to the MCP Tool schema format
mcp_tool_schema = adk_to_mcp_tool_type(adk_tool_to_expose)
print(f"MCP Server: Advertising tool: {mcp_tool_schema.name}")
return [mcp_tool_schema]
# Implement the MCP server's handler to execute a tool call
@app.call_tool()
async def call_mcp_tool(
name: str, arguments: dict
) -> list[mcp_types.Content]: # MCP uses mcp_types.Content
"""MCP handler to execute a tool call requested by an MCP client."""
print(f"MCP Server: Received call_tool request for '{name}' with args: {arguments}")
# Check if the requested tool name matches our wrapped ADK tool
if name == adk_tool_to_expose.name:
try:
# Execute the ADK tool's run_async method.
# Note: tool_context is None here because this MCP server is
# running the ADK tool outside of a full ADK Runner invocation.
# If the ADK tool requires ToolContext features (like state or auth),
# this direct invocation might need more sophisticated handling.
adk_tool_response = await adk_tool_to_expose.run_async(
args=arguments,
tool_context=None,
)
print(f"MCP Server: ADK tool '{name}' executed. Response: {adk_tool_response}")
# Format the ADK tool's response (often a dict) into an MCP-compliant format.
# Here, we serialize the response dictionary as a JSON string within TextContent.
# Adjust formatting based on the ADK tool's output and client needs.
response_text = json.dumps(adk_tool_response, indent=2)
# MCP expects a list of mcp_types.Content parts
return [mcp_types.TextContent(type="text", text=response_text)]
except Exception as e:
print(f"MCP Server: Error executing ADK tool '{name}': {e}")
# Return an error message in MCP format
error_text = json.dumps({"error": f"Failed to execute tool '{name}': {str(e)}"})
return [mcp_types.TextContent(type="text", text=error_text)]
else:
# Handle calls to unknown tools
print(f"MCP Server: Tool '{name}' not found/exposed by this server.")
error_text = json.dumps({"error": f"Tool '{name}' not implemented by this server."})
return [mcp_types.TextContent(type="text", text=error_text)]
# --- MCP Server Runner ---
async def run_mcp_stdio_server():
"""Runs the MCP server, listening for connections over standard input/output."""
# Use the stdio_server context manager from the mcp.server.stdio library
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
print("MCP Stdio Server: Starting handshake with client...")
await app.run(
read_stream,
write_stream,
InitializationOptions(
server_name=app.name, # Use the server name defined above
server_version="0.1.0",
capabilities=app.get_capabilities(
# Define server capabilities - consult MCP docs for options
notification_options=NotificationOptions(),
experimental_capabilities={},
),
),
)
print("MCP Stdio Server: Run loop finished or client disconnected.")
if __name__ == "__main__":
print("Launching MCP Server to expose ADK tools via stdio...")
try:
asyncio.run(run_mcp_stdio_server())
except KeyboardInterrupt:
print("\nMCP Server (stdio) stopped by user.")
except Exception as e:
print(f"MCP Server (stdio) encountered an error: {e}")
finally:
print("MCP Server (stdio) process exiting.")
# --- End MCP Server ---
Step 3: Test your Custom MCP Server with an ADK Agent
Now, create an ADK agent that will act as a client to the MCP server you just built. This ADK agent will use MCPToolset to connect to your my_adk_mcp_server.py script.
Create an agent.py (e.g., in ./adk_agent_samples/mcp_client_agent/agent.py):
# ./adk_agent_samples/mcp_client_agent/agent.py
import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioConnectionParams
# IMPORTANT: Replace this with the ABSOLUTE path to your my_adk_mcp_server.py script
PATH_TO_YOUR_MCP_SERVER_SCRIPT = "/path/to/your/my_adk_mcp_server.py" # <<< REPLACE
if not os.path.exists(PATH_TO_YOUR_MCP_SERVER_SCRIPT):
raise FileNotFoundError(f"MCP Server script not found at: {PATH_TO_YOUR_MCP_SERVER_SCRIPT}")
root_agent = LlmAgent(
model='gemini-2.0-flash',
name='web_reader_mcp_client_agent',
instruction="Use the 'load_web_page' tool to fetch content from a URL provided by the user.",
tools=[
MCPToolset(
connection_params=StdioConnectionParams(
command='python3', # Command to run your MCP server script
args=[PATH_TO_YOUR_MCP_SERVER_SCRIPT], # Argument is the path to the script
)
)
],
)
And an __init__.py in the same directory:
# ./adk_agent_samples/mcp_client_agent/__init__.py
from . import agent
To run the test:
-
Run
adk webfor the client agent: Navigate to the parent directory ofmcp_client_agentand runadk web. -
Interact in the ADK Web UI:
- Select the
web_reader_mcp_client_agent. - Try a prompt like: "Load the content from https://example.com"
- Select the
The ADK agent will use MCPToolset to start and connect to your my_adk_mcp_server.py. Your MCP server will receive the call_tool request, execute the ADK load_web_page tool, and return the result.
Using MCP Tools outside of adk web
When you are not using adk web and are building your own application, you need to manage the agent and tool lifecycle yourself. The key difference is that creating the agent and its tools becomes an asynchronous operation, as ADK needs to connect to the MCP server to discover the tools.
The following example is modified from the file system server example above.
# standalone_mcp_agent.py
import os
import asyncio
from google.genai import types
from google.adk.agents.llm_agent import LlmAgent
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset, StdioConnectionParams, StreamableHTTPConnectionParams
TARGET_FOLDER_PATH = "/path/to/your/folder" # REPLACE with a valid absolute path
async def create_agent_with_mcp_tools():
"""Asynchronously creates an ADK Agent with tools from an MCP Server."""
toolset = MCPToolset(
# For local process communication
connection_params=StdioConnectionParams(
command='npx',
args=["-y", "@modelcontextprotocol/server-filesystem", TARGET_FOLDER_PATH],
),
# For remote servers, you would use StreamableHTTPConnectionParams instead:
# connection_params=StreamableHTTPConnectionParams(url="http://remote-server:port/mcp")
)
root_agent = LlmAgent(
model='gemini-2.0-flash',
name='filesystem_assistant',
instruction='Help user accessing their file systems',
tools=[toolset],
)
return root_agent, toolset
async def main():
session_service = InMemorySessionService()
session = await session_service.create_session(app_name='mcp_fs_app', user_id='user1')
query = "list all files"
print(f"User Query: '{query}'")
content = types.Content(role='user', parts=[types.Part(text=query)])
root_agent, toolset = await create_agent_with_mcp_tools()
runner = Runner(agent=root_agent, session_service=session_service)
print("Running agent...")
try:
events_async = runner.run_async(
session_id=session.id, user_id=session.user_id, new_message=content
)
async for event in events_async:
print(f"Event received: {event}")
finally:
# Crucially, ensure the toolset connection is closed to terminate
# any subprocesses.
print("Closing MCP server connection...")
await toolset.close()
print("Cleanup complete.")
if __name__ == '__main__':
try:
asyncio.run(main())
except Exception as e:
print(f"An error occurred: {e}")
Key considerations
When working with MCP and ADK, keep these points in mind:
-
Protocol vs. Library: MCP is a protocol specification, defining communication rules. ADK is a Python library/framework for building agents.
MCPToolsetbridges these by implementing the client side of the MCP protocol within the ADK framework. -
Connection Types:
StdioConnectionParams: For running an MCP server as a local subprocess. Ideal for development.StreamableHTTPConnectionParams: For connecting to a remote MCP server over HTTP. This is the standard for production and connecting to separate services.
-
Asynchronous Nature: Both ADK and the MCP Python library are heavily based on
asyncio. Tool implementations and server handlers should generally beasyncfunctions. Creating an agent with anMCPToolsetis anasyncoperation. -
Stateful Sessions (MCP): MCP establishes stateful, persistent connections between a client and server instance. This differs from typical stateless REST APIs.
- Deployment: This statefulness can pose challenges for scaling, especially for remote servers. Managing these persistent connections requires careful infrastructure considerations (e.g., load balancing, session affinity).
- ADK
MCPToolset: Manages this connection lifecycle. Thetry...finallyblock withtoolset.close()shown in the standalone example is crucial for ensuring the connection (and any subprocess) is properly terminated.