diff --git a/docs/tools-custom/mcp-tools-advanced.md b/docs/tools-custom/mcp-tools-advanced.md
new file mode 100644
index 0000000000..aa3fa07bcd
--- /dev/null
+++ b/docs/tools-custom/mcp-tools-advanced.md
@@ -0,0 +1,394 @@
+# Advanced MCP Configuration & Production Guide
+
+
+ Supported in ADKPython v0.1.0Typescript v0.2.0Go v0.1.0Java v0.1.0Kotlin v0.1.0
+
+
+This guide covers advanced integration patterns for the Model Context Protocol (MCP) in ADK. It provides production patterns for dynamic per-user authentication, human-in-the-loop approvals, long-running progress tracking, custom runtime execution, and enterprise cloud deployments.
+
+---
+
+## Choose your mechanism
+
+Use the matrix below to select the right configuration mechanism for your production workload:
+
+| Developer Requirement | Recommended Mechanism | Primary API / Parameter | Typical Scenario |
+| :--- | :--- | :--- | :--- |
+| **Inject per-user credentials or dynamic session tokens** | Dynamic Header Provider | `header_provider=...` | Multi-tenant apps, per-user JWTs/OAuth tokens |
+| **Require approval before dangerous tool calls** | Tool Confirmation | `require_confirmation=...` | Database mutations, destructive shell/file ops |
+| **Stream real-time progress for long operations** | Progress Callback & Factory | `progress_callback=...` | Heavy SQL queries, web scraping, data indexing |
+| **Run agents in FastAPI / backend services without `adk web`** | Programmatic Runner Lifecycle | `Runner` + `await toolset.close()` | Custom microservices, CLI tools, worker queues |
+| **Deploy containerized MCP agents to Cloud Run or GKE** | Stateless Streamable HTTP / Sidecar | `StreamableHTTPConnectionParams` | Horizontally scalable serverless or cluster pods |
+| **Resolve tool naming collisions across servers** | Tool Namespacing & Filtering | `tool_name_prefix`, `tool_filter` | Aggregating multiple MCP servers (DB + GitHub) |
+| **Handle server-requested sampling or auth challenges** | Bi-directional Protocol Callbacks | `sampling_callback`, `elicitation_callback` | Server-initiated LLM generation & auth prompts |
+| **Inspect raw STDERR diagnostic streams** | Diagnostic Stream Logging | `errlog=sys.stderr` | Troubleshooting MCP subprocess crashes |
+
+---
+
+## Dynamic authentication and per-user headers (`header_provider`)
+
+In multi-tenant or user-facing systems, hardcoding credentials into connection parameters is insecure. `McpToolset` supports `header_provider`, an asynchronous or synchronous callable that receives the active `ReadonlyContext` to dynamically construct authentication headers on every tool invocation.
+
+=== "Python"
+
+```python
+from google.adk.agents import LlmAgent, ReadonlyContext
+from google.adk.tools.mcp_tool import McpToolset
+from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
+
+async def extract_per_user_headers(context: ReadonlyContext) -> dict[str, str]:
+ """Dynamically extracts session state or per-user token on every turn."""
+ user_token = context.state.get("user_access_token", "ANONYMOUS_TOKEN")
+ return {
+ "Authorization": f"Bearer {user_token}",
+ "X-User-ID": context.user_id,
+ "X-Session-ID": context.session_id,
+ }
+
+toolset = McpToolset(
+ connection_params=StreamableHTTPConnectionParams(
+ url="https://mcp-server.example.com/mcp",
+ timeout=5,
+ sse_read_timeout=300,
+ ),
+ header_provider=extract_per_user_headers,
+)
+
+root_agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="enterprise_assistant",
+ instruction="Execute authorized MCP tools on behalf of authenticated users.",
+ tools=[toolset],
+)
+```
+
+---
+
+## Human-in-the-loop and tool confirmations (`require_confirmation`)
+
+MCP servers can expose high-impact capabilities, for example: database schema modifications or record deletions. You can enforce confirmation globally across all tools in the toolset or conditionally via a predicate function that inspects tool arguments.
+
+=== "Python"
+
+```python
+from typing import Any
+from google.adk.agents import LlmAgent
+from google.adk.tools.mcp_tool import McpToolset
+from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
+from mcp import StdioServerParameters
+
+def should_require_approval(args: dict[str, Any]) -> bool:
+ """Require user approval for destructive SQL statements."""
+ query = str(args.get("query", "")).lower()
+ destructive_keywords = ["drop", "delete", "truncate", "alter", "update"]
+ return any(keyword in query for keyword in destructive_keywords)
+
+toolset = McpToolset(
+ connection_params=StdioConnectionParams(
+ server_params=StdioServerParameters(
+ command="npx",
+ args=["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/db"],
+ ),
+ timeout=5,
+ ),
+ require_confirmation=should_require_approval, # Can also be a boolean (True)
+)
+
+root_agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="db_administrator",
+ instruction="Execute database queries safely with explicit approval for mutations.",
+ tools=[toolset],
+)
+```
+
+---
+
+## Real-time progress tracking (`progress_callback`)
+
+Long-running MCP operations, such as scraping large websites or training jobs, send intermediate progress notifications over the `notifications/progress` channel.
+
+### Option A: Global callback function
+Assign a shared callback for simple logging or progress reporting:
+
+```python
+async def on_mcp_progress(progress: float, total: float | None, message: str | None) -> None:
+ percentage = (progress / total * 100) if total else progress
+ print(f"[MCP Progress] {percentage:.1f}% complete: {message or 'Working...'}")
+
+toolset = McpToolset(
+ connection_params=...,
+ progress_callback=on_mcp_progress,
+)
+```
+
+### Option B: Per-tool callback factory (Session-Aware)
+Use `ProgressCallbackFactory` to inject tool-specific callbacks with write access to `ToolContext.state`:
+
+```python
+from google.adk.tools.tool_context import ToolContext
+
+def create_tool_progress_tracker(tool_name: str, callback_context: ToolContext, **kwargs):
+ """Generates custom progress handlers and updates active agent session state."""
+ async def progress_handler(progress: float, total: float | None, message: str | None):
+ callback_context.state[f"{tool_name}_status"] = message
+ callback_context.state[f"{tool_name}_progress"] = progress
+ return progress_handler
+
+toolset = McpToolset(
+ connection_params=...,
+ progress_callback=create_tool_progress_tracker,
+)
+```
+
+---
+
+## Standalone runner execution (Outside `adk web`)
+
+When embedding ADK agents into custom FastAPI applications, background workers, or standalone CLI scripts, instantiate `Runner` and manage lifecycle teardown explicitly via `await toolset.close()`.
+
+```python
+import asyncio
+import os
+from google.genai import types
+from google.adk.agents import LlmAgent
+from google.adk.runners import Runner
+from google.adk.sessions import InMemorySessionService
+from google.adk.tools.mcp_tool import McpToolset
+from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
+from mcp import StdioServerParameters
+
+async def run_standalone_mcp_agent():
+ # 1. Define McpToolset and Agent synchronously
+ toolset = McpToolset(
+ connection_params=StdioConnectionParams(
+ server_params=StdioServerParameters(
+ command="npx",
+ args=["-y", "@modelcontextprotocol/server-filesystem", os.path.abspath("./data")],
+ ),
+ timeout=5,
+ ),
+ tool_filter=["list_directory", "read_file"],
+ )
+
+ agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="filesystem_assistant",
+ instruction="Assist users with file management.",
+ tools=[toolset],
+ )
+
+ # 2. Setup Session and Runner
+ session_service = InMemorySessionService()
+ session = await session_service.create_session(
+ app_name="standalone_mcp_app",
+ user_id="user_001",
+ )
+
+ runner = Runner(
+ app_name="standalone_mcp_app",
+ agent=agent,
+ session_service=session_service,
+ )
+
+ try:
+ # 3. Stream agent execution
+ user_message = types.Content(
+ role="user",
+ parts=[types.Part(text="List the files available in the directory.")],
+ )
+
+ async for event in runner.run_async(
+ session_id=session.id,
+ user_id=session.user_id,
+ new_message=user_message,
+ ):
+ if event.content and event.content.parts:
+ for part in event.content.parts:
+ if part.text:
+ print(part.text, end="", flush=True)
+ finally:
+ # 4. Gracefully terminate subprocesses and network connections
+ print("\nTerminating MCP connection...")
+ await toolset.close()
+
+if __name__ == "__main__":
+ asyncio.run(run_standalone_mcp_agent())
+```
+
+---
+
+## Enterprise Cloud deployment architectures
+
+### Architecture 1: Cloud run remote service (Streamable HTTP)
+
+Deploy MCP servers as independently scalable Cloud Run services and connect your ADK agent using `StreamableHTTPConnectionParams`.
+
+=== "Python"
+
+```python
+# agent.py
+import os
+from google.adk.agents import LlmAgent
+from google.adk.tools.mcp_tool import McpToolset
+from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
+
+root_agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="cloud_run_agent",
+ instruction="Execute cloud-hosted tools.",
+ tools=[
+ McpToolset(
+ connection_params=StreamableHTTPConnectionParams(
+ url=os.getenv("REMOTE_MCP_URL", "https://mcp-service-xyz.run.app/mcp"),
+ headers={"Authorization": f"Bearer {os.getenv('MCP_AUTH_TOKEN')}"},
+ timeout=5,
+ sse_read_timeout=300,
+ )
+ )
+ ],
+)
+```
+
+Deploying to Cloud Run:
+```bash
+uv run adk deploy cloud_run \
+ --project= \
+ --region= \
+ --service_name="mcp-agent-service" \
+ ./path/to/agent_directory
+```
+
+---
+
+### Architecture 2: GKE sidecar pattern
+
+In Kubernetes/GKE environments, run the MCP server as a companion sidecar container in the same Pod for high-throughput, low-latency `localhost` communication.
+
+```yaml
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: adk-agent-with-mcp
+spec:
+ replicas: 3
+ template:
+ spec:
+ containers:
+ # Primary ADK Agent Container
+ - name: adk-agent
+ image: gcr.io/my-project/adk-agent:latest
+ ports:
+ - containerPort: 8080
+ env:
+ - name: MCP_SERVER_URL
+ value: "http://127.0.0.1:8081/mcp"
+ # MCP Server Sidecar
+ - name: mcp-server
+ image: gcr.io/my-project/mcp-server:latest
+ ports:
+ - containerPort: 8081
+```
+
+---
+
+### Architecture 3: Agent Platform runtime
+
+Deploying to Agent Platform Runtime:
+
+```bash
+uv run adk deploy agent_engine \
+ --project= \
+ --region= \
+ --display_name="Production MCP Agent" \
+ ./path/to/agent_directory
+```
+
+---
+
+## Name collisions and tool namespacing (`tool_name_prefix`)
+
+When you connect to multiple MCP servers, tool names such as `query` or `search` can conflict. Use `tool_name_prefix` to automatically namespace discovered tools:
+
+```python
+from google.adk.tools.mcp_tool import McpToolset
+from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
+from mcp import StdioServerParameters
+
+postgres_toolset = McpToolset(
+ connection_params=StdioConnectionParams(
+ server_params=StdioServerParameters(
+ command="npx",
+ args=["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/db"],
+ )
+ ),
+ tool_name_prefix="pg_", # Generates pg_query, pg_list_tables
+)
+
+github_toolset = McpToolset(
+ connection_params=StdioConnectionParams(
+ server_params=StdioServerParameters(
+ command="npx",
+ args=["-y", "@modelcontextprotocol/server-github"],
+ )
+ ),
+ tool_name_prefix="gh_", # Generates gh_search_repositories, gh_create_issue
+)
+```
+
+---
+
+## Bi-directional protocol hooks: sampling and elicitation
+
+The Model Context Protocol supports bi-directional interaction where servers can request actions from clients:
+- **Sampling (`sampling_callback`)**: Allows the MCP server to ask the ADK host to generate an LLM completion.
+- **Elicitation (`elicitation_callback`)**: Allows the MCP server to request out-of-band user interactions or authentication flows.
+
+```python
+from mcp import SamplingCapability
+from google.adk.tools.mcp_tool import McpToolset
+
+async def handle_server_sampling(params):
+ """Processes server-initiated LLM generation requests."""
+ return {
+ "role": "assistant",
+ "content": {"type": "text", "text": "Generated response from ADK"},
+ }
+
+async def handle_server_elicitation(params):
+ """Handles authentication or interactive challenges from the server."""
+ print(f"Elicitation requested: {params}")
+ return {"action": "approved"}
+
+toolset = McpToolset(
+ connection_params=...,
+ sampling_callback=handle_server_sampling,
+ sampling_capabilities=SamplingCapability(),
+ elicitation_callback=handle_server_elicitation,
+)
+```
+
+---
+
+## Diagnostic logging and error streams (`errlog`)
+
+By default, MCP subprocess errors are logged to standard error. You can redirect STDERR streams to an external file or diagnostic buffer for root-cause debugging:
+
+```python
+import sys
+from google.adk.tools.mcp_tool import McpToolset
+
+with open("mcp_server_errors.log", "a") as error_file:
+ toolset = McpToolset(
+ connection_params=...,
+ errlog=error_file, # Redirect subprocess STDERR to a log file
+ )
+```
+
+---
+
+## Next Steps
+
+* Return to the [Model Context Protocol Overview](./mcp-tools.md) for basic setup, Resources, and Experimental UI Widgets.
+* Explore [Custom Function Tools](./function-tools.md) for in-process Python tools.
+* Read the [ADK Deployment Guide](../deploy/index.md) for full cloud configuration options.
diff --git a/docs/tools-custom/mcp-tools.md b/docs/tools-custom/mcp-tools.md
index 1b8573941e..66f04e4e92 100644
--- a/docs/tools-custom/mcp-tools.md
+++ b/docs/tools-custom/mcp-tools.md
@@ -1,691 +1,442 @@
# Model Context Protocol Tools
- Supported in ADKPython v0.1.0Typescript v0.2.0Go v0.1.0Java v0.1.0
+ Supported in ADKPython v0.3.10Typescript v0.2.0Go v0.1.0Java v0.1.0
-This guide walks you through two ways of integrating Model Context Protocol (MCP) with ADK.
-
-!!! tip "MCP tools for ADK"
- For a list of pre-built MCP tools for ADK, see [Tools and Integrations](/integrations/?topic=mcp).
-
-## 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:
-
-1. **Using Existing MCP Servers within ADK:** An ADK agent acts as an MCP client, leveraging tools provided by external MCP servers.
-2. **Exposing ADK Tools via an MCP Server:** Building an MCP server that wraps ADK tools, making them accessible to any MCP client.
-
-## Key considerations
-
-When you start building with the Model Context Protocol (MCP) and ADK, these key architectural differences will help you design more stable and efficient agents:
-
-* **Protocol vs. Library:** MCP is a protocol specification, defining communication rules. ADK is a Python library/framework for building agents. McpToolset bridges these by implementing the client side of the MCP protocol within the ADK framework. Conversely, building an MCP server in Python requires using the model-context-protocol library.
-
-* **ADK Tools vs. MCP Tools:**
-
- * ADK Tools (BaseTool, FunctionTool, AgentTool, etc.) are Python objects designed for direct use within the ADK's LlmAgent and Runner.
- * MCP Tools are capabilities exposed by an MCP Server according to the protocol's schema. McpToolset makes these look like ADK tools to an LlmAgent.
-
-* **Asynchronous nature:** Both ADK and the MCP Python library are heavily based on the asyncio Python library. Tool implementations and server handlers should generally be async functions.
-
-* **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 and deployment, especially for remote servers handling many users. The original MCP design often assumed client and server were co-located. Managing these persistent connections requires careful infrastructure considerations (e.g., load balancing, session affinity).
- * **ADK McpToolset:** Manages this connection lifecycle. The exit\_stack pattern shown in the examples is crucial for ensuring the connection (and potentially the server process) is properly terminated when the ADK agent finishes.
-
-* **Session persistence**: The `MCPToolset` supports object serialization via `getstate` and `setstate` methods. This feature helps your agent maintain its context when deployed to managed environments like Cloud Run or Google Kubernetes Engine (GKE).
-
-!!! Note: While the agent preserves its session state during lifecycle events, active MCP connections are not automatically re-established upon restoration. The agent will re-initialize its connection to the MCP server as needed after the process is restored to ensure a reliable and up-to-date link.
+The **Model Context Protocol (MCP)** is an open standard for connecting generative AI models to external data sources, tools, and systems. Think of it as a universal connection mechanism that simplifies how LLMs obtain context, execute actions, and interact with various systems.
+
+---
+
+## Core architecture and concepts
+
+MCP follows a client-server architecture, defining how data or resources, interactive templates or prompts, and actionable functions or tools are exposed by an MCP server and consumed by an MCP client, which could be an LLM host application or an AI agent. In ADK, you use the McpToolset class as an interface between MCP Servers and ADK agents. It is also possible to configure an ADK server as an MCP server for use by other client systems.
+
+```mermaid
+sequenceDiagram
+ autonumber
+ participant Agent as ADK LlmAgent (Client)
+ participant Toolset as McpToolset
+ participant Server as MCP Server
+
+ Agent->>Toolset: Initialize connection
+ Toolset->>Server: Protocol Handshake & Tool Discovery (list_tools)
+ Server-->>Toolset: Available Tool Schemas
+ Toolset-->>Agent: Adapted ADK Tools
+
+ Agent->>Toolset: Call Tool (arguments)
+ Toolset->>Server: Execute Tool (call_tool via Stdio/HTTP)
+ Server-->>Toolset: Execution Result (Text/JSON)
+ Toolset-->>Agent: Result returned to LLM
+```
-## Prerequisites
+## Prerequisites and setup rules
Before you begin, ensure you have the following set up:
-* **Set up ADK:** Follow the standard ADK [setup instructions](../get-started/index.md) in the quickstart.
-* **Install/update Python/Java:** MCP requires Python version of 3.9 or higher for Python or Java 17 or higher.
-* **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](https://nodejs.org/en).
-* **Verify Installations:** **(Python only)** Confirm `adk` and `npx` are in your PATH within the activated virtual environment:
+- **ADK Installed**: Complete standard ADK setup in your project environment.
+- **Runtime Requirements**: Python 3.10+ or Java 17+.
+- **Node.js & `npx`** *(Python/TS only)*: Required to run npm-packaged community MCP servers.
+- **Verify installations**: Confirm `adk` and `npx` are in your PATH in the activated virtual environment:
=== "MacOS / Linux"
- ```shell
- # Both commands should print the path to the executables.
- which adk
- which npx
- ```
-
-=== "Windows"
-
- ```shell
- # Both commands should print the path to the executables.
- Get-Command adk
- Get-Command npx
- ```
-
-## 1. Using MCP servers with ADK agents (ADK as an MCP client) in `adk web`
-
-This section demonstrates how to integrate tools from external MCP (Model Context Protocol) 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. You will see how the `McpToolset` class can be directly added to your agent's `tools` list, enabling seamless connection to an MCP server, discovery of its tools, and making them available for your agent to use. These examples primarily focus on interactions within the `adk web` development environment.
-
-### `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:
-
-1. **Connection Management:** On initialization, `McpToolset` establishes and manages the connection to the MCP server. This can be a local server process (using `StdioConnectionParams` for communication over standard input/output) or a remote server (using `SseConnectionParams` for Server-Sent Events). The toolset also handles the graceful shutdown of this connection when the agent or application terminates.
-2. **Tool Discovery & Adaptation:** Once connected, `McpToolset` queries the MCP server for its available tools (via the `list_tools` MCP method). It then converts the schemas of these discovered MCP tools into ADK-compatible `BaseTool` instances.
-3. **Exposure to Agent:** These adapted tools are then made available to your `LlmAgent` as if they were native ADK tools.
-4. **Proxying Tool Calls:** When your `LlmAgent` decides to use one of these tools, `McpToolset` transparently proxies the call (using the `call_tool` MCP method) to the MCP server, sends the necessary arguments, and returns the server's response back to the agent.
-5. **Filtering (Optional):** You can use the `tool_filter` parameter when creating an `McpToolset` to select a specific subset of tools from the MCP server, rather than exposing all of them to your agent.
-
-The following examples demonstrate how to use `McpToolset` within the `adk web` development environment. For scenarios where you need more fine-grained control over the MCP connection lifecycle or are not using `adk web`, refer to the "Using MCP Tools in your own Agent out of `adk web`" section later in this page.
-
-### Example 1: File System MCP Server
-
-This Python example demonstrates connecting to a local MCP server that provides file system operations.
-
-#### Step 1: Define your Agent with `McpToolset`
-
-Create an `agent.py` file (e.g., in `./adk_agent_samples/mcp_agent/agent.py`). The `McpToolset` is instantiated directly within the `tools` list of your `LlmAgent`.
-
-* **Important:** Replace `"/path/to/your/folder"` in the `args` list with the **absolute path** to an actual folder on your local system that the MCP server can access.
-* **Important:** Place the `.env` file in the parent directory of the `./adk_agent_samples` directory.
-
-```python
-# ./adk_agent_samples/mcp_agent/agent.py
-import os # Required for path operations
-from google.adk.agents import LlmAgent
-from google.adk.tools.mcp_tool import McpToolset
-from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
-from mcp import StdioServerParameters
-
-# It's good practice to define paths dynamically if possible,
-# or ensure the user understands the need for an ABSOLUTE path.
-# For this example, we'll construct a path relative to this file,
-# assuming '/path/to/your/folder' is in the same directory as agent.py.
-# REPLACE THIS with an actual absolute path if needed for your setup.
-TARGET_FOLDER_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "/path/to/your/folder")
-# Ensure TARGET_FOLDER_PATH is an absolute path for the MCP server.
-# If you created ./adk_agent_samples/mcp_agent/your_folder,
-
-root_agent = LlmAgent(
- model='gemini-flash-latest',
- name='filesystem_assistant_agent',
- instruction='Help the user manage their files. You can list files, read files, etc.',
- tools=[
- McpToolset(
- connection_params=StdioConnectionParams(
- server_params = StdioServerParameters(
- command='npx',
- args=[
- "-y", # Argument for npx to auto-confirm install
- "@modelcontextprotocol/server-filesystem",
- # IMPORTANT: This MUST be an ABSOLUTE path to a folder the
- # npx process can access.
- # Replace with a valid absolute path on your system.
- # For example: "/Users/youruser/accessible_mcp_files"
- # or use a dynamically constructed absolute path:
- 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.
-
-```python
-# ./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` (e.g., `adk_agent_samples`) in your terminal and run:
-
-```shell
-cd ./adk_agent_samples # Or your equivalent parent directory
-adk web
-```
-
-!!!info "Note for Windows users"
-
- When hitting the `_make_subprocess_transport NotImplementedError`, consider using `adk web --no-reload` instead.
-
-
-Once the ADK Web UI loads in your browser:
-
-1. Select the `filesystem_assistant_agent` from the agent dropdown.
-2. Try prompts like:
- * "List files in the current directory."
- * "Can you read the file named sample.txt?" (assuming you created it in `TARGET_FOLDER_PATH`).
- * "What is the content of `another_file.md`?"
-
-You should see the agent interacting with the MCP file system server, and the server's responses (file listings, file content) relayed through the agent. The `adk web` console (terminal where you ran the command) might also show logs from the `npx` process if it outputs to stderr.
-
-
+ ```bash
+ # Both commands should print the path to the executables.
+ which adk
+ which npx
+ ```
+
+=== "Windows PowerShell"
-For Java, refer to the following sample to define an agent that initializes the `McpToolset`:
+ ```powershell
+ # Both commands should print the path to the executables.
+ Get-Command adk
+ Get-Command npx
+ ```
+
+!!! warning "Deployment rule"
-```java
-package agents;
+ Agents deployed to production **must define `McpToolset` synchronously** in `agent.py`. Dynamic asynchronous agent initialization is only supported for local debugging or custom standalone runners.
-import com.google.adk.agents.LlmAgent;
-import com.google.adk.runner.InMemoryRunner;
-import com.google.adk.sessions.SessionKey;
-import com.google.adk.tools.mcp.McpToolset;
-import com.google.adk.tools.mcp.StdioServerParameters;
-import com.google.genai.types.Content;
-import com.google.genai.types.Part;
+---
-import java.util.List;
+## MCP Implementation options
-public class McpAgentCreator {
+ When you start building with the Model Context Protocol (MCP) and ADK, these key architectural differences will help you design more stable and efficient agents. The following table works as a comparative guide to help you construct those agents.
- /**
- * Initializes an McpToolset, retrieves tools from an MCP server using stdio,
- * creates an LlmAgent with these tools, sends a prompt to the agent,
- * and ensures the toolset is closed.
- * @param args Command line arguments (not used).
- */
- public static void main(String[] args) {
- //Note: you may have permissions issues if the folder is outside home
- String yourFolderPath = "~/path/to/folder";
+| Dimension | [**Direct MCP Tool Integration** (`McpToolset`)](#direct-mcp-tool-integration-mcptoolset) | [**Agent-Exposed MCP Server** (`to_mcp_server`)](#agent-exposed-mcp-server-to_mcp_server) | [**Specialized Sub-Agent Delegation** (`AgentTool`)](#specialized-sub-agent-delegation-agenttool) |
+| :--- | :--- | :--- | :--- |
+| **Architecture** | External server process or remote service providing deterministic endpoints adapted into the primary `LlmAgent` tool list. | An autonomous ADK agent compiled into an MCP server, callable by external clients (Claude Code, IDEs, external hosts). | In-process, hierarchical agent encapsulation where a parent agent invokes a child `LlmAgent` as a callable tool. |
+| **Context Window Impact** | **High Context Bloat**: Every tool definition and raw output, for example: database rows or file blobs, enters the primary agent's history. | **Isolated**: The external caller only receives the final aggregated response text/blocks. | **Zero Context Bloat**: Intermediate exploratory reasoning, failed tool calls, and large raw outputs remain isolated in the sub-agent loop. |
+| **AI Model Load and Tiering** | Single model must understand all tool schemas, validation constraints, and workflow state simultaneously. | Independent model reasoning dedicated solely to the wrapped task. | Enables **model tiering**, for example: `gemini-2.5-pro` for orchestrator, and `gemini-2.5-flash` for sub-agent tool execution with dedicated system instructions. |
+| **Latency & Token Cost** | **Lower Cost & Predictable Latency**: 1 LLM turn + 1 deterministic tool invocation + 1 response generation turn. | Client-driven; latency depends on internal agent execution depth. | **Higher Cost & Variable Latency**: Multiple LLM calls, sub-agent reasoning turns before returning to parent. |
+| **Ideal Use Cases** | - Deterministic API integrations: Postgres, BigQuery, GitHub, Google Maps.
- File system operations & static resource reading.
- Reusing standard pre-built community MCP servers.
| - Exposing complex ADK multi-agent capabilities to external MCP-compliant ecosystems.
- Integrating ADK agents into IDEs, editors, or A2A pipelines.
| - Multi-step autonomous workflows requiring trial-and-error. For example: code debugging or research synthesis.
- Tasks needing isolated personas or specialized instructions.
- Scenarios with >20 tools where schema overload harms accuracy.
|
- StdioServerParameters serverParams = StdioServerParameters.builder()
- .command("npx")
- .args(List.of(
- "-y",
- "@modelcontextprotocol/server-filesystem",
- yourFolderPath
- ))
- .build();
+!!! note "State restoration"
- try (McpToolset toolset = new McpToolset(serverParams.toServerParameters())) {
- LlmAgent agent = LlmAgent.builder()
- .model("gemini-flash-latest")
- .name("enterprise_assistant")
- .description("An agent to help users access their file systems")
- .instruction(
- "Help user accessing their file systems. You can list files in a directory."
- )
- .tools(toolset)
- .build();
+ While ADK agents preserve session state during lifecycle events, they do not automatically re-establish active MCP connections upon restoration. Agents re-initialize connections as needed.
- System.out.println("Agent created: " + agent.name());
+## Understand uses and integrations
- InMemoryRunner runner = new InMemoryRunner(agent);
- String userId = "user123";
- String sessionId = "1234";
- String promptText = "Which files are in this directory - " + yourFolderPath + "?";
+There are three main integration patterns:
+1. **Direct MCP Tool Integration**: When an ADK agent acts as an MCP client using `McpToolset`.
+2. **Agent-Exposed MCP Server**: When you build an MCP server that wraps ADK Tools using `to_mcp_server`.
+3. **Specialized Sub-Agent Delegation**: When an agent delegates to a sub-agent using `AgentTool`.
+
- // Explicitly create the session first
- SessionKey sessionKey = runner.sessionService().createSession(runner.appName(), userId, null, sessionId).blockingGet().sessionKey();
- System.out.println("Session created: " + sessionId + " for user: " + userId);
+### Direct MCP Tool Integration (McpToolset)
- Content promptContent = Content.fromParts(Part.fromText(promptText));
+The `McpToolset` class can be directly added to your agent's tools list; this class enables seamless connection to an MCP server, discovery of its tools, and making them available for your agent to use. On initialization, `McpToolset` establishes and manages the connection to the MCP server. It also handles graceful connection shutdown when the agent or process terminates.
+Use `McpToolset` to import tools from an external MCP server into your ADK `LlmAgent`.
- System.out.println("\nSending prompt: \"" + promptText + "\" to agent...\n");
-
- runner.runAsync(sessionKey, promptContent)
- .blockingForEach(event -> {
- System.out.println("Event received: " + event.toJson());
- });
- } catch (Exception e) {
- System.err.println("An error occurred: " + e.getMessage());
- e.printStackTrace();
- }
- }
-}
-```
+#### Example: Local Stdio Transport (FileSystem MCP)
-Assuming a folder containing three files named `first`, `second` and `third`, successful response will look like this:
+This example sets up an ADK agent that connects to a local MCP file system server; it instantiates the McpToolset directly within the agent's tools list to enable file management capabilities.
-```shell
-Event received: {"id":"163a449e-691a-48a2-9e38-8cadb6d1f136","invocationId":"e-c2458c56-e57a-45b2-97de-ae7292e505ef","author":"enterprise_assistant","content":{"parts":[{"functionCall":{"id":"adk-388b4ac2-d40e-4f6a-bda6-f051110c6498","args":{"path":"~/home-test"},"name":"list_directory"}}],"role":"model"},"actions":{"stateDelta":{},"artifactDelta":{},"requestedAuthConfigs":{}},"timestamp":1747377543788}
+**Step 1**. Define your agent with `McpToolset`:
-Event received: {"id":"8728380b-bfad-4d14-8421-fa98d09364f1","invocationId":"e-c2458c56-e57a-45b2-97de-ae7292e505ef","author":"enterprise_assistant","content":{"parts":[{"functionResponse":{"id":"adk-388b4ac2-d40e-4f6a-bda6-f051110c6498","name":"list_directory","response":{"text_output":[{"text":"[FILE] first\n[FILE] second\n[FILE] third"}]}}}],"role":"user"},"actions":{"stateDelta":{},"artifactDelta":{},"requestedAuthConfigs":{}},"timestamp":1747377544679}
-
-Event received: {"id":"8fe7e594-3e47-4254-8b57-9106ad8463cb","invocationId":"e-c2458c56-e57a-45b2-97de-ae7292e505ef","author":"enterprise_assistant","content":{"parts":[{"text":"There are three files in the directory: first, second, and third."}],"role":"model"},"actions":{"stateDelta":{},"artifactDelta":{},"requestedAuthConfigs":{}},"timestamp":1747377544689}
-```
-
-For Typescript, you can define an agent that initializes the `MCPToolset` as follows:
+=== "Python"
-```typescript
-import 'dotenv/config';
-import {LlmAgent, MCPToolset} from "@google/adk";
-
-// REPLACE THIS with an actual absolute path for your setup.
-const TARGET_FOLDER_PATH = "/path/to/your/folder";
-
-export const rootAgent = new LlmAgent({
- model: "gemini-flash-latest",
- name: "filesystem_assistant_agent",
- instruction: "Help the user manage their files. You can list files, read files, etc.",
- tools: [
- // To filter tools, pass a list of tool names as the second argument
- // to the MCPToolset constructor.
- // e.g., new MCPToolset(connectionParams, ['list_directory', 'read_file'])
- new MCPToolset(
- {
+ ```python
+ import os
+ from google.adk.agents import LlmAgent
+ from google.adk.tools.mcp_tool import McpToolset
+ from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
+ from mcp import StdioServerParameters
+
+ TARGET_FOLDER = os.path.abspath("./accessible_files")
+
+ root_agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="filesystem_assistant",
+ instruction="Help users manage local files.",
+ tools=[
+ McpToolset(
+ connection_params=StdioConnectionParams(
+ server_params=StdioServerParameters(
+ command="npx",
+ args=["-y", "@modelcontextprotocol/server-filesystem", TARGET_FOLDER],
+ ),
+ ),
+ # Optional: Select specific tools exposed to the agent
+ tool_filter=["list_directory", "read_file"],
+ )
+ ],
+ )
+ ```
+
+ **Step 2**: Package and Run your Agent to make your agent discoverable to ADK and start interacting with it, follow this workflow:
+
+ - Initiate your package: Create an `__init__.py` file in the same directory as your agent.py. This step is required for ADK to recognize your agent.
+ - Launch the Web Interface:
+
+ ```bash
+ cd ./adk_agent_samples
+ adk web
+ ```
+
+ - Interact with the Agent: select `filesystem_assistant` from the drop-down menu and prompt the Agent with commands: *List files in the current directory* or *What is the content of another_file.md?*
+
+ 
+
+=== "TypeScript"
+
+ ```typescript
+ import { LlmAgent, MCPToolset } from "@google/adk";
+ import path from "path";
+
+ const TARGET_FOLDER = path.resolve("./accessible_files");
+
+ export const rootAgent = new LlmAgent({
+ model: "gemini-flash-latest",
+ name: "filesystem_assistant",
+ instruction: "Help users manage local files.",
+ tools: [
+ new MCPToolset({
type: "StdioConnectionParams",
serverParams: {
command: "npx",
- args: [
- "-y",
- "@modelcontextprotocol/server-filesystem",
- // IMPORTANT: This MUST be an ABSOLUTE path to a folder the
- // npx process can access.
- // Replace with a valid absolute path on your system.
- // For example: "/Users/youruser/accessible_mcp_files"
- TARGET_FOLDER_PATH,
- ],
+ args: ["-y", "@modelcontextprotocol/server-filesystem", TARGET_FOLDER],
},
- }
- )
- ],
-});
-```
-
+ }, ["list_directory", "read_file"]) // Optional tool filter array
+ ],
+ });
+ ```
+=== "Java"
-### Example 2: Google Maps Grounding Lite MCP Server
+ ```java
+ package agents;
-[Google Maps Platform Grounding Lite](https://developers.google.com/maps/ai/grounding-lite) is a service with Model Context Protocol (MCP) support that makes it easy to ground your AI applications with trusted geospatial data from Google Maps. The MCP server provides tools that allow LLMs to access capabilities for places, weather, and routes. You can try out Grounding Lite by enabling it in any tool that supports MCP servers.
+ import com.google.adk.agents.LlmAgent;
+ import com.google.adk.tools.mcp.McpToolset;
+ import com.google.adk.tools.mcp.StdioServerParameters;
+ import java.util.List;
+
+ public class FileSystemAgentCreator {
+ public static void main(String[] args) throws Exception {
+ StdioServerParameters serverParams = StdioServerParameters.builder()
+ .command("npx")
+ .args(List.of("-y", "@modelcontextprotocol/server-filesystem", "/absolute/path/to/folder"))
+ .build();
-Grounding Lite provides tools that allow LLMs to access the following Google Maps capabilities:
+ try (McpToolset toolset = new McpToolset(serverParams.toServerParameters())) {
+ LlmAgent agent = LlmAgent.builder()
+ .model("gemini-flash-latest")
+ .name("filesystem_assistant")
+ .instruction("Help users access their file systems.")
+ .tools(toolset)
+ .build();
+
+ System.out.println("Agent initialized: " + agent.name());
+ }
+ }
+ }
+ ```
-* **Search places:** Request information about places and get AI-generated place data summaries, as well as Place IDs, latitude and longitude coordinates, and Google Maps links for each of the places included in the summary. You can use the returned Place IDs and latitude and longitude coordinates with other Google Maps Platform APIs to show places on a map.
-* **Lookup weather:** Request information about weather and return current conditions, hourly forecasts, and daily forecasts.
-* **Compute routes:** Request information about driving or walking routes between two locations and return route distance and duration information.
+---
-#### Step 1: Enable the Maps Grounding Lite service on your Google Cloud project
+#### Example: Remote HTTP / SSE Transport (Google Maps Grounding Lite)
-1. [Set up your Google Cloud project](https://developers.google.com/maps/get-started#create-project) if you haven’t got one.
-2. In the [Google Cloud Console](https://console.developers.google.com), choose the project you want to use for Grounding Lite.
-3. Enable Grounding Lite in the [Google Cloud Console API Library](https://console.developers.google.com/apis/library/mapstools.googleapis.com).
-4. [Get a Google Maps Platform API Key](https://developers.google.com/maps/get-started#api-key)
+Before starting, follow the instructions for [Google Maps Grounding Lite](https://developers.google.com/maps/ai/grounding-lite) to enable the service on your Google Cloud project and generate your Maps Platform API Key.
+Unlike the previous local process example, this pattern connects your agent to a remote, cloud-hosted MCP server using Server-Sent Events (SSE). It uses the Google Maps Grounding Lite service to demonstrate how to pass authentication headers, such as an API key, to a scalable endpoint.
-#### Step 2: Define your Agent with `McpToolset` for Google Maps Grounding Lite
+**Step 1**: Define your agent with `McpToolset`.
-Modify your `agent.py` file (e.g., in `./adk_agent_samples/mcp_agent/agent.py`). Replace `YOUR_GOOGLE_MAPS_API_KEY` with the actual API key you obtained.
+=== "Python"
-```python
-# ./adk_agent_samples/mcp_agent/agent.py
-import os
-from google.adk.agents.llm_agent import Agent
-from google.adk.tools.mcp_tool import McpToolset
-from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
-
-# Retrieve the API key from an environment variable or directly insert it.
-# Using an environment variable is generally safer.
-# Ensure this environment variable is set in the terminal where you run 'adk web'.
-# Example: export GOOGLE_MAPS_API_KEY="YOUR_ACTUAL_KEY"
-GOOGLE_MAPS_API_KEY = os.getenv("GOOGLE_MAPS_API_KEY")
-
-if not GOOGLE_MAPS_API_KEY:
- # Fallback or direct assignment for testing - NOT RECOMMENDED FOR PRODUCTION
- GOOGLE_MAPS_API_KEY = "YOUR_GOOGLE_MAPS_API_KEY_HERE" # Replace if not using env var
- if GOOGLE_MAPS_API_KEY == "YOUR_GOOGLE_MAPS_API_KEY_HERE":
- print("WARNING: GOOGLE_MAPS_API_KEY is not set. Please set it as an environment variable or in the script.")
- # You might want to raise an error or exit if the key is crucial and not found.
-
-root_agent = Agent(
- model='gemini-flash-latest',
- name='travel_planner_agent',
- description='A helpful assistant for planning travel routes.',
- tools=[
- McpToolset(
- connection_params=StreamableHTTPConnectionParams(
- url="https://mapstools.googleapis.com/mcp",
- headers={
- "X-Goog-Api-Key": GOOGLE_MAPS_API_KEY,
- "Content-Type": "application/json",
- "Accept": "application/json, text/event-stream"
- }
+ ```python
+ import os
+ from google.adk.agents import LlmAgent
+ from google.adk.tools.mcp_tool import McpToolset
+ from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
+
+ API_KEY = os.getenv("GOOGLE_MAPS_API_KEY")
+
+ root_agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="travel_planner",
+ instruction="Plan travel routes and search locations using Google Maps.",
+ tools=[
+ McpToolset(
+ connection_params=StreamableHTTPConnectionParams(
+ url="https://mapstools.googleapis.com/mcp",
+ headers={
+ "X-Goog-Api-Key": API_KEY,
+ "Content-Type": "application/json",
+ "Accept": "application/json, text/event-stream",
+ },
+ timeout=5,
+ sse_read_timeout=300
+ )
)
- )
- ]
-)
-```
-
-#### Step 3: Ensure `__init__.py` Exists
-
-If you created this in Example 1, you can skip this. Otherwise, ensure you have an `__init__.py` in the `./adk_agent_samples/mcp_agent/` directory:
-
-```python
-# ./adk_agent_samples/mcp_agent/__init__.py
-from . import agent
-```
-
-#### Step 4: Run `adk web` and Interact
-
-1. **Set Environment Variable (Recommended):**
- Before running `adk web`, it's best to set your Google Maps API key as an environment variable in your terminal:
- ```shell
- export GOOGLE_MAPS_API_KEY="YOUR_ACTUAL_GOOGLE_MAPS_API_KEY"
+ ],
+ )
```
- Replace `YOUR_ACTUAL_GOOGLE_MAPS_API_KEY` with your key.
-
-2. **Run `adk web`**:
- Navigate to the parent directory of `mcp_agent` (e.g., `adk_agent_samples`) and run:
- ```shell
- cd ./adk_agent_samples # Or your equivalent parent directory
- adk web
+ **Step 2**: Set environment variable before running `adk web`, set you Google API key in your terminal
+
+ ```bash
+ export GOOGLE_MAPS_API_KEY="YOUR_ACTUAL_GOOGLE_MAPS_API_KEY"
+ ```
+
+ **Step 3**: Run `adk web`: Navigate to the parent directory of `mcp_agent` and launch the web Interface.
+ **Step 4**: Interact with the UI:
+ - Select `travel_planner` from the drop-down.
+ - Try prompts such as: *I will be in San Francisco tomorrow. What's the weather like* or *Find coffee shops near Golden Gate Park*
+
+ 
+
+=== "TypeScript"
+
+ ```typescript
+ import { LlmAgent, MCPToolset } from "@google/adk";
+
+ export const rootAgent = new LlmAgent({
+ model: "gemini-flash-latest",
+ name: "travel_planner",
+ instruction: "Plan travel routes and search locations using Google Maps.",
+ tools: [
+ new MCPToolset({
+ type: "SseConnectionParams",
+ url: "https://mapstools.googleapis.com/mcp",
+ headers: {
+ "X-Goog-Api-Key": process.env.GOOGLE_MAPS_API_KEY!,
+ "Content-Type": "application/json",
+ "Accept": "application/json, text/event-stream",
+ },
+ timeout: 5,
+ sseReadTimeout: 300
+ }),
+ ],
+ });
```
+
+---
-3. **Interact in the UI**:
- * Select the `travel_planner_agent`.
- * Try prompts like:
- * "I will be in San Francisco tomorrow. What’s the weather like?"
- * "Find coffee shops near Golden Gate Park."
- * "Get directions from GooglePlex to SFO."
-
-You should see the agent use the Google Maps Grounding Lite MCP tools to provide directions or location-based information.
+### Agent-Exposed MCP Server (to_mcp_server)
-
+You can make ADK capabilities accessible to external MCP clients, such as Claude Desktop, IDEs, or custom hosts, in two ways:
-For Java, refer to the following sample to define an agent that initializes the `McpToolset`:
+1. **Expose an entire Agent (`to_mcp_server`)**: One-line conversion that exposes full multi-turn agent reasoning and internal tool execution to external MCP clients.
+2. **Expose individual Tools (`FunctionTool`)**: Manually build a lightweight MCP server that wraps specific standalone ADK tools.
-```java
-package agents;
+---
-import com.google.adk.agents.LlmAgent;
-import com.google.adk.runner.InMemoryRunner;
-import com.google.adk.sessions.SessionKey;
-import com.google.adk.tools.mcp.McpToolset;
-import com.google.adk.tools.mcp.StdioServerParameters;
-import com.google.genai.types.Content;
-import com.google.genai.types.Part;
+#### Expose an entire ADK Agent (`to_mcp_server`)
+Use ADK's native `to_mcp_server()` utility to wrap an existing `LlmAgent` into a standard FastMCP server:
-import java.util.HashMap;
-import java.util.Map;
-
-public class MapsAgentCreator {
-
- /**
- * Initializes an McpToolset for Google Maps Grounding Lite,
- * creates an LlmAgent, sends a map-related prompt, and closes the toolset.
- */
- public static void main(String[] args) {
- // Read from environment variables
- String googleMapsApiKey = System.getenv("GOOGLE_MAPS_API_KEY");
-
- if (googleMapsApiKey == null || googleMapsApiKey.trim().isEmpty()) {
- // Fallback or direct assignment for testing - NOT RECOMMENDED FOR PRODUCTION
- googleMapsApiKey = "YOUR_GOOGLE_MAPS_API_KEY_HERE"; // Replace if not using env var
- if ("YOUR_GOOGLE_MAPS_API_KEY_HERE".equals(googleMapsApiKey)) {
- System.out.println("WARNING: GOOGLE_MAPS_API_KEY is not set. Please set it as an environment variable or in the script.");
- }
- }
-
- // Setup the headers for the remote MCP connection
- Map headers = new HashMap<>();
- headers.put("X-Goog-Api-Key", googleMapsApiKey);
- headers.put("Content-Type", "application/json");
- headers.put("Accept", "application/json, text/event-stream");
-
- // Use StreamableHttpServerParameters for the remote HTTP MCP server connection
- StreamableHttpServerParameters serverParams = StreamableHttpServerParameters.builder("https://mapstools.googleapis.com/mcp")
- .headers(headers)
- .build();
-
- try (McpToolset toolset = new McpToolset(serverParams)) {
- // Build the Agent with the configured Toolset
- LlmAgent agent = LlmAgent.builder()
- .model("gemini-flash-latest")
- .name("travel_planner_agent")
- .description("A helpful assistant for planning travel routes.")
- .tools(toolset)
- .build();
-
- System.out.println("Agent created: " + agent.name());
-
- // Set up the runner and session
- InMemoryRunner runner = new InMemoryRunner(agent);
- String userId = "maps-user-" + System.currentTimeMillis();
- String sessionId = "maps-session-" + System.currentTimeMillis();
-
- String promptText = "Please give me directions to the nearest pharmacy to Madison Square Garden.";
-
- // Explicitly create the session first
- SessionKey sessionKey = runner.sessionService().createSession(runner.appName(), userId, null, sessionId).blockingGet().sessionKey();
- System.out.println("Session created: " + sessionId + " for user: " + userId);
-
- Content promptContent = Content.fromParts(Part.fromText(promptText));
-
- System.out.println("\nSending prompt: \"" + promptText + "\" to agent...\n");
-
- // Execute the prompt asynchronously and print the streamed events
- runner.runAsync(sessionKey, promptContent)
- .blockingForEach(event -> {
- System.out.println("Event received: " + event.toJson());
- });
- } catch (Exception e) {
- System.err.println("An error occurred: " + e.getMessage());
- e.printStackTrace();
- }
- }
-}
+```python
+from google.adk.agents import LlmAgent
+from google.adk.tools.load_web_page import load_web_page
+from google.adk.tools.mcp_tool import to_mcp_server
+# Define your ADK agent
+agent = LlmAgent(
+ model="gemini-flash-latest",
+ name="web_reader_agent",
+ instruction="Fetch and summarize web content for the user.",
+ tools=[load_web_page],
+)
+# Convert the agent into an MCP server
+app = to_mcp_server(agent)
+if __name__ == "__main__":
+ # Runs the agent as a standard stdio MCP server
+ app.run()
```
-For TypeScript, refer to the following sample to define an agent that initializes the `MCPToolset`:
+---
-```typescript
-import 'dotenv/config';
-import {LlmAgent, MCPToolset} from "@google/adk";
-
-// Retrieve the API key from an environment variable.
-// Ensure this environment variable is set in the terminal where you run 'adk web'.
-// Example: export GOOGLE_MAPS_API_KEY="YOUR_ACTUAL_KEY"
-const googleMapsApiKey = process.env.GOOGLE_MAPS_API_KEY;
-if (!googleMapsApiKey) {
- console.warn("WARNING: GOOGLE_MAPS_API_KEY is not set.");
- // We throw an error here to prevent the agent from booting without its crucial grounding key
- throw new Error('GOOGLE_MAPS_API_KEY is not provided, please run "export GOOGLE_MAPS_API_KEY=YOUR_ACTUAL_KEY" to add that.');
-}
-
-export const rootAgent = new LlmAgent({
- model: "gemini-flash-latest",
- name: "travel_planner_agent",
- description: "A helpful assistant for planning travel.",
- tools: [
- new MCPToolset({
- // Using SseConnectionParams to connect to the remote Grounding Lite service,
- // mirroring Python's StreamableHTTPConnectionParams.
- type: "SseConnectionParams",
- url: "https://mapstools.googleapis.com/mcp",
- headers: {
- "X-Goog-Api-Key": googleMapsApiKey,
- "Content-Type": "application/json",
- "Accept": "application/json, text/event-stream"
- }
- })
- ],
-});
-```
+#### Expose individual Tools
-## 2. Build an MCP server with ADK tools (MCP server exposing ADK)
+If you only want to expose individual ADK tools without the full agent reasoning loop, wrap FunctionTool inside an MCP Server:
-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.
+#### Prerequisites
-### Summary of steps
+Install the MCP Server library in the same environment as your ADK installation:
-You will create a standard Python MCP server application using the `mcp` library. Within this server, you will:
+ ```bash
+ pip install mcp
+ ```
-1. Instantiate the ADK tool(s) you want to expose (e.g., `FunctionTool(load_web_page)`).
-2. 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 the `adk_to_mcp_tool_type` utility from `google.adk.tools.mcp_tool.conversion_utils`.
-3. 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`).
+### Specialized Sub-Agent Delegation (AgentTool)
-### Prerequisites
+The `AgentTool` pattern involves wrapping an MCP Server inside a dedicated sub-agent, and then providing that sub-agent to your main agent as a tool.
-Install the MCP server library in the same Python environment as your ADK installation:
+While powerful, **this is the least recommended pattern for standard implementations** due to the added complexity and performance overhead. It should generally be reserved for advanced, multi-agent architectures.
-```shell
-pip install mcp
-```
+#### Architectural Trade-offs
+Before choosing this pattern, consider the following drawbacks:
+* **Increased Latency & Cost:** Every time the parent agent needs to use an MCP tool, it must generate a prompt to the sub-agent. The sub-agent then executes the tool call and summarizes the result back. This "double-hop" adds significant latency and increases token consumption.
+* **Context Fragmentation:** The sub-agent only knows what the parent agent explicitly tells it. If the parent agent fails to pass relevant conversational context, the sub-agent might fail to execute the MCP tool correctly.
+* **Prompting Complexity:** You must carefully craft instructions for *both* agents—teaching the parent when to delegate, and teaching the sub-agent how to utilize the MCP server.
-### Step 1: Create the MCP Server Script
+#### When to use this pattern
+Despite its drawbacks, delegating MCP servers to a sub-agent is highly effective in a few specific scenarios:
-Create a new Python file for your MCP server, for example, `my_adk_mcp_server.py`.
+1. **Tool Overload (Cognitive Routing):** If your main agent already has dozens of tools, adding an entire MCP server's toolkit might overwhelm the LLM's cognitive capacity or exceed context limits. Hiding the MCP server behind a single `Database_Agent` or `DevOps_Agent` tool simplifies the parent's decision-making.
+2. **Multi-Step Reasoning Loops:** If using the MCP server requires autonomous trial-and-error (e.g., querying a database, getting an error, and rewriting the SQL), a sub-agent can handle that isolated reasoning loop without cluttering the parent agent's context window.
+3. **Security and Sandboxing:** If the MCP server exposes sensitive operations, placing it behind a sub-agent with a highly restrictive system instruction ensures the main agent (which interacts directly with unpredictable user input) cannot easily manipulate the tools.
-### Step 2: Implement the Server Logic
+#### Implementation Overview
+To implement this pattern, you combine `McpToolset` and `AgentTool`:
+1. Initialize your MCP connection (e.g., HTTP or Stdio).
+2. Create a specific `LlmAgent` (the sub-agent) and attach the `McpToolset` to it.
+3. Wrap that sub-agent in an `AgentTool`.
+4. Provide the `AgentTool` to your root agent.
+
-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.
+### Build the MCP server with ADK tools
+
+1. Create a new Python file for your MCP server, for example: `my_adk_mcp_server.py`.
+2. Implement server logic with the following code to your new file. This following script sets up an MCP server that exposes the ADK `load_web_page` tool.
```python
-# 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 import types as mcp_types
from mcp.server.lowlevel import Server, NotificationOptions
from mcp.server.models import InitializationOptions
-import mcp.server.stdio # For running as a stdio server
+import mcp.server.stdio
-# 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.load_web_page import load_web_page
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
+# Load environment variables (e.g., API keys required by ADK tools)
+load_dotenv()
-# --- 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
+ """List tools exposed by this server."""
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
+async def call_mcp_tool(name: str, arguments: dict) -> list[mcp_types.Content]:
+ """Execute a tool call requested by an MCP client."""
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.
+ # Note: tool_context is None because the ADK tool runs outside a full ADK Runner.
+ # Tools requiring ToolContext features (like state or auth) need custom handling here.
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.
+ # Serialize the ADK tool's response into MCP's expected Content format
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
+ """Run the MCP server over standard input/output."""
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_name=app.name,
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
+### 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.
+To test your custom server, you need to build an ADK agent that acts as a client. This agent uses the `McpToolset` to establish a connection to the server script you just created.
-Create an `agent.py` (e.g., in `./adk_agent_samples/mcp_client_agent/agent.py`):
+1. Set up your agent in a new directory such as `./adk_agent_samples/mcp_client_agent/`. Create an `agent.py` file and include an `__init__.py` alongside it to make it discoverable.
```python
-# ./adk_agent_samples/mcp_client_agent/agent.py
-import os
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
-# 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 PATH_TO_YOUR_MCP_SERVER_SCRIPT == "/path/to/your/my_adk_mcp_server.py":
- print("WARNING: PATH_TO_YOUR_MCP_SERVER_SCRIPT is not set. Please update it in agent.py.")
- # Optionally, raise an error if the path is critical
+# IMPORTANT: Provide the absolute path to the server script you built previously
+MCP_SERVER_SCRIPT = "/path/to/your/my_adk_mcp_server.py"
root_agent = LlmAgent(
model='gemini-flash-latest',
@@ -694,585 +445,238 @@ root_agent = LlmAgent(
tools=[
McpToolset(
connection_params=StdioConnectionParams(
- server_params = StdioServerParameters(
- command='python3', # Command to run your MCP server script
- args=[PATH_TO_YOUR_MCP_SERVER_SCRIPT], # Argument is the path to the script
+ server_params=StdioServerParameters(
+ command='python3',
+ args=[MCP_SERVER_SCRIPT],
)
)
- # tool_filter=['load_web_page'] # Optional: ensure only specific tools are loaded
)
],
)
```
-And an `__init__.py` in the same directory:
-```python
-# ./adk_agent_samples/mcp_client_agent/__init__.py
-from . import agent
-```
-
-**To run the test:**
+2. Navigate to your agent's parent directory in the terminal:
-1. **Start your custom MCP server (optional, for separate observation):**
- You can run your `my_adk_mcp_server.py` directly in one terminal to see its logs:
- ```shell
- python3 /path/to/your/my_adk_mcp_server.py
- ```
- It will print "Launching MCP Server..." and wait. The ADK agent (run via `adk web`) will then connect to this process if the `command` in `StdioConnectionParams` is set up to execute it.
- *(Alternatively, `McpToolset` will start this server script as a subprocess automatically when the agent initializes).*
-
-2. **Run `adk web` for the client agent:**
- Navigate to the parent directory of `mcp_client_agent` (e.g., `adk_agent_samples`) and run:
- ```shell
- cd ./adk_agent_samples # Or your equivalent parent directory
- adk web
- ```
-
-3. **Interact in the ADK Web UI:**
- * Select the `web_reader_mcp_client_agent`.
- * Try a prompt like: "Load the content from https://example.com"
-
-The ADK agent (`web_reader_mcp_client_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. The ADK agent will then relay this information. You should see logs from both the ADK Web UI (and its terminal) and potentially from your `my_adk_mcp_server.py` terminal if you ran it separately.
+```bash
+cd ./adk_agent_samples
+adk web
+```
-This example demonstrates how ADK tools can be encapsulated within an MCP server, making them accessible to a broader range of MCP-compliant clients, not just ADK agents.
+3. Open the ADK Web UI and select the web_reader_mcp_client_agent.
+4. Test the connection with a prompt such as: *Load the content from "https://example.com"*.
-Refer to the [documentation](https://modelcontextprotocol.io/quickstart/server#core-mcp-concepts), to try it out with Claude Desktop.
+---
-## Advanced use cases
+## Remote MCP Authentication and resource access
-The following sections describe how to handle more advanced use cases with MCP Tools in agents.
+This section shows you how to connect to remote MCP servers using authentication and how to read data **Resources** exposed by an MCP server. When an MCP server requires authentication, such as over Server-Sent Events `SseConnectionParams` or Streamable HTTP, `McpToolset` handles credential injection and token management automatically.
-### Use MCP Tools without `adk web`
+### Key authentication parameters
-This section is relevant to you if:
+| Parameter | Type | Description |
+| :--- | :--- | :--- |
+| `auth_scheme` | `AuthScheme` | The authentication strategy (e.g., `Bearer`, `Basic`, `APIKey`, `OAuth2`). |
+| `auth_credential` | `AuthCredential` | The secret credential payload, for example, API token, OAuth access token, username/password. |
-* You are developing your own Agent using ADK
-* And, you are **NOT** using `adk web`,
-* And, you are exposing the agent via your own UI
+ADK automatically constructs the required `Authorization` HTTP headers and manages OAuth 2.0 token refreshes during client requests.
+### Configure authentication
-Using MCP Tools requires a different setup than using regular tools, due to the fact that specs for MCP Tools are fetched asynchronously
-from the MCP Server running remotely, or in another process.
+When an MCP server requires authentication, `McpToolset` handles credential injection and token management automatically. Use the native `auth_scheme` and `auth_credential` parameters rather than manually injecting HTTP headers.
-The following example is modified from the "Example 1: File System MCP Server" example above. The main differences are:
+*For general ADK authentication patterns, see our [Custom Tools Authentication Guide](./authentication.md)*
-1. Your tool and agent are created asynchronously
-2. You need to properly manage the exit stack, so that your agents and tools are destructed properly when the connection to MCP Server is closed.
+=== "Python"
```python
-# agent.py (modify get_tools_async and other parts as needed)
-# ./adk_agent_samples/mcp_agent/agent.py
import os
-import asyncio
-from dotenv import load_dotenv
-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.artifacts.in_memory_artifact_service import InMemoryArtifactService # Optional
from google.adk.tools.mcp_tool import McpToolset
-from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
-from mcp import StdioServerParameters
+from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams
-# Load environment variables from .env file in the parent directory
-# Place this near the top, before using env vars like API keys
-load_dotenv('../.env')
+# Configure Bearer Token Authentication via headers
+toolset = McpToolset(
+ connection_params=SseConnectionParams(
+ url="https://mcp-server.example.com/sse",
+ headers={"Authorization": f"Bearer {os.getenv('MCP_AUTH_TOKEN')}"},
+ timeout=5,
+ )
+)
+```
-# Ensure TARGET_FOLDER_PATH is an absolute path for the MCP server.
-TARGET_FOLDER_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "/path/to/your/folder")
+=== "TypeScript"
-# --- Step 1: Agent Definition ---
-async def get_agent_async():
- """Creates an ADK Agent equipped with tools from the MCP Server."""
- toolset = McpToolset(
- # Use StdioConnectionParams for local process communication
- connection_params=StdioConnectionParams(
- server_params = StdioServerParameters(
- command='npx', # Command to run the server
- args=["-y", # Arguments for the command
- "@modelcontextprotocol/server-filesystem",
- TARGET_FOLDER_PATH],
- ),
- ),
- tool_filter=['read_file', 'list_directory'] # Optional: filter specific tools
- # For remote servers, you would use SseConnectionParams instead:
- # connection_params=SseConnectionParams(url="http://remote-server:port/path", headers={...})
- )
+```typescript
+import { MCPToolset } from "@google/adk";
+
+// Configure Bearer Token Authentication via headers
+const toolset = new MCPToolset({
+ type: "SseConnectionParams",
+ url: "https://mcp-server.example.com/sse",
+ headers: {
+ "Authorization": `Bearer ${process.env.MCP_AUTH_TOKEN}`,
+ },
+ timeout: 5,
+});
+```
- # Use in an agent
- root_agent = LlmAgent(
- model='gemini-flash-latest', # Adjust model name if needed based on availability
- name='enterprise_assistant',
- instruction='Help user accessing their file systems',
- tools=[toolset], # Provide the MCP tools to the ADK agent
- )
- return root_agent, toolset
+---
-# --- Step 2: Main Execution Logic ---
-async def async_main():
- session_service = InMemorySessionService()
- # Artifact service might not be needed for this example
- artifacts_service = InMemoryArtifactService()
+## Accessing MCP Resources
- session = await session_service.create_session(
- state={}, app_name='mcp_filesystem_app', user_id='user_fs'
- )
+In addition to executable **Tools**, MCP servers can expose **Resources** data files, database records, or API context blobs.
- # TODO: Change the query to be relevant to YOUR specified folder.
- # e.g., "list files in the 'documents' subfolder" or "read the file 'notes.txt'"
- query = "list files in the tests folder"
- print(f"User Query: '{query}'")
- content = types.Content(role='user', parts=[types.Part(text=query)])
+`McpToolset` provides two core methods to discover and read these data resources:
- root_agent, toolset = await get_agent_async()
+### Core Methods
- runner = Runner(
- app_name='mcp_filesystem_app',
- agent=root_agent,
- artifact_service=artifacts_service, # Optional
- session_service=session_service,
- )
+* **`list_resources()`**: Returns a list of all available data resources exposed by the MCP server.
+* **`read_resource(name)`**: Retrieves the raw content blocks, text or binary data, for a specific resource by its name or URI.
- print("Running agent...")
- events_async = runner.run_async(
- session_id=session.id, user_id=session.user_id, new_message=content
- )
+### Try it out
- async for event in events_async:
- print(f"Event received: {event}")
+=== "Python"
- # Cleanup is handled automatically by the agent framework
- # But you can also manually close if needed:
- print("Closing MCP server connection...")
- await toolset.close()
- print("Cleanup complete.")
+ ```python
+ import asyncio
-if __name__ == '__main__':
- try:
- asyncio.run(async_main())
- except Exception as e:
- print(f"An error occurred: {e}")
-```
+ async def fetch_mcp_data(toolset):
+ # 1. Discover available resources on the server
+ resources = await toolset.list_resources()
+ print("Available Resources:", resources)
-### Handling progress updates
+ # 2. Read content from a specific resource
+ if resources:
+ resource_name = resources[0]
+ content_blocks = await toolset.read_resource(name=resource_name)
+ print(f"Content of {resource_name}:", content_blocks)
+ ```
-For long-running tools, `McpToolset` supports a `progress_callback`. This approach allows you to receive real-time updates from the MCP server. You can provide a simple callback function or a factory that creates callbacks with access to the runtime context, such as updating session state.
+=== "TypeScript"
-```python
-async def my_progress_callback(progress: float, total: float, message: str):
- print(f"Progress: {progress}/{total} - {message}")
+ ```typescript
+ async function fetchMcpData(toolset: any) {
+ // 1. Discover available resources
+ const resources = await toolset.listResources();
+ console.log("Available Resources:", resources);
-toolset = McpToolset(
- connection_params=...,
- progress_callback=my_progress_callback
-)
-```
+ // 2. Read a specific resource
+ if (resources.length > 0) {
+ const content = await toolset.readResource(resources[0]);
+ console.log(`Content of ${resources[0]}:`, content);
+ }
+ }
+ ```
-## Deploy Agents with MCP Tools
+---
-When deploying ADK agents that use MCP tools to production environments like Cloud Run, GKE, or Agent Runtime, you need to consider how MCP connections will work in containerized and distributed environments.
+## Troubleshooting & Best Practices Checklist
-### Critical Deployment Requirement: Synchronous Agent Definition
+* **Security & Scoping**: Always supply `tool_filter=[...]` in `McpToolset` to expose only necessary actions to your LLM.
+* **Timeouts**: Configure explicit timeouts on `StdioConnectionParams(timeout=5)` to prevent hanging subprocesses.
+* **Lifecycle Cleanup**: In non-`adk web` runners, invoke `await toolset.close()` or use async context managers to gracefully shutdown subprocesses.
+* **Environment Detection**: Dynamically pick connection types based on environment variables, for example: `K_SERVICE` for Cloud Run vs Stdio for local dev.
-**⚠️ Important:** When deploying agents with MCP tools, the agent and its McpToolset must be defined **synchronously** in your `agent.py` file. While `adk web` allows for asynchronous agent creation, deployment environments require synchronous instantiation.
+### Configure the environment-aware connection
```python
-# ✅ CORRECT: Synchronous agent definition for deployment
import os
-from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool import McpToolset
-from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
-from mcp import StdioServerParameters
-
-_allowed_path = os.path.dirname(os.path.abspath(__file__))
-
-root_agent = LlmAgent(
- model='gemini-flash-latest',
- name='enterprise_assistant',
- instruction=f'Help user accessing their file systems. Allowed directory: {_allowed_path}',
- tools=[
- McpToolset(
- connection_params=StdioConnectionParams(
- server_params=StdioServerParameters(
- command='npx',
- args=['-y', '@modelcontextprotocol/server-filesystem', _allowed_path],
- ),
- timeout=5, # Configure appropriate timeouts
- ),
- # Filter tools for security in production
- tool_filter=[
- 'read_file', 'read_multiple_files', 'list_directory',
- 'directory_tree', 'search_files', 'get_file_info',
- 'list_allowed_directories',
- ],
- )
- ],
+from google.adk.tools.mcp_tool.mcp_session_manager import (
+ StdioConnectionParams,
+ StreamableHTTPConnectionParams,
)
-```
-
-```python
-# ❌ WRONG: Asynchronous patterns don't work in deployment
-async def get_agent(): # This won't work for deployment
- toolset = await create_mcp_toolset_async()
- return LlmAgent(tools=[toolset])
-```
-
-### Quick Deployment Commands
+from mcp import StdioServerParameters
-#### Agent Runtime
-```bash
-uv run adk deploy agent_engine \
- --project= \
- --region= \
- --staging_bucket="gs://" \
- --display_name="My MCP Agent" \
- ./path/to/your/agent_directory
-```
+if os.getenv("K_SERVICE"):
+ # Running in Production (Cloud Run)
+ # Uses Streamable HTTP for stateless scalability and bearer auth headers
+ mcp_toolset = McpToolset(
+ connection_params=StreamableHTTPConnectionParams(
+ url=os.getenv("REMOTE_MCP_URL"),
+ headers={"Authorization": f"Bearer {os.getenv('MCP_AUTH_TOKEN')}"},
+ timeout=5,
+ sse_read_timeout=300,
+ )
+ )
+else:
+ # Running in Local Development
+ # Uses Stdio Subprocess IPC for zero-network latency testing
+ mcp_toolset = McpToolset(
+ connection_params=StdioConnectionParams(
+ server_params=StdioServerParameters(
+ command="npx",
+ args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"],
+ ),
+ timeout=5,
+ )
+ )
-#### Cloud Run
-```bash
-uv run adk deploy cloud_run \
- --project= \
- --region= \
- --service_name= \
- ./path/to/your/agent_directory
```
-### Deployment Patterns
-
-#### Pattern 1: Self-Contained Stdio MCP Servers
-
-For MCP servers that can be packaged as npm packages or Python modules (like `@modelcontextprotocol/server-filesystem`), you can include them directly in your agent container:
+## UI Rendering
-**Container Requirements:**
-```dockerfile
-# Example for npm-based MCP servers
-FROM python:3.13-slim
+Standard MCP tools return plain text or JSON output. **Experimental UI Rendering** enables MCP tools to return rich, interactive visual widgets, such as maps, charts, or forms, directly inside the chat interface.
-# Install Node.js and npm for MCP servers
-RUN apt-get update && apt-get install -y nodejs npm && rm -rf /var/lib/apt/lists/*
+```mermaid
+sequenceDiagram
+ autonumber
+ participant Tool as MCP Server Tool
+ participant ADK as ADK Framework
+ participant UI as Client UI (adk web / Frontend)
-# Install your Python dependencies
-COPY requirements.txt .
-RUN pip install -r requirements.txt
-
-# Copy your agent code
-COPY . .
-
-# Your agent can now use StdioConnectionParams with 'npx' commands
-CMD ["python", "main.py"]
+ Tool-->>ADK: Returns result + metadata (meta.ui.resourceUri = "ui://widgets/map")
+ ADK->>ADK: Detects meta.ui.resourceUri annotation
+ ADK-->>UI: Emits Event with UI rendering signal & Resource URI
+ UI->>Tool: Fetches UI bundle from Resource URI
+ UI-->>UI: Renders interactive widget in chat interface
```
-**Agent Configuration:**
-```python
-# This works in containers because npx and the MCP server run in the same environment
-McpToolset(
- connection_params=StdioConnectionParams(
- server_params=StdioServerParameters(
- command='npx',
- args=["-y", "@modelcontextprotocol/server-filesystem", "/app/data"],
- ),
- ),
-)
-```
+### How It Works
-#### Pattern 2: Remote MCP Servers (Streamable HTTP)
+1. **Tool Registration**: The MCP tool declares a UI resource link in its schema definition metadata during `tools/list`: `_meta.ui.resourceUri = "ui://widgets/weather-card"`.
+2. **ADK Detection**: ADK reads the schema definition to detect `_meta.ui.resourceUri` and knows this tool supports an interactive UI.
+3. **Client Display**: Upon tool execution, ADK signals the web UI (`adk web` or custom frontend) to fetch the UI resource and render an interactive widget instead of plain text.
-For production deployments requiring scalability, deploy MCP servers as separate services and connect via Streamable HTTP:
-
-**MCP Server Deployment (Cloud Run):**
```python
-# deploy_mcp_server.py - Separate Cloud Run service using Streamable HTTP
-import contextlib
-import logging
-from collections.abc import AsyncIterator
-from typing import Any
-
-import anyio
-import click
-import mcp.types as types
+from mcp import types as mcp_types
from mcp.server.lowlevel import Server
-from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
-from starlette.applications import Starlette
-from starlette.routing import Mount
-from starlette.types import Receive, Scope, Send
-
-logger = logging.getLogger(__name__)
-
-def create_mcp_server():
- """Create and configure the MCP server."""
- app = Server("adk-mcp-streamable-server")
-
- @app.call_tool()
- async def call_tool(name: str, arguments: dict[str, Any]) -> list[types.ContentBlock]:
- """Handle tool calls from MCP clients."""
- # Example tool implementation - replace with your actual ADK tools
- if name == "example_tool":
- result = arguments.get("input", "No input provided")
- return [
- types.TextContent(
- type="text",
- text=f"Processed: {result}"
- )
- ]
- else:
- raise ValueError(f"Unknown tool: {name}")
-
- @app.list_tools()
- async def list_tools() -> list[types.Tool]:
- """List available tools."""
- return [
- types.Tool(
- name="example_tool",
- description="Example tool for demonstration",
- inputSchema={
- "type": "object",
- "properties": {
- "input": {
- "type": "string",
- "description": "Input text to process"
- }
- },
- "required": ["input"]
- }
- )
- ]
-
- return app
-
-def main(port: int = 8080, json_response: bool = False):
- """Main server function."""
- logging.basicConfig(level=logging.INFO)
-
- app = create_mcp_server()
-
- # Create session manager with stateless mode for scalability
- session_manager = StreamableHTTPSessionManager(
- app=app,
- event_store=None,
- json_response=json_response,
- stateless=True, # Important for Cloud Run scalability
- )
-
- async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> None:
- await session_manager.handle_request(scope, receive, send)
-
- @contextlib.asynccontextmanager
- async def lifespan(app: Starlette) -> AsyncIterator[None]:
- """Manage session manager lifecycle."""
- async with session_manager.run():
- logger.info("MCP Streamable HTTP server started!")
- try:
- yield
- finally:
- logger.info("MCP server shutting down...")
-
- # Create ASGI application
- starlette_app = Starlette(
- debug=False, # Set to False for production
- routes=[
- Mount("/mcp", app=handle_streamable_http),
- ],
- lifespan=lifespan,
- )
-
- import uvicorn
- uvicorn.run(starlette_app, host="0.0.0.0", port=port)
-
-if __name__ == "__main__":
- main()
-```
-
-**Agent Configuration for Remote MCP:**
-
-=== "Python"
-
- ```python
- # Your ADK agent connects to the remote MCP service via Streamable HTTP
- McpToolset(
- connection_params=StreamableHTTPConnectionParams(
- url="https://your-mcp-server-url.run.app/mcp",
- headers={"Authorization": "Bearer your-auth-token"}
- ),
- )
- ```
-
-=== "Java"
-
- ```java
- import java.util.Map;
- import com.google.adk.tools.mcp.StreamableHttpServerParameters;
- import com.google.adk.tools.mcp.McpToolset;
-
- // Your ADK agent connects to the remote MCP service via Streamable HTTP
- StreamableHttpServerParameters streamableParams = StreamableHttpServerParameters.builder()
- .url("https://your-mcp-server-url.run.app/mcp")
- .headers(Map.of("Authorization", "Bearer your-auth-token"))
- .build();
-
- McpToolset toolset = new McpToolset(streamableParams);
- ```
-
-#### Pattern 3: Sidecar MCP Servers (GKE)
-
-In Kubernetes environments, you can deploy MCP servers as sidecar containers:
-
-```yaml
-# deployment.yaml - GKE with MCP sidecar
-apiVersion: apps/v1
-kind: Deployment
-metadata:
- name: adk-agent-with-mcp
-spec:
- template:
- spec:
- containers:
- # Main ADK agent container
- - name: adk-agent
- image: your-adk-agent:latest
- ports:
- - containerPort: 8080
- env:
- - name: MCP_SERVER_URL
- value: "http://localhost:8081"
-
- # MCP server sidecar
- - name: mcp-server
- image: your-mcp-server:latest
- ports:
- - containerPort: 8081
-```
-
-### Connection Management Considerations
-
-#### Stdio Connections
-- **Pros:** Simple setup, process isolation, works well in containers
-- **Cons:** Process overhead, not suitable for high-scale deployments
-- **Best for:** Development, single-tenant deployments, simple MCP servers
-
-#### SSE/HTTP Connections
-- **Pros:** Network-based, scalable, can handle multiple clients
-- **Cons:** Requires network infrastructure, authentication complexity
-- **Best for:** Production deployments, multi-tenant systems, external MCP services
-
-### Production Deployment Checklist
-
-When deploying agents with MCP tools to production:
-
-**✅ Connection Lifecycle**
-- Ensure proper cleanup of MCP connections using exit_stack patterns
-- Configure appropriate timeouts for connection establishment and requests
-- Implement retry logic for transient connection failures
-
-**✅ Resource Management**
-- Monitor memory usage for stdio MCP servers (each spawns a process)
-- Configure appropriate CPU/memory limits for MCP server processes
-- Consider connection pooling for remote MCP servers
-
-**✅ Security**
-- Use authentication headers for remote MCP connections
-- Restrict network access between ADK agents and MCP servers
-- **Filter MCP tools using `tool_filter` to limit exposed functionality**
-- Validate MCP tool inputs to prevent injection attacks
-- Use restrictive file paths for filesystem MCP servers (e.g., `os.path.dirname(os.path.abspath(__file__))`)
-- Consider read-only tool filters for production environments
-**✅ Monitoring & Observability**
-- Log MCP connection establishment and teardown events
-- Monitor MCP tool execution times and success rates
-- Set up alerts for MCP connection failures
+app = Server("weather-mcp-server")
-**✅ Scalability**
-- For high-volume deployments, prefer remote MCP servers over stdio
-- Configure session affinity if using stateful MCP servers
-- Consider MCP server connection limits and implement circuit breakers
-### Environment-Specific Configurations
+@app.list_tools()
+async def list_mcp_tools() -> list[mcp_types.Tool]:
+ """Declares the tool and attaches UI rendering metadata."""
+ return [
+ mcp_types.Tool(
+ name="get_weather",
+ description="Get weather forecast for a city.",
+ inputSchema={
+ "type": "object",
+ "properties": {"city": {"type": "string"}},
+ "required": ["city"],
+ },
+ meta={"ui": {"resourceUri": "ui://widgets/weather-card"}},
+ )
+ ]
-#### Cloud Run
-```python
-# Cloud Run environment variables for MCP configuration
-import os
-# Detect Cloud Run environment
-if os.getenv('K_SERVICE'):
- # Use remote MCP servers in Cloud Run
- mcp_connection = SseConnectionParams(
- url=os.getenv('MCP_SERVER_URL'),
- headers={'Authorization': f"Bearer {os.getenv('MCP_AUTH_TOKEN')}"}
- )
-else:
- # Use stdio for local development
- mcp_connection = StdioConnectionParams(
- server_params=StdioServerParameters(
- command='npx',
- args=["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
+@app.call_tool()
+async def call_mcp_tool(name: str, arguments: dict) -> list[mcp_types.Content]:
+ """Executes the tool and returns standard text/data content."""
+ if name == "get_weather":
+ city = arguments.get("city", "Unknown")
+ return [
+ mcp_types.TextContent(
+ type="text", text=f"Weather in {city}: 72°F Sunny"
)
- )
-
-McpToolset(connection_params=mcp_connection)
-```
+ ]
+ return [
+ mcp_types.TextContent(type="text", text=f"Unknown tool: '{name}'")
+ ]
-#### GKE
-```python
-# GKE-specific MCP configuration
-# Use service discovery for MCP servers within the cluster
-McpToolset(
- connection_params=SseConnectionParams(
- url="http://mcp-service.default.svc.cluster.local:8080/sse"
- ),
-)
```
-#### Agent Runtime
-```python
-# Agent Runtime managed deployment
-# Prefer lightweight, self-contained MCP servers or external services
-McpToolset(
- connection_params=SseConnectionParams(
- url="https://your-managed-mcp-service.googleapis.com/sse",
- headers={'Authorization': 'Bearer $(gcloud auth print-access-token)'}
- ),
-)
-```
+## Further resources
-### Troubleshooting Deployment Issues
-
-**Common MCP Deployment Problems:**
-
-1. **Stdio Process Startup Failures**
- ```python
- # Debug stdio connection issues
- McpToolset(
- connection_params=StdioConnectionParams(
- server_params=StdioServerParameters(
- command='npx',
- args=["-y", "@modelcontextprotocol/server-filesystem", "/app/data"],
- # Add environment debugging
- env={'DEBUG': '1'}
- ),
- ),
- )
- ```
-
-2. **Network Connectivity Issues**
- ```python
- # Test remote MCP connectivity
- import aiohttp
-
- async def test_mcp_connection():
- async with aiohttp.ClientSession() as session:
- async with session.get('https://your-mcp-server.com/health') as resp:
- print(f"MCP Server Health: {resp.status}")
- ```
-
-3. **Resource Exhaustion**
- - Monitor container memory usage when using stdio MCP servers
- - Set appropriate limits in Kubernetes deployments
- - Use remote MCP servers for resource-intensive operations
-
-## Further Resources
-
-* [Model Context Protocol Documentation](https://modelcontextprotocol.io/ )
-* [MCP Specification](https://modelcontextprotocol.io/specification/)
-* [MCP Python SDK & Examples](https://github.com/modelcontextprotocol/)
+Once you understand the basics, explore [Advanced use cases](/mcp-tools-advanced) for complex implementations and custom integrations.