chore: import upstream snapshot with attribution
Continuous Integration / Pre-commit Linter (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.10) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.11) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.12) (push) Has been cancelled
Continuous Integration / Mypy Check (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.12) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / Unit Tests (Python 3.14) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.10) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.11) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.12) (push) Has been cancelled
Copybara PR Handler / close-imported-pr (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.13) (push) Has been cancelled
Continuous Integration / A2A v0.3 Tests (Python 3.14) (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:25:13 +08:00
commit ec2b666284
2231 changed files with 491535 additions and 0 deletions
@@ -0,0 +1,8 @@
This agent connects to a local MCP server via Streamable HTTP and provides
custom per-request headers to the MCP server.
To run this agent, start the local MCP server first by running:
```bash
uv run header_server.py
```
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,33 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
root_agent = LlmAgent(
name='tenant_agent',
instruction="""You are a helpful assistant that helps users get tenant
information. Call the get_tenant_data tool when the user asks for tenant data.""",
tools=[
McpToolset(
connection_params=StreamableHTTPConnectionParams(
url='http://localhost:3000/mcp',
),
tool_filter=['get_tenant_data'],
header_provider=lambda ctx: {'X-Tenant-ID': 'tenant1'},
)
],
)
@@ -0,0 +1,50 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import annotations
from fastapi import Request
from mcp.server.fastmcp import Context
from mcp.server.fastmcp import FastMCP
mcp = FastMCP('Header Check Server', host='localhost', port=3000)
TENANT_DATA = {
'tenant1': {'name': 'Tenant 1', 'data': 'Data for tenant 1'},
'tenant2': {'name': 'Tenant 2', 'data': 'Data for tenant 2'},
}
@mcp.tool(
description='Returns tenant specific data based on X-Tenant-ID header.'
)
def get_tenant_data(context: Context) -> dict:
"""Return tenant specific data."""
if context.request_context and context.request_context.request:
headers = context.request_context.request.headers
tenant_id = headers.get('x-tenant-id')
if tenant_id in TENANT_DATA:
return TENANT_DATA[tenant_id]
else:
return {'error': f'Tenant {tenant_id} not found'}
else:
return {'error': 'Could not get request context'}
if __name__ == '__main__':
try:
print('Starting Header Check MCP server on http://localhost:3000')
mcp.run(transport='streamable-http')
except KeyboardInterrupt:
print('\nServer stopped.')
@@ -0,0 +1,74 @@
# AgentTool with MCP Demo (SSE Mode)
This demo shows how `AgentTool` works with MCP (Model Context Protocol) toolsets using **SSE mode**.
## SSE vs Stdio Mode
This demo uses **SSE (Server-Sent Events) mode** where the MCP server runs as a separate HTTP server:
- **Remote connection** - Connects to server via HTTP
- **Separate process** - Server must be started manually
- **Network communication** - Uses HTTP/SSE for messaging
For the **stdio (subprocess) version**, see [mcp_in_agent_tool_stdio](../mcp_in_agent_tool_stdio/).
## Setup
**Start the MCP simple-tool server in SSE mode** (in a separate terminal):
```bash
# Run the server using uvx (no installation needed)
# Port 3000 avoids conflict with adk web (which uses 8000)
uvx --from 'git+https://github.com/modelcontextprotocol/python-sdk.git#subdirectory=examples/servers/simple-tool' \
mcp-simple-tool --transport sse --port 3000
```
The server should be accessible at `http://localhost:3000/sse`.
## Running the Demo
```bash
adk web contributing/samples
```
Then select **mcp_in_agent_tool_remote** from the list and interact with the agent.
## Try These Prompts
This demo uses **Gemini 2.5 Flash** as the model. Try these prompts:
1. **Check available tools:**
```
What tools do you have access to?
```
1. **Fetch and summarize JSON Schema specification:**
```
Use the mcp_helper to fetch https://json-schema.org/specification and summarize the key features of JSON Schema
```
## Architecture
```
main_agent (root_agent)
└── AgentTool wrapping:
└── mcp_helper (sub_agent)
└── McpToolset (SSE connection)
└── http://localhost:3000/sse
└── MCP simple-tool server
└── Website Fetcher Tool
```
## Related
- **Issue:** [#1112 - Using agent as tool outside of adk web doesn't exit cleanly](https://github.com/google/adk-python/issues/1112)
- **Related Issue:** [#929 - LiteLLM giving error with OpenAI models and Grafana's MCP server](https://github.com/google/adk-python/issues/929)
- **Stdio Version:** [mcp_in_agent_tool_stdio](../mcp_in_agent_tool_stdio/) - Uses local subprocess connection
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,68 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from google.adk.agents import Agent
from google.adk.tools import AgentTool
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams
# Create MCP toolset
# This uses the simple-tool MCP server via SSE
# You need to start the MCP server separately (see README.md)
mcp_toolset = McpToolset(
connection_params=SseConnectionParams(
url="http://localhost:3000/sse",
timeout=10.0,
sse_read_timeout=300.0,
)
)
# Create sub-agent with MCP tools
# This agent has direct access to MCP tools
sub_agent = Agent(
name="mcp_helper",
description=(
"A helpful assistant with access to MCP tools for fetching websites."
),
instruction="""You are a helpful assistant with access to MCP tools.
When the user asks for help:
1. Explain what tools you have available (website fetching)
2. Use the appropriate tool if needed
3. Provide clear and helpful responses
You have access to a website fetcher tool via MCP. Use it to fetch and return website content.""",
tools=[mcp_toolset],
)
# Wrap sub-agent as an AgentTool
# This allows the main agent to delegate tasks to the sub-agent
# The sub-agent has access to MCP tools for fetching websites
mcp_agent_tool = AgentTool(agent=sub_agent)
# Create main agent
# This agent can delegate to the sub-agent via AgentTool
root_agent = Agent(
name="main_agent",
description="Main agent that can delegate to a sub-agent with MCP tools.",
instruction="""You are a helpful assistant. You have access to a sub-agent (mcp_helper)
that has MCP tools for fetching websites.
When the user asks for help:
- If they need to fetch a website, call the mcp_helper tool
- Otherwise, respond directly
Always be helpful and explain what you're doing.""",
tools=[mcp_agent_tool],
)
@@ -0,0 +1,74 @@
# AgentTool with MCP Demo (Stdio Mode)
This demo shows how `AgentTool` works with MCP (Model Context Protocol) toolsets using **stdio mode**.
## Stdio vs SSE Mode
This demo uses **stdio mode** where the MCP server runs as a subprocess:
- **Simpler setup** - No need to start a separate server
- **Auto-launched** - Server starts automatically when agent runs
- **Local process** - Uses stdin/stdout for communication
For the **SSE (remote server) version**, see [mcp_in_agent_tool_remote](../mcp_in_agent_tool_remote/).
## Setup
**No installation required!** The MCP server will be launched automatically using `uvx` when you run the agent.
The demo uses `uvx` to fetch and run the MCP simple-tool server directly from the GitHub repository's subdirectory:
```bash
uvx --from 'git+https://github.com/modelcontextprotocol/python-sdk.git#subdirectory=examples/servers/simple-tool' \
mcp-simple-tool
```
This happens automatically via the stdio connection when the agent starts.
## Running the Demo
```bash
adk web contributing/samples
```
Then select **mcp_in_agent_tool_stdio** from the list and interact with the agent.
## Try These Prompts
This demo uses **Gemini 2.5 Flash** as the model. Try these prompts:
1. **Check available tools:**
```
What tools do you have access to?
```
1. **Fetch and summarize JSON Schema specification:**
```
Use the mcp_helper to fetch https://json-schema.org/specification and summarize the key features of JSON Schema
```
## Architecture
```
main_agent (root_agent)
└── AgentTool wrapping:
└── mcp_helper (sub_agent)
└── McpToolset (stdio connection)
└── MCP Server (subprocess via uvx)
└── uvx --from git+...#subdirectory=... mcp-simple-tool
└── Website Fetcher Tool
```
## Related
- **Issue:** [#1112 - Using agent as tool outside of adk web doesn't exit cleanly](https://github.com/google/adk-python/issues/1112)
- **Related Issue:** [#929 - LiteLLM giving error with OpenAI models and Grafana's MCP server](https://github.com/google/adk-python/issues/929)
- **SSE Version:** [mcp_in_agent_tool_remote](../mcp_in_agent_tool_remote/) - Uses remote server connection
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,75 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from google.adk.agents import Agent
from google.adk.tools import AgentTool
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
# Create MCP toolset
# This uses the simple-tool MCP server via stdio
# The server will be launched automatically using uvx from the subdirectory
mcp_toolset = McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="uvx",
args=[
"--from",
"git+https://github.com/modelcontextprotocol/python-sdk.git#subdirectory=examples/servers/simple-tool",
"mcp-simple-tool",
],
),
timeout=10.0,
)
)
# Create sub-agent with MCP tools
# This agent has direct access to MCP tools
sub_agent = Agent(
name="mcp_helper",
description=(
"A helpful assistant with access to MCP tools for fetching websites."
),
instruction="""You are a helpful assistant with access to MCP tools.
When the user asks for help:
1. Explain what tools you have available (website fetching)
2. Use the appropriate tool if needed
3. Provide clear and helpful responses
You have access to a website fetcher tool via MCP. Use it to fetch and return website content.""",
tools=[mcp_toolset],
)
# Wrap sub-agent as an AgentTool
# This allows the main agent to delegate tasks to the sub-agent
# The sub-agent has access to MCP tools for fetching websites
mcp_agent_tool = AgentTool(agent=sub_agent)
# Create main agent
# This agent can delegate to the sub-agent via AgentTool
root_agent = Agent(
name="main_agent",
description="Main agent that can delegate to a sub-agent with MCP tools.",
instruction="""You are a helpful assistant. You have access to a sub-agent (mcp_helper)
that has MCP tools for fetching websites.
When the user asks for help:
- If they need to fetch a website, call the mcp_helper tool
- Otherwise, respond directly
Always be helpful and explain what you're doing.""",
tools=[mcp_agent_tool],
)
@@ -0,0 +1,69 @@
# PostgreSQL MCP Agent
This agent uses the PostgreSQL MCP server to interact with PostgreSQL databases. It demonstrates how to:
- Connect to a PostgreSQL database using MCP (Model Context Protocol)
- Use `uvx` to run the MCP server without manual installation
- Pass database credentials securely via environment variables
## Prerequisites
- **PostgreSQL Database**: You need access to a PostgreSQL database with a connection string
- **uvx**: The agent uses `uvx` (part of the `uv` package manager) to run the MCP server
## Setup Instructions
### 1. Configure Database Connection
Create a `.env` file in the `mcp_postgres_agent` directory:
```bash
POSTGRES_CONNECTION_STRING=postgresql://user:password@host:port/database
```
Example connection string format:
```
postgresql://username:password@localhost:5432/mydb
postgresql://postgres.xyz:password@aws-region.pooler.supabase.com:5432/postgres
```
### 2. Run the Agent
Start the ADK Web UI from the samples directory:
```bash
adk web
```
The agent will automatically:
- Load the connection string from the `.env` file
- Use `uvx` to run the `postgres-mcp` server with unrestricted access mode
- Connect to your PostgreSQL database
### 3. Example Queries
Once the agent is running, try these queries:
- "What tables are in the database?"
- "Show me the schema for the users table"
- "Query the first 10 rows from the products table"
- "What indexes exist on the orders table?"
- "Create a new table called test_table with columns id and name"
## Configuration Details
The agent uses:
- **Model**: Gemini 2.0 Flash
- **MCP Server**: `postgres-mcp` (via `uvx`)
- **Access Mode**: Unrestricted (allows read/write operations). **Warning**: Using unrestricted mode in a production environment can pose significant security risks. It is recommended to use a more restrictive access mode or configure database user permissions appropriately for production use.
- **Connection**: StdioConnectionParams with 60-second timeout
- **Environment Variable**: `DATABASE_URI` (mapped from `POSTGRES_CONNECTION_STRING`)
## Troubleshooting
- Ensure your `POSTGRES_CONNECTION_STRING` is correctly formatted
- Verify database credentials and network access
- Check that `uv` is installed (`pip install uv` or `brew install uv`)
+15
View File
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,56 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from dotenv import load_dotenv
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool import StdioConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.genai.types import GenerateContentConfig
from mcp import StdioServerParameters
load_dotenv()
POSTGRES_CONNECTION_STRING = os.getenv("POSTGRES_CONNECTION_STRING")
if not POSTGRES_CONNECTION_STRING:
raise ValueError(
"POSTGRES_CONNECTION_STRING environment variable not set. "
"Please create a .env file with this variable."
)
root_agent = LlmAgent(
name="postgres_agent",
instruction=(
"You are a PostgreSQL database assistant. "
"Use the provided tools to query, manage, and interact with "
"the PostgreSQL database. Ask clarifying questions when unsure."
),
tools=[
MCPToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command="uvx",
args=["postgres-mcp", "--access-mode=unrestricted"],
env={"DATABASE_URI": POSTGRES_CONNECTION_STRING},
),
timeout=60,
),
)
],
generate_content_config=GenerateContentConfig(
temperature=0.2,
top_p=0.95,
),
)
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,165 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Sample agent demonstrating MCP progress callback feature.
This sample shows how to use the progress_callback parameter in McpToolset
to receive progress notifications from MCP servers during long-running tool
executions.
There are two ways to use progress callbacks:
1. Simple callback (shared by all tools):
Pass a ProgressFnT callback that receives (progress, total, message).
2. Factory function (per-tool callbacks with runtime context):
Pass a ProgressCallbackFactory that takes (tool_name, callback_context, **kwargs)
and returns a ProgressFnT or None. This allows different tools to have different
progress handling logic, and the factory can access and modify session state
via the CallbackContext. The **kwargs ensures forward compatibility for future
parameters.
IMPORTANT: Progress callbacks only work when the MCP server actually sends
progress notifications. Most simple MCP servers (like the filesystem server)
do not send progress updates. This sample uses a mock server that demonstrates
progress reporting.
Usage:
adk run contributing/samples/mcp_progress_callback_agent
Then try:
"Run the long running task with 5 steps"
"Process these items: apple, banana, cherry"
"""
import os
import sys
from typing import Any
from google.adk.agents.callback_context import CallbackContext
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool import McpToolset
from google.adk.tools.mcp_tool import StdioConnectionParams
from mcp import StdioServerParameters
from mcp.shared.session import ProgressFnT
_current_dir = os.path.dirname(os.path.abspath(__file__))
_mock_server_path = os.path.join(_current_dir, "mock_progress_server.py")
# Option 1: Simple shared callback
async def simple_progress_callback(
progress: float,
total: float | None,
message: str | None,
) -> None:
"""Handle progress notifications from MCP server.
This callback is shared by all tools in the toolset.
"""
if total is not None:
percentage = (progress / total) * 100
bar_length = 20
filled = int(bar_length * progress / total)
bar = "=" * filled + "-" * (bar_length - filled)
print(f"[{bar}] {percentage:.0f}% ({progress}/{total}) {message or ''}")
else:
print(f"Progress: {progress} {f'- {message}' if message else ''}")
# Option 2: Factory function for per-tool callbacks with runtime context
def progress_callback_factory(
tool_name: str,
*,
callback_context: CallbackContext | None = None,
**kwargs: Any,
) -> ProgressFnT | None:
"""Create a progress callback for a specific tool.
This factory allows different tools to have different progress handling.
It receives a CallbackContext for accessing and modifying runtime information
like session state. The **kwargs parameter ensures forward compatibility.
Args:
tool_name: The name of the MCP tool.
callback_context: The callback context providing access to session,
state, artifacts, and other runtime information. Allows modifying
state via ctx.state['key'] = value. May be None if not available.
**kwargs: Additional keyword arguments for future extensibility.
Returns:
A progress callback function, or None if no callback is needed.
"""
# Example: Access session info from context (if available)
session_id = "unknown"
if callback_context and callback_context.session:
session_id = callback_context.session.id
async def callback(
progress: float,
total: float | None,
message: str | None,
) -> None:
# Include tool name and session info in the progress output
prefix = f"[{tool_name}][session:{session_id}]"
if total is not None:
percentage = (progress / total) * 100
bar_length = 20
filled = int(bar_length * progress / total)
bar = "=" * filled + "-" * (bar_length - filled)
print(f"{prefix} [{bar}] {percentage:.0f}% {message or ''}")
# Example: Store progress in state (callback_context allows modification)
if callback_context:
callback_context.state["last_progress"] = progress
callback_context.state["last_total"] = total
else:
print(
f"{prefix} Progress: {progress} {f'- {message}' if message else ''}"
)
return callback
root_agent = LlmAgent(
name="progress_demo_agent",
instruction="""\
You are a helpful assistant that can run long-running tasks.
Available tools:
- long_running_task: Simulates a task with multiple steps. You can specify
the number of steps and delay between them.
- process_items: Processes a list of items one by one with progress updates.
When the user asks you to run a task, use these tools and the progress
will be logged automatically.
Example requests:
- "Run a long task with 5 steps"
- "Process these items: apple, banana, cherry, date"
""",
tools=[
McpToolset(
connection_params=StdioConnectionParams(
server_params=StdioServerParameters(
command=sys.executable, # Use current Python interpreter
args=[_mock_server_path],
),
timeout=60,
),
# Use factory function for per-tool callbacks (Option 2)
# Or use simple_progress_callback for shared callback (Option 1)
progress_callback=progress_callback_factory,
)
],
)
@@ -0,0 +1,161 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Mock MCP server that sends progress notifications.
This server demonstrates how MCP servers can send progress updates
during long-running tool execution.
Run this server directly:
python mock_progress_server.py
Or use it with the sample agent:
See agent_with_mock_server.py
"""
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import TextContent
from mcp.types import Tool
server = Server("mock-progress-server")
@server.list_tools()
async def list_tools() -> list[Tool]:
"""List available tools."""
return [
Tool(
name="long_running_task",
description=(
"A simulated long-running task that reports progress. "
"Use this to test progress callback functionality."
),
inputSchema={
"type": "object",
"properties": {
"steps": {
"type": "integer",
"description": "Number of steps to simulate (default: 5)",
"default": 5,
},
"delay": {
"type": "number",
"description": (
"Delay in seconds between steps (default: 0.5)"
),
"default": 0.5,
},
},
},
),
Tool(
name="process_items",
description="Process a list of items with progress reporting.",
inputSchema={
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {"type": "string"},
"description": "List of items to process",
},
},
"required": ["items"],
},
),
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
"""Handle tool calls with progress reporting."""
ctx = server.request_context
if name == "long_running_task":
steps = arguments.get("steps", 5)
delay = arguments.get("delay", 0.5)
# Get progress token from request metadata
progress_token = None
if ctx.meta and hasattr(ctx.meta, "progressToken"):
progress_token = ctx.meta.progressToken
for i in range(steps):
# Simulate work
await asyncio.sleep(delay)
# Send progress notification if client supports it
if progress_token is not None:
await ctx.session.send_progress_notification(
progress_token=progress_token,
progress=i + 1,
total=steps,
message=f"Completed step {i + 1} of {steps}",
)
return [
TextContent(
type="text",
text=f"Successfully completed {steps} steps!",
)
]
elif name == "process_items":
items = arguments.get("items", [])
total = len(items)
progress_token = None
if ctx.meta and hasattr(ctx.meta, "progressToken"):
progress_token = ctx.meta.progressToken
results = []
for i, item in enumerate(items):
# Simulate processing
await asyncio.sleep(0.3)
results.append(f"Processed: {item}")
# Send progress
if progress_token is not None:
await ctx.session.send_progress_notification(
progress_token=progress_token,
progress=i + 1,
total=total,
message=f"Processing item: {item}",
)
return [
TextContent(
type="text",
text="\n".join(results),
)
]
return [TextContent(type="text", text=f"Unknown tool: {name}")]
async def main():
"""Run the MCP server."""
async with stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
server.create_initialization_options(),
)
if __name__ == "__main__":
asyncio.run(main())
@@ -0,0 +1,52 @@
# FastMCP Server-Side Sampling with ADK
This project demonstrates how to use server-side sampling with a `fastmcp` server connected to an ADK `MCPToolset`.
## Description
The setup consists of two main components:
1. **ADK Agent (`agent.py`):** An `LlmAgent` is configured with an `MCPToolset`. This toolset connects to a local `fastmcp` server.
1. **FastMCP Server (`mcp_server.py`):** A `fastmcp` server that exposes a single tool, `analyze_sentiment`. This server is configured to use its own LLM for sampling, independent of the ADK agent's LLM.
The flow is as follows:
1. The user provides a text prompt to the ADK agent.
1. The agent decides to use the `analyze_sentiment` tool from the `MCPToolset`.
1. The tool call is sent to the `mcp_server.py`.
1. Inside the `analyze_sentiment` tool, `ctx.sample()` is called. This delegates an LLM call to the `fastmcp` server's own sampling handler.
1. The `mcp_server`'s LLM processes the prompt from `ctx.sample()` and returns the result to the server.
1. The server processes the LLM response and returns the final sentiment to the agent.
1. The agent displays the result to the user.
## Steps to Run
### Prerequisites
- Python 3.10+
- `google-adk` library installed.
- A configured OpenAI API key.
### 1. Set up the Environment
Clone the project and navigate to the directory. Make sure your `OPENAI_API_KEY` is available as an environment variable.
### 2. Install Dependencies
Install the required Python libraries:
```bash
pip install fastmcp openai litellm
```
### 3. Run the Example
Navigate to the `samples` directory and choose this ADK agent:
```bash
adk web .
```
The agent will automatically start the FastMCP server in the background.
- **Sample user prompt:** "What is the sentiment of 'I love building things with Python'?"
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
+56
View File
@@ -0,0 +1,56 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from google.adk.agents import LlmAgent
from google.adk.models.lite_llm import LiteLlm
from google.adk.tools.mcp_tool import MCPToolset
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
from mcp import StdioServerParameters
# This example uses the OpenAI API for both the agent and the server.
# Ensure your OPENAI_API_KEY is available as an environment variable.
api_key = os.getenv('OPENAI_API_KEY')
if not api_key:
raise ValueError('The OPENAI_API_KEY environment variable must be set.')
# Configure the StdioServerParameters to start the mcp_server.py script
# as a subprocess. The OPENAI_API_KEY is passed to the server's environment.
server_params = StdioServerParameters(
command='python',
args=['mcp_server.py'],
env={'OPENAI_API_KEY': api_key},
)
# Create the ADK MCPToolset, which connects to the FastMCP server.
# The `tool_filter` ensures that only the 'analyze_sentiment' tool is exposed
# to the agent.
mcp_toolset = MCPToolset(
connection_params=StdioConnectionParams(
server_params=server_params,
),
tool_filter=['analyze_sentiment'],
)
# Define the ADK agent that uses the MCP toolset.
root_agent = LlmAgent(
model=LiteLlm(model='openai/gpt-4o'),
name='SentimentAgent',
instruction=(
'You are an expert at analyzing text sentiment. Use the'
' analyze_sentiment tool to classify user input.'
),
tools=[mcp_toolset],
)
@@ -0,0 +1,81 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
from fastmcp import Context
from fastmcp import FastMCP
from fastmcp.experimental.sampling.handlers.openai import OpenAISamplingHandler
from openai import OpenAI
logging.basicConfig(level=logging.INFO)
API_KEY = os.getenv("OPENAI_API_KEY")
# Set up the server's LLM handler using the OpenAI API.
# This handler will be used for all sampling requests from tools on this server.
llm_handler = OpenAISamplingHandler(
default_model="gpt-4o",
client=OpenAI(
api_key=API_KEY,
),
)
# Create the FastMCP Server instance.
# The `sampling_handler` is configured to use the server's own LLM.
# `sampling_handler_behavior="always"` ensures the server never delegates
# sampling back to the ADK agent.
mcp = FastMCP(
name="SentimentAnalysis",
sampling_handler=llm_handler,
sampling_handler_behavior="always",
)
@mcp.tool
async def analyze_sentiment(text: str, ctx: Context) -> dict:
"""Analyzes sentiment by delegating to the server's own LLM."""
logging.info("analyze_sentiment tool called with text: %s", text)
prompt = f"""Analyze the sentiment of the following text as positive,
negative, or neutral. Just output a single word.
Text to analyze: {text}"""
# This delegates the LLM call to the server's own sampling handler,
# as configured in the FastMCP instance.
logging.info("Attempting to call ctx.sample()")
try:
response = await ctx.sample(prompt)
logging.info("ctx.sample() successful. Response: %s", response)
except Exception as e:
logging.error("ctx.sample() failed: %s", e, exc_info=True)
raise
sentiment = response.text.strip().lower()
if "positive" in sentiment:
result = "positive"
elif "negative" in sentiment:
result = "negative"
else:
result = "neutral"
logging.info("Sentiment analysis result: %s", result)
return {"text": text, "sentiment": result}
if __name__ == "__main__":
print("Starting FastMCP server with tool 'analyze_sentiment'...")
# This runs the server process, which the ADK agent will connect to.
mcp.run()
@@ -0,0 +1,57 @@
# MCP Service Account Agent Sample
This agent demonstrates how to connect to a remote MCP server using a gcloud service account for authentication. It uses Streamable HTTP for communication.
## Setup
Before running the agent, you need to configure the MCP server URL and your service account credentials in `agent.py`.
1. **Configure MCP Server URL:**
Update the `MCP_SERVER_URL` variable with the URL of your MCP server instance.
```python
# agent.py
# TODO: Update this to the production MCP server url and scopes.
MCP_SERVER_URL = "https://test.sandbox.googleapis.com/mcp"
```
1. **Set up Service Account Credentials:**
- Obtain the JSON key file for your gcloud service account.
- In `agent.py`, find the `ServiceAccountCredential` object and populate its parameters (e.g., `project_id`, `private_key`, `client_email`, etc.) with the corresponding values from your JSON key file.
```python
# agent.py
# TODO: Update this to the user's service account credentials.
auth_credential=AuthCredential(
auth_type=AuthCredentialTypes.SERVICE_ACCOUNT,
service_account=ServiceAccount(
service_account_credential=ServiceAccountCredential(
type_="service_account",
project_id="example",
private_key_id="123",
private_key="123",
client_email="test@example.iam.gserviceaccount.com",
client_id="123",
auth_uri="https://accounts.google.com/o/oauth2/auth",
token_uri="https://oauth2.googleapis.com/token",
auth_provider_x509_cert_url=(
"https://www.googleapis.com/oauth2/v1/certs"
),
client_x509_cert_url="https://www.googleapis.com/robot/v1/metadata/x509/example.iam.gserviceaccount.com",
universe_domain="googleapis.com",
),
scopes=SCOPES.keys(),
),
),
```
## Running the Agent
Once configured, you can run the agent.
For example:
```bash
adk web
```
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,73 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from fastapi.openapi.models import OAuth2
from fastapi.openapi.models import OAuthFlowClientCredentials
from fastapi.openapi.models import OAuthFlows
from google.adk.agents.llm_agent import LlmAgent
from google.adk.auth.auth_credential import AuthCredential
from google.adk.auth.auth_credential import AuthCredentialTypes
from google.adk.auth.auth_credential import ServiceAccount
from google.adk.auth.auth_credential import ServiceAccountCredential
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPServerParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
# TODO: Update this to the production MCP server url and scopes.
MCP_SERVER_URL = "https://test.sandbox.googleapis.com/mcp"
SCOPES = {"https://www.googleapis.com/auth/cloud-platform": ""}
root_agent = LlmAgent(
name="enterprise_assistant",
instruction="""
Help the user with the tools available to you.
""",
tools=[
MCPToolset(
connection_params=StreamableHTTPServerParams(
url=MCP_SERVER_URL,
),
auth_scheme=OAuth2(
flows=OAuthFlows(
clientCredentials=OAuthFlowClientCredentials(
tokenUrl="https://oauth2.googleapis.com/token",
scopes=SCOPES,
)
)
),
# TODO: Update this to the user's service account credentials.
auth_credential=AuthCredential(
auth_type=AuthCredentialTypes.SERVICE_ACCOUNT,
service_account=ServiceAccount(
service_account_credential=ServiceAccountCredential(
type_="service_account",
project_id="example",
private_key_id="123",
private_key="123",
client_email="test@example.iam.gserviceaccount.com",
client_id="123",
auth_uri="https://accounts.google.com/o/oauth2/auth",
token_uri="https://oauth2.googleapis.com/token",
auth_provider_x509_cert_url=(
"https://www.googleapis.com/oauth2/v1/certs"
),
client_x509_cert_url="https://www.googleapis.com/robot/v1/metadata/x509/example.iam.gserviceaccount.com",
universe_domain="googleapis.com",
),
scopes=SCOPES.keys(),
),
),
)
],
)
@@ -0,0 +1,7 @@
This agent connects to a local MCP server via sse.
To run this agent, start the local MCP server first by :
```bash
uv run filesystem_server.py
```
+15
View File
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
+86
View File
@@ -0,0 +1,86 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import logging
import os
import pprint
from typing import Any
from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.mcp_instruction_provider import McpInstructionProvider
from google.adk.tools.base_tool import BaseTool
from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.tool_context import ToolContext
# Configure logging; the mcp_tool logger must be set to
# DEBUG to capture http_debug_info
logging.basicConfig(level=logging.INFO)
logging.getLogger('google_adk.google.adk.tools.mcp_tool.mcp_tool').setLevel(
logging.DEBUG
)
_allowed_path = os.path.dirname(os.path.abspath(__file__))
connection_params = SseConnectionParams(
url='http://localhost:3000/sse',
headers={'Accept': 'text/event-stream'},
)
def after_tool_debug_callback(
tool: BaseTool,
args: dict[str, Any],
tool_context: ToolContext,
tool_response: dict[str, Any],
) -> dict[str, Any] | None:
# pylint: disable=unused-argument
print(f'\n=== HTTP Debug Info (from Callback for {tool.name}) ===')
pprint.pprint(tool_context.custom_metadata.get('http_debug_info'))
print('====================================================\n')
return None
root_agent = LlmAgent(
name='enterprise_assistant',
instruction=McpInstructionProvider(
connection_params=connection_params,
prompt_name='file_system_prompt',
),
tools=[
MCPToolset(
connection_params=connection_params,
# don't want agent to do write operation
# you can also do below
# tool_filter=lambda tool, ctx=None: tool.name
# not in [
# 'write_file',
# 'edit_file',
# 'create_directory',
# 'move_file',
# ],
tool_filter=[
'read_file',
'read_multiple_files',
'list_directory',
'directory_tree',
'search_files',
'get_file_info',
'list_allowed_directories',
],
require_confirmation=True,
)
],
after_tool_callback=after_tool_debug_callback,
)
@@ -0,0 +1,88 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import os
from pathlib import Path
import sys
from mcp.server.fastmcp import FastMCP
# Create an MCP server with a name
mcp = FastMCP("Filesystem Server", host="localhost", port=3000)
# Add a tool to read file contents
@mcp.tool(description="Read contents of a file")
def read_file(filepath: str) -> str:
"""Read and return the contents of a file."""
with open(filepath, "r") as f:
return f.read()
# Add a tool to list directory contents
@mcp.tool(description="List contents of a directory")
def list_directory(dirpath: str) -> list:
"""List all files and directories in the given directory."""
return os.listdir(dirpath)
# Add a tool to get current working directory
@mcp.tool(description="Get current working directory")
def get_cwd() -> str:
"""Return the current working directory."""
return str(Path.cwd())
# Add a prompt for accessing file systems
@mcp.prompt(name="file_system_prompt")
def file_system_prompt() -> str:
return f"""\
Help the user access their file systems."""
# Graceful shutdown handler
async def shutdown(signal, loop):
"""Cleanup tasks tied to the service's shutdown."""
print(f"\nReceived exit signal {signal.name}...")
# Get all running tasks
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
# Cancel all tasks
for task in tasks:
task.cancel()
print(f"Cancelling {len(tasks)} outstanding tasks")
await asyncio.gather(*tasks, return_exceptions=True)
# Stop the loop
loop.stop()
print("Shutdown complete!")
# Main entry point with graceful shutdown handling
if __name__ == "__main__":
try:
# The MCP run function ultimately uses asyncio.run() internally
mcp.run(transport="sse")
except KeyboardInterrupt:
print("\nServer shutting down gracefully...")
# The asyncio event loop has already been stopped by the KeyboardInterrupt
print("Server has been shut down.")
except Exception as e:
print(f"Unexpected error: {e}")
sys.exit(1)
finally:
print("Thank you for using the Filesystem MCP Server!")
@@ -0,0 +1,76 @@
# MCP SSE Agent with mTLS
This sample demonstrates how to configure an ADK agent to connect to an MCP server using **mutual TLS (mTLS)** over SSE (HTTPS).
## Prerequisites
To test mTLS locally, you need to generate local certificates (CA, Server, and Client) and configure your environment to trust them.
### 1. Generate Certificates
Run the helper script in this directory to generate a local CA and sign the server and client certificates:
```bash
./generate_mtls_certs.sh
```
This will generate:
- `ca.crt`, `ca.key` (Local CA)
- `server.crt`, `server.key` (Server certificate/key)
- `client.crt`, `client.key` (Client certificate/key)
- `certificate_config.json` (Workload certificate configuration for `google-auth`)
______________________________________________________________________
## Running the Sample
### Step 1: Start the MCP Server
Start the server in this directory. We configure it to trust our local CA so it can verify the client certificate:
```bash
# Point to the certificate config
export GOOGLE_API_CERTIFICATE_CONFIG=$(pwd)/certificate_config.json
# Tell the server to trust our test CA for client verification
export SSL_CA_CERTS=$(pwd)/ca.crt
# Run the server
python filesystem_server.py
```
*(The server will run on `https://localhost:3000`)*
### Step 2: Run the ADK Agent (Client)
In a second terminal, navigate to the open-source workspace root and run the client.
```bash
cd third_party/py/google/adk/open_source_workspace
source .venv/bin/activate
# 1. Combine system CAs with our test CA so the client trusts the server cert
cat /usr/lib/ssl/cert.pem contributing/samples/mcp/mcp_sse_mtls_agent/ca.crt > combined_ca.pem
export SSL_CERT_FILE=$(pwd)/combined_ca.pem
# 2. Point google-auth to our simulated workload config
export GOOGLE_API_CERTIFICATE_CONFIG=$(pwd)/contributing/samples/mcp/mcp_sse_mtls_agent/certificate_config.json
# 3. Enable client certificate usage
export GOOGLE_API_USE_CLIENT_CERTIFICATE=true
# 4. Set your LLM credentials (e.g. source your env file)
source test/.env
# 5. Run the agent
adk run contributing/samples/mcp/mcp_sse_mtls_agent
```
______________________________________________________________________
## How it works
1. **Client Certificate (mTLS):** The `google-auth` library (used by ADK) reads `GOOGLE_API_CERTIFICATE_CONFIG` to load the client certificate (`client.crt`) and key (`client.key`) as a simulated Workload Certificate.
1. **Server Verification:** The server loads the CA (`ca.crt`) via `SSL_CA_CERTS` and requires the client to present a certificate signed by this CA (`ssl_cert_reqs=ssl.CERT_REQUIRED`).
1. **Client Verification:** The client trusts the server certificate (`server.crt`) because it is signed by the same CA, which we added to `SSL_CERT_FILE`.
@@ -0,0 +1,45 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from google.adk.agents.llm_agent import LlmAgent
from google.adk.agents.mcp_instruction_provider import McpInstructionProvider
from google.adk.tools.mcp_tool.mcp_session_manager import SseConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
connection_params = SseConnectionParams(
url=os.environ.get('MCP_SERVER_URL', 'https://localhost:3000/sse'),
headers={'Accept': 'text/event-stream'},
)
root_agent = LlmAgent(
name='enterprise_assistant',
model='gemini-2.5-flash',
instruction=McpInstructionProvider(
connection_params=connection_params,
prompt_name='file_system_prompt',
),
tools=[
MCPToolset(
connection_params=connection_params,
tool_filter=[
'read_file',
'list_directory',
'get_cwd',
],
)
],
)
@@ -0,0 +1,151 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import os
import pathlib
import ssl
import sys
import tempfile
import google.auth.transport.mtls as google_mtls
from mcp.server.fastmcp import FastMCP
import uvicorn
# Create an MCP server with a name
mcp = FastMCP("Filesystem Server (mTLS)", host="localhost", port=3000)
# Add a tool to read file contents
@mcp.tool(description="Read contents of a file")
def read_file(filepath: str) -> str:
"""Read and return the contents of a file."""
with open(filepath, "r") as f:
return f.read()
# Add a tool to list directory contents
@mcp.tool(description="List contents of a directory")
def list_directory(dirpath: str) -> list:
"""List all files and directories in the given directory."""
return os.listdir(dirpath)
# Add a tool to get current working directory
@mcp.tool(description="Get current working directory")
def get_cwd() -> str:
"""Return the current working directory."""
return str(pathlib.Path.cwd())
# Add a prompt for accessing file systems
@mcp.prompt()
def file_system_prompt() -> str:
"""Prompt helper for accessing file systems."""
return (
"You are a helpful assistant with access to the local filesystem. You can"
" read files and list directories to help the user with their request."
)
# Graceful shutdown handler
async def shutdown(signal, loop):
"""Cleanup tasks on shutdown."""
print(f"\nReceived exit signal {signal.name}...")
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
for task in tasks:
task.cancel()
print(f"Cancelling {len(tasks)} outstanding tasks")
await asyncio.gather(*tasks, return_exceptions=True)
loop.stop()
# Main entry point with mTLS enabled
if __name__ == "__main__":
cert_dir = os.path.dirname(os.path.abspath(__file__))
keyfile = os.path.join(cert_dir, "server.key")
certfile = os.path.join(cert_dir, "server.crt")
if not (os.path.exists(keyfile) and os.path.exists(certfile)):
print(f"Error: mTLS cert files not found in {cert_dir}")
print("Please generate them using the helper script:")
print(f" ./generate_mtls_certs.sh")
sys.exit(1)
# Configure SSL context for mTLS
print("Configuring SSL context for mTLS...")
# Allow explicit CA certs override (useful for testing with custom CA signed certs)
ca_certs = os.environ.get("SSL_CA_CERTS")
temp_ca_file = None
if ca_certs:
print(f" Using explicit SSL_CA_CERTS: {ca_certs}")
else:
has_cert_source = google_mtls.has_default_client_cert_source()
print(f" has_default_client_cert_source: {has_cert_source}")
print(f" default cafile: {ssl.get_default_verify_paths().cafile}")
if has_cert_source:
try:
callback = google_mtls.default_client_cert_source()
client_cert_bytes, _ = callback()
temp_ca_file = tempfile.NamedTemporaryFile(delete=False, suffix=".crt")
temp_ca_file.write(client_cert_bytes)
temp_ca_file.close()
ca_certs = temp_ca_file.name
print(f" Loaded client cert to trust: {ca_certs}")
except Exception as e:
print(f" Warning: Failed to load default client cert: {e}")
ca_certs = ssl.get_default_verify_paths().cafile
else:
print(" No default client cert source found. Using system CAs.")
ca_certs = ssl.get_default_verify_paths().cafile
print(f" Using ca_certs for client verification: {ca_certs}")
app = mcp.sse_app()
config = uvicorn.Config(
app,
host=mcp.settings.host,
port=mcp.settings.port,
log_level=mcp.settings.log_level.lower(),
ssl_keyfile=keyfile,
ssl_certfile=certfile,
ssl_cert_reqs=int(ssl.CERT_REQUIRED),
ssl_ca_certs=ca_certs,
)
server = uvicorn.Server(config)
print(
"Starting MCP server with mTLS on"
f" https://{mcp.settings.host}:{mcp.settings.port}"
)
try:
asyncio.run(server.serve())
except KeyboardInterrupt:
print("\nServer shutting down gracefully...")
except Exception as e:
print(f"Unexpected error: {e}")
sys.exit(1)
finally:
if temp_ca_file:
try:
os.unlink(temp_ca_file.name)
print(f"Cleaned up temp CA file: {temp_ca_file.name}")
except OSError:
pass
print("Thank you for using the Filesystem MCP Server!")
@@ -0,0 +1,56 @@
#!/bin/bash
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
set -e
# Directory where this script is located
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$DIR"
echo "Generating certificates in $DIR..."
# 1. Create CA
openssl req -x509 -new -nodes -newkey rsa:2048 -keyout ca.key -sha256 -days 365 -out ca.crt -subj '/CN=TestCA'
# 2. Create Server Cert
openssl req -new -nodes -newkey rsa:2048 -keyout server.key -out server.csr -subj '/CN=localhost'
# Sign with CA
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256
# 3. Create Client Cert
openssl req -new -nodes -newkey rsa:2048 -keyout client.key -out client.csr -subj '/CN=TestClient'
# Sign with CA
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 365 -sha256
# Clean up CSRs and serial file
rm -f server.csr client.csr ca.srl
# 4. Create certificate_config.json
cat <<EOF > certificate_config.json
{
"cert_configs": {
"workload": {
"cert_path": "$DIR/client.crt",
"key_path": "$DIR/client.key"
}
}
}
EOF
echo "Done! Generated:"
echo " - ca.crt, ca.key (CA)"
echo " - server.crt, server.key (Server cert)"
echo " - client.crt, client.key (Client cert)"
echo " - certificate_config.json (Workload config for google-auth)"
@@ -0,0 +1,21 @@
# Notion MCP Agent
This is an agent that is using Notion MCP tool to call Notion API. And it demonstrates how to pass in the Notion API key.
Follow below instruction to use it:
- Follow the installation instruction in below page to get an API key for Notion API:
https://www.npmjs.com/package/@notionhq/notion-mcp-server
- Set the environment variable `NOTION_API_KEY` to the API key you obtained in the previous step.
```bash
export NOTION_API_KEY=<your_notion_api_key>
```
- Run the agent in ADK Web UI
- Send below queries:
- What can you do for me ?
- Search `XXXX` in my pages.
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,47 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
from dotenv import load_dotenv
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from google.adk.tools.mcp_tool.mcp_toolset import StdioServerParameters
load_dotenv()
NOTION_API_KEY = os.getenv("NOTION_API_KEY")
NOTION_HEADERS = json.dumps({
"Authorization": f"Bearer {NOTION_API_KEY}",
"Notion-Version": "2022-06-28",
})
root_agent = LlmAgent(
name="notion_agent",
instruction=(
"You are my workspace assistant. "
"Use the provided tools to read, search, comment on, "
"or create Notion pages. Ask clarifying questions when unsure."
),
tools=[
MCPToolset(
connection_params=StdioServerParameters(
command="npx",
args=["-y", "@notionhq/notion-mcp-server"],
env={"OPENAPI_MCP_HEADERS": NOTION_HEADERS},
)
)
],
)
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
+65
View File
@@ -0,0 +1,65 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool import StdioConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
from mcp import StdioServerParameters
_allowed_path = os.path.dirname(os.path.abspath(__file__))
root_agent = LlmAgent(
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', # Arguments for the command
'@modelcontextprotocol/server-filesystem',
_allowed_path,
],
),
timeout=5,
),
# don't want agent to do write operation
# you can also do below
# tool_filter=lambda tool, ctx=None: tool.name
# not in [
# 'write_file',
# 'edit_file',
# 'create_directory',
# 'move_file',
# ],
tool_filter=[
'read_file',
'read_multiple_files',
'list_directory',
'directory_tree',
'search_files',
'get_file_info',
'list_allowed_directories',
],
)
],
)
@@ -0,0 +1,7 @@
This agent connects to a local MCP server via Streamable HTTP.
To run this agent, start the local MCP server first by:
```bash
uv run filesystem_server.py
```
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,57 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
from google.adk.agents.llm_agent import LlmAgent
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPServerParams
from google.adk.tools.mcp_tool.mcp_toolset import MCPToolset
_allowed_path = os.path.dirname(os.path.abspath(__file__))
root_agent = LlmAgent(
name='enterprise_assistant',
instruction=f"""\
Help user accessing their file systems.
Allowed directory: {_allowed_path}
""",
tools=[
MCPToolset(
connection_params=StreamableHTTPServerParams(
url='http://localhost:3000/mcp',
),
# don't want agent to do write operation
# you can also do below
# tool_filter=lambda tool, ctx=None: tool.name
# not in [
# 'write_file',
# 'edit_file',
# 'create_directory',
# 'move_file',
# ],
tool_filter=[
'read_file',
'read_multiple_files',
'list_directory',
'directory_tree',
'search_files',
'get_file_info',
'list_allowed_directories',
],
use_mcp_resources=True,
)
],
)
@@ -0,0 +1,100 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import asyncio
import json
import os
from pathlib import Path
import sys
from mcp.server.fastmcp import FastMCP
# Create an MCP server with a name
mcp = FastMCP("Filesystem Server", host="localhost", port=3000)
# Add a tool to read file contents
@mcp.tool(description="Read contents of a file")
def read_file(filepath: str) -> str:
"""Read and return the contents of a file."""
with open(filepath, "r") as f:
return f.read()
# Add a tool to list directory contents
@mcp.tool(description="List contents of a directory")
def list_directory(dirpath: str) -> list:
"""List all files and directories in the given directory."""
return os.listdir(dirpath)
# Add a tool to get current working directory
@mcp.tool(description="Get current working directory")
def get_cwd() -> str:
"""Return the current working directory."""
return str(Path.cwd())
# Add a resource for testing with JSON data
@mcp.resource(
name="sample_data",
uri="file:///sample_data.json",
mime_type="application/json",
)
def sample_data() -> str:
data = {
"users": [
{"id": 1, "name": "Alice", "role": "admin"},
{"id": 2, "name": "Bob", "role": "user"},
{"id": 3, "name": "Charlie", "role": "user"},
],
"settings": {"theme": "dark", "notifications": True},
}
return json.dumps(data, indent=2)
# Graceful shutdown handler
async def shutdown(signal, loop):
"""Cleanup tasks tied to the service's shutdown."""
print(f"\nReceived exit signal {signal.name}...")
# Get all running tasks
tasks = [t for t in asyncio.all_tasks() if t is not asyncio.current_task()]
# Cancel all tasks
for task in tasks:
task.cancel()
print(f"Cancelling {len(tasks)} outstanding tasks")
await asyncio.gather(*tasks, return_exceptions=True)
# Stop the loop
loop.stop()
print("Shutdown complete!")
# Main entry point with graceful shutdown handling
if __name__ == "__main__":
try:
# The MCP run function ultimately uses asyncio.run() internally
mcp.run(transport="streamable-http")
except KeyboardInterrupt:
print("\nServer shutting down gracefully...")
# The asyncio event loop has already been stopped by the KeyboardInterrupt
print("Server has been shut down.")
except Exception as e:
print(f"Unexpected error: {e}")
sys.exit(1)
finally:
print("Thank you for using the Filesystem MCP Server!")
@@ -0,0 +1,47 @@
# MCP Toolset OAuth Authentication Sample
This sample demonstrates the toolset authentication feature where OAuth credentials are required for both tool listing and tool calling.
## Overview
The toolset authentication flow works in two phases:
1. **Phase 1**: When the agent tries to get tools from the MCP server without credentials, the toolset signals "authentication required" and returns an auth request event.
1. **Phase 2**: After the user provides OAuth credentials, the agent can successfully list and call tools.
## Files
- `oauth_mcp_server.py` - MCP server that requires Bearer token authentication
- `agent.py` - Agent configuration with OAuth-protected MCP toolset
- `main.py` - Test script demonstrating the two-phase auth flow
## Running the Sample
1. Start the MCP server in one terminal:
```bash
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/oauth_mcp_server.py
```
2. Run the test script in another terminal:
```bash
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/main.py
```
## Expected Behavior
1. First invocation yields an `adk_request_credential` function call
1. The credential ID is `_adk_toolset_auth_McpToolset` to indicate toolset auth
1. After providing the access token, the agent can list and call tools
## Testing with ADK Web UI
You can also test with the ADK web UI:
```bash
adk web contributing/samples/mcp_toolset_auth
```
Note: The web UI will display the auth request and you'll need to manually provide credentials.
@@ -0,0 +1,15 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from . import agent
@@ -0,0 +1,75 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Agent that uses MCP toolset requiring OAuth authentication.
This agent demonstrates the toolset authentication feature where OAuth
credentials are required for both tool listing and tool calling.
"""
from __future__ import annotations
from fastapi.openapi.models import OAuth2
from fastapi.openapi.models import OAuthFlowAuthorizationCode
from fastapi.openapi.models import OAuthFlows
from google.adk.agents import LlmAgent
from google.adk.auth.auth_credential import AuthCredential
from google.adk.auth.auth_credential import AuthCredentialTypes
from google.adk.auth.auth_credential import OAuth2Auth
from google.adk.tools.mcp_tool.mcp_session_manager import StreamableHTTPConnectionParams
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
# OAuth2 auth scheme with authorization code flow
# This specifies the OAuth metadata needed for the full OAuth flow
auth_scheme = OAuth2(
flows=OAuthFlows(
authorizationCode=OAuthFlowAuthorizationCode(
authorizationUrl='https://example.com/oauth/authorize',
tokenUrl='https://example.com/oauth/token',
scopes={'read': 'Read access', 'write': 'Write access'},
)
)
)
# OAuth credential with client credentials (used for token exchange)
# In a real scenario, this would be used to obtain the access token
auth_credential = AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
client_id='test_client_id',
client_secret='test_client_secret',
),
)
# Create the MCP toolset with OAuth authentication
mcp_toolset = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url='http://localhost:3001/mcp',
),
auth_scheme=auth_scheme,
auth_credential=auth_credential,
)
# Define the agent that uses the OAuth-protected MCP toolset
root_agent = LlmAgent(
name='oauth_mcp_agent',
instruction="""You are a helpful assistant that can access user information.
You have access to tools that require authentication:
- get_user_profile: Get profile information for a specific user
- list_users: List all available users
When the user asks about users, use these tools to help them.""",
tools=[mcp_toolset],
)
@@ -0,0 +1,168 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Test script for MCP Toolset OAuth Authentication Flow.
This script demonstrates the two-phase tool discovery flow:
1. First invocation: Agent tries to get tools, auth is required, returns auth
request event (adk_request_credential)
2. User provides OAuth credentials (simulated)
3. Second invocation: Agent has credentials, can list and call tools
Usage:
# Start the MCP server first (in another terminal):
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/oauth_mcp_server.py
# Run the demo:
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/main.py
"""
from __future__ import annotations
import asyncio
from agent import auth_credential
from agent import auth_scheme
from agent import mcp_toolset
from agent import root_agent
from google.adk.auth.auth_credential import AuthCredential
from google.adk.auth.auth_credential import AuthCredentialTypes
from google.adk.auth.auth_credential import OAuth2Auth
from google.adk.auth.auth_tool import AuthConfig
from google.adk.runners import Runner
from google.adk.sessions.in_memory_session_service import InMemorySessionService
from google.genai import types
async def run_demo():
"""Run demo with real MCP server."""
print('=' * 60)
print('MCP Toolset OAuth Authentication Demo')
print('=' * 60)
print('\nNote: Make sure the MCP server is running:')
print(' python oauth_mcp_server.py\n')
# Create session service and runner
session_service = InMemorySessionService()
runner = Runner(
agent=root_agent,
app_name='toolset_auth_demo',
session_service=session_service,
)
# Create a session
session = await session_service.create_session(
app_name='toolset_auth_demo',
user_id='test_user',
)
print(f'Session created: {session.id}')
print('\n--- Phase 1: Initial request (no credentials) ---\n')
# First invocation - should trigger auth request
user_message = 'List all users'
print(f'User: {user_message}')
events = []
auth_function_call_id = None
max_events = 10
try:
async for event in runner.run_async(
session_id=session.id,
user_id='test_user',
new_message=types.Content(
role='user',
parts=[types.Part(text=user_message)],
),
):
events.append(event)
print(f'\nEvent from {event.author}:')
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
print(f' Text: {part.text}')
if part.function_call:
print(f' Function call: {part.function_call.name}')
if part.function_call.name == 'adk_request_credential':
auth_function_call_id = part.function_call.id
if len(events) >= max_events:
print(f'\n** SAFETY LIMIT ({max_events} events) **')
break
except Exception as e:
print(f'\nError: {e}')
print('Make sure the MCP server is running!')
await mcp_toolset.close()
return
if auth_function_call_id:
print('\n** Auth request detected! **')
print('\n--- Phase 2: Provide OAuth credentials ---\n')
# Simulate user providing OAuth credentials after completing OAuth flow
auth_response = AuthConfig(
auth_scheme=auth_scheme,
raw_auth_credential=auth_credential,
exchanged_auth_credential=AuthCredential(
auth_type=AuthCredentialTypes.OAUTH2,
oauth2=OAuth2Auth(
access_token='test_access_token_12345',
),
),
)
print('Providing access token: test_access_token_12345')
auth_response_message = types.Content(
role='user',
parts=[
types.Part(
function_response=types.FunctionResponse(
name='adk_request_credential',
id=auth_function_call_id,
response=auth_response.model_dump(exclude_none=True),
)
)
],
)
async for event in runner.run_async(
session_id=session.id,
user_id='test_user',
new_message=auth_response_message,
):
print(f'\nEvent from {event.author}:')
if event.content and event.content.parts:
for part in event.content.parts:
if part.text:
text = (
part.text[:200] + '...' if len(part.text) > 200 else part.text
)
print(f' Text: {text}')
if part.function_call:
print(f' Function call: {part.function_call.name}')
else:
print('\n** No auth request - credentials may already be available **')
print('\n' + '=' * 60)
print('Demo completed')
print('=' * 60)
await mcp_toolset.close()
if __name__ == '__main__':
asyncio.run(run_demo())
@@ -0,0 +1,120 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""MCP Server that requires OAuth Bearer token for both tool listing and calling.
This server validates the Authorization header on every request including:
- Tool listing (list_tools endpoint)
- Tool calling (call_tool endpoint)
This is used to test the toolset authentication feature in ADK.
"""
from __future__ import annotations
import logging
from fastapi import FastAPI
from fastapi import HTTPException
from fastapi import Request
from mcp.server.fastmcp import Context
from mcp.server.fastmcp import FastMCP
import uvicorn
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger('google_adk.' + __name__)
# Expected OAuth token for testing
VALID_TOKEN = 'test_access_token_12345'
# Create FastMCP server
mcp = FastMCP('OAuth Protected MCP Server', host='localhost', port=3001)
def validate_auth_header(request: Request) -> bool:
"""Validate the Authorization header contains a valid Bearer token."""
auth_header = request.headers.get('authorization', '')
if not auth_header.startswith('Bearer '):
logger.warning('Missing or invalid Authorization header: %s', auth_header)
return False
token = auth_header[7:] # Remove 'Bearer ' prefix
if token != VALID_TOKEN:
logger.warning('Invalid token: %s', token)
return False
logger.info('Valid token received')
return True
@mcp.tool(description='Get user profile information. Requires authentication.')
def get_user_profile(user_id: str, context: Context) -> dict:
"""Return user profile data for the given user ID."""
logger.info('get_user_profile called for user: %s', user_id)
if context.request_context and context.request_context.request:
if not validate_auth_header(context.request_context.request):
return {'error': 'Unauthorized - invalid or missing token'}
# Mock user data
users = {
'user1': {'id': 'user1', 'name': 'Alice', 'email': 'alice@example.com'},
'user2': {'id': 'user2', 'name': 'Bob', 'email': 'bob@example.com'},
}
if user_id in users:
return users[user_id]
return {'error': f'User {user_id} not found'}
@mcp.tool(description='List all available users. Requires authentication.')
def list_users(context: Context) -> dict:
"""Return a list of all users."""
logger.info('list_users called')
if context.request_context and context.request_context.request:
if not validate_auth_header(context.request_context.request):
return {'error': 'Unauthorized - invalid or missing token'}
return {
'users': [
{'id': 'user1', 'name': 'Alice'},
{'id': 'user2', 'name': 'Bob'},
]
}
# Create custom FastAPI app to add auth middleware for list_tools
app = FastAPI()
@app.middleware('http')
async def auth_middleware(request: Request, call_next):
"""Middleware to validate auth on all MCP endpoints."""
# Check if this is an MCP request
if request.url.path.startswith('/mcp'):
if not validate_auth_header(request):
raise HTTPException(status_code=401, detail='Unauthorized')
return await call_next(request)
if __name__ == '__main__':
print(f'Starting OAuth Protected MCP server on http://localhost:3001')
print(f'Expected token: Bearer {VALID_TOKEN}')
print(
'This server requires authentication for both tool listing and calling.'
)
# Run with streamable-http transport
mcp.run(transport='streamable-http')
@@ -0,0 +1,48 @@
# Config-based Agent Sample - MCP Toolset with Notion MCP Server
This sample demonstrates how to configure an ADK agent to use the Notion MCP server for interacting with Notion pages and databases.
## Setup Instructions
### 1. Create a Notion Integration
1. Go to [Notion Integrations](https://www.notion.so/my-integrations)
1. Click "New integration"
1. Give it a name and select your workspace
1. Copy the "Internal Integration Secret" (starts with `ntn_`)
For detailed setup instructions, see the [Notion MCP Server documentation](https://www.npmjs.com/package/@notionhq/notion-mcp-server).
### 2. Configure the Agent
Replace `<your_notion_token>` in `root_agent.yaml` with your actual Notion integration token:
```yaml
env:
OPENAPI_MCP_HEADERS: '{"Authorization": "Bearer secret_your_actual_token_here", "Notion-Version": "2022-06-28"}'
```
### 3. Grant Integration Access
**Important**: After creating the integration, you must grant it access to specific pages and databases:
1. Go to `Access` tab in [Notion Integrations](https://www.notion.so/my-integrations) page
1. Click "Edit access"
1. Add pages or databases as needed
### 4. Run the Agent
Use the `adk web` to run the agent and interact with your Notion workspace.
## Example Queries
- "What can you do for me?"
- "Search for 'project' in my pages"
- "Create a new page called 'Meeting Notes'"
- "List all my databases"
## Troubleshooting
- If you get "Unauthorized" errors, check that your token is correct
- If you get "Object not found" errors, ensure you've granted the integration access to the specific pages/databases
- Make sure the Notion API version in the headers matches what the MCP server expects
@@ -0,0 +1,30 @@
# Copyright 2026 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# yaml-language-server: $schema=https://raw.githubusercontent.com/google/adk-python/refs/heads/main/src/google/adk/agents/config_schemas/AgentConfig.json
name: notion_agent
model: gemini-2.5-flash
instruction: |
You are my workspace assistant. Use the provided tools to read, search, comment on, or create
Notion pages. Ask clarifying questions when unsure.
tools:
- name: MCPToolset
args:
stdio_server_params:
command: "npx"
args:
- "-y"
- "@notionhq/notion-mcp-server"
env:
OPENAPI_MCP_HEADERS: '{"Authorization": "Bearer <your_notion_token>", "Notion-Version": "2022-06-28"}'