fix(samples): make the MCP auth sample actually enforce auth, move MCP samples off the deprecated toolset name, and correct code execution claims
Co-authored-by: George Weale <gweale@google.com> PiperOrigin-RevId: 963555685
This commit is contained in:
committed by
Copybara-Service
parent
2353dde8e9
commit
c840dbe991
@@ -1,4 +1,4 @@
|
||||
# OAuth Sample
|
||||
# Agent Engine Code Execution Sample
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -7,9 +7,9 @@ This sample data science agent uses Agent Engine Code Execution Sandbox to execu
|
||||
|
||||
## How to use
|
||||
|
||||
* 1. Follow https://docs.cloud.google.com/agent-builder/agent-engine/code-execution/quickstart#create-an-agent-engine-instance to create an agent engine instance. Replace the AGENT_ENGINE_RESOURCE_NAME with the one you just created. A new sandbox environment under this agent engine instance will be created for each session with TTL of 1 year. But sandbox can only main its state for up to 14 days. This is the recommended usage for production environments.
|
||||
* 1. Follow https://docs.cloud.google.com/agent-builder/agent-engine/code-execution/quickstart#create-an-agent-engine-instance to create an agent engine instance. Set the `agent_engine_resource_name` argument in `agent.py` to the one you just created. A new sandbox environment under this agent engine instance will be created for each session with TTL of 1 year. But sandbox can only main its state for up to 14 days. This is the recommended usage for production environments.
|
||||
|
||||
* 2. For testing or protyping purposes, create a sandbox environment by following this guide: https://docs.cloud.google.com/agent-builder/agent-engine/code-execution/quickstart#create_a_sandbox. Replace the SANDBOX_RESOURCE_NAME with the one you just created. This will be used as the default sandbox environment for all the code executions throughout the lifetime of the agent. As the sandbox is re-used across sessions, all sessions will share the same Python environment and variable values."
|
||||
* 2. For testing or protyping purposes, create a sandbox environment by following this guide: https://docs.cloud.google.com/agent-builder/agent-engine/code-execution/quickstart#create_a_sandbox. Set the `sandbox_resource_name` argument in `agent.py` to the one you just created. This will be used as the default sandbox environment for all the code executions throughout the lifetime of the agent. As the sandbox is re-used across sessions, all sessions will share the same Python environment and variable values."
|
||||
|
||||
|
||||
## Sample prompt
|
||||
|
||||
@@ -36,7 +36,7 @@ def base_system_instruction():
|
||||
print(df.shape)
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
(49, 7)
|
||||
|
||||
```
|
||||
@@ -46,15 +46,15 @@ def base_system_instruction():
|
||||
print(f'{{x=}}')
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
x=999751168
|
||||
|
||||
```
|
||||
- You **never** generate ```tool_outputs yourself.
|
||||
- You **never** generate ```tool_output yourself.
|
||||
- You can then use this output to decide on next steps.
|
||||
- Print just variables (e.g., `print(f'{{variable=}}')`.
|
||||
|
||||
**No Assumptions:** **Crucially, avoid making assumptions about the nature of the data or column names.** Base findings solely on the data itself. Always use the information obtained from `explore_df` to guide your analysis.
|
||||
**No Assumptions:** **Crucially, avoid making assumptions about the nature of the data or column names.** Base findings solely on the data itself. Always inspect the data (its shape, dtypes and column names) before analyzing it.
|
||||
|
||||
**Available files:** Only use the files that are available as specified in the list of available files.
|
||||
|
||||
@@ -85,7 +85,7 @@ When plotting trends, you should make sure to sort and order the data by the x-a
|
||||
""",
|
||||
code_executor=AgentEngineSandboxCodeExecutor(
|
||||
# Replace with your sandbox resource name if you already have one. Only use it for testing or prototyping purposes, because this will use the same sandbox for all requests.
|
||||
# "projects/vertex-agent-loadtest/locations/us-central1/reasoningEngines/6842889780301135872/sandboxEnvironments/6545148628569161728",
|
||||
# "projects/PROJECT/locations/LOCATION/reasoningEngines/ENGINE_ID/sandboxEnvironments/SANDBOX_ID",
|
||||
sandbox_resource_name=None,
|
||||
# Replace with agent engine resource name used for creating sandbox environment.
|
||||
agent_engine_resource_name=None,
|
||||
|
||||
@@ -28,19 +28,9 @@ def base_system_instruction():
|
||||
|
||||
**Code Execution:** All code snippets provided will be executed within the Colab environment.
|
||||
|
||||
**Statefulness:** All code snippets are executed and the variables stays in the environment. You NEVER need to re-initialize variables. You NEVER need to reload files. You NEVER need to re-import libraries.
|
||||
**Statefulness:** Variables and imports do NOT carry over between turns. Re-create any variable, re-load any file and re-import any library that a snippet needs.
|
||||
|
||||
**Imported Libraries:** The following libraries are ALREADY imported and should NEVER be imported again:
|
||||
|
||||
```tool_code
|
||||
import io
|
||||
import math
|
||||
import re
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import scipy
|
||||
```
|
||||
**Imported Libraries:** Nothing is imported for you. Import what you need (for example `io`, `math`, `re`, `matplotlib.pyplot as plt`, `numpy as np`, `pandas as pd`, `scipy`) at the top of every snippet that uses it.
|
||||
|
||||
**Output Visibility:** Always print the output of code execution to visualize results, especially for data exploration and analysis. For example:
|
||||
- To look at the shape of a pandas.DataFrame do:
|
||||
@@ -48,7 +38,7 @@ def base_system_instruction():
|
||||
print(df.shape)
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
(49, 7)
|
||||
|
||||
```
|
||||
@@ -58,15 +48,15 @@ def base_system_instruction():
|
||||
print(f'{{x=}}')
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
x=999751168
|
||||
|
||||
```
|
||||
- You **never** generate ```tool_outputs yourself.
|
||||
- You **never** generate ```tool_output yourself.
|
||||
- You can then use this output to decide on next steps.
|
||||
- Print just variables (e.g., `print(f'{{variable=}}')`.
|
||||
|
||||
**No Assumptions:** **Crucially, avoid making assumptions about the nature of the data or column names.** Base findings solely on the data itself. Always use the information obtained from `explore_df` to guide your analysis.
|
||||
**No Assumptions:** **Crucially, avoid making assumptions about the nature of the data or column names.** Base findings solely on the data itself. Always inspect the data (its shape, dtypes and column names) before analyzing it.
|
||||
|
||||
**Available files:** Only use the files that are available as specified in the list of available files.
|
||||
|
||||
|
||||
@@ -58,13 +58,13 @@ You can run this agent using the ADK CLI.
|
||||
To interact with the agent through the command line:
|
||||
|
||||
```bash
|
||||
adk run contributing/samples/custom_code_execution "Plot a bar chart with these categories and values: {'リンゴ': 10, 'バナナ': 15, 'オレンジ': 8}. Title the chart '果物の在庫' (Fruit Stock)."
|
||||
adk run contributing/samples/code_execution/custom_code_execution "Plot a bar chart with these categories and values: {'リンゴ': 10, 'バナナ': 15, 'オレンジ': 8}. Title the chart '果物の在庫' (Fruit Stock)."
|
||||
```
|
||||
|
||||
To use the web interface:
|
||||
|
||||
```bash
|
||||
adk web contributing/samples/
|
||||
adk web contributing/samples/code_execution/
|
||||
```
|
||||
|
||||
Then select `custom_code_execution` from the list of agents and interact with
|
||||
|
||||
@@ -114,7 +114,7 @@ def base_system_instruction():
|
||||
print(df.shape)
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
(49, 7)
|
||||
|
||||
```
|
||||
@@ -124,11 +124,11 @@ def base_system_instruction():
|
||||
print(f'{{x=}}')
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
x=999751168
|
||||
|
||||
```
|
||||
- You **never** generate ```tool_outputs yourself.
|
||||
- You **never** generate ```tool_output yourself.
|
||||
- You can then use this output to decide on next steps.
|
||||
- Print just variables (e.g., `print(f'{{variable=}}')`.
|
||||
|
||||
@@ -161,5 +161,5 @@ When plotting trends, you should make sure to sort and order the data by the x-a
|
||||
|
||||
|
||||
""",
|
||||
code_executor=CustomCodeExecutor(),
|
||||
code_executor=CustomCodeExecutor(stateful=True),
|
||||
)
|
||||
|
||||
@@ -46,13 +46,13 @@ You can run this agent using the ADK CLI from the root of the repository.
|
||||
To interact with the agent through the command line:
|
||||
|
||||
```bash
|
||||
adk run contributing/samples/vertex_code_execution "Plot a sine wave from 0 to 10"
|
||||
adk run contributing/samples/code_execution/vertex_code_execution "Plot a sine wave from 0 to 10"
|
||||
```
|
||||
|
||||
To use the web interface:
|
||||
|
||||
```bash
|
||||
adk web contributing/samples/
|
||||
adk web contributing/samples/code_execution/
|
||||
```
|
||||
|
||||
Then select `vertex_code_execution` from the list of agents and interact with
|
||||
|
||||
@@ -48,7 +48,7 @@ def base_system_instruction():
|
||||
print(df.shape)
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
(49, 7)
|
||||
|
||||
```
|
||||
@@ -58,11 +58,11 @@ def base_system_instruction():
|
||||
print(f'{{x=}}')
|
||||
```
|
||||
The output will be presented to you as:
|
||||
```tool_outputs
|
||||
```tool_output
|
||||
x=999751168
|
||||
|
||||
```
|
||||
- You **never** generate ```tool_outputs yourself.
|
||||
- You **never** generate ```tool_output yourself.
|
||||
- You can then use this output to decide on next steps.
|
||||
- Print just variables (e.g., `print(f'{{variable=}}')`.
|
||||
|
||||
@@ -95,5 +95,5 @@ When plotting trends, you should make sure to sort and order the data by the x-a
|
||||
|
||||
|
||||
""",
|
||||
code_executor=VertexAiCodeExecutor(),
|
||||
code_executor=VertexAiCodeExecutor(stateful=True),
|
||||
)
|
||||
|
||||
@@ -28,14 +28,14 @@ The server should be accessible at `http://localhost:3000/sse`.
|
||||
## Running the Demo
|
||||
|
||||
```bash
|
||||
adk web contributing/samples
|
||||
adk web contributing/samples/mcp
|
||||
```
|
||||
|
||||
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:
|
||||
The agents do not set a model, so they use the ADK default. Try these prompts:
|
||||
|
||||
1. **Check available tools:**
|
||||
|
||||
|
||||
@@ -28,14 +28,14 @@ This happens automatically via the stdio connection when the agent starts.
|
||||
## Running the Demo
|
||||
|
||||
```bash
|
||||
adk web contributing/samples
|
||||
adk web contributing/samples/mcp
|
||||
```
|
||||
|
||||
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:
|
||||
The agents do not set a model, so they use the ADK default. Try these prompts:
|
||||
|
||||
1. **Check available tools:**
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ Once the agent is running, try these queries:
|
||||
|
||||
The agent uses:
|
||||
|
||||
- **Model**: Gemini 2.0 Flash
|
||||
- **Model**: the ADK default model (the agent does not set `model`)
|
||||
- **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
|
||||
|
||||
@@ -17,7 +17,7 @@ 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.adk.tools.mcp_tool.mcp_toolset import McpToolset
|
||||
from google.genai.types import GenerateContentConfig
|
||||
from mcp import StdioServerParameters
|
||||
|
||||
@@ -38,7 +38,7 @@ root_agent = LlmAgent(
|
||||
"the PostgreSQL database. Ask clarifying questions when unsure."
|
||||
),
|
||||
tools=[
|
||||
MCPToolset(
|
||||
McpToolset(
|
||||
connection_params=StdioConnectionParams(
|
||||
server_params=StdioServerParameters(
|
||||
command="uvx",
|
||||
|
||||
@@ -36,7 +36,7 @@ do not send progress updates. This sample uses a mock server that demonstrates
|
||||
progress reporting.
|
||||
|
||||
Usage:
|
||||
adk run contributing/samples/mcp_progress_callback_agent
|
||||
adk run contributing/samples/mcp/mcp_progress_callback_agent
|
||||
|
||||
Then try:
|
||||
"Run the long running task with 5 steps"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
# FastMCP Server-Side Sampling with ADK
|
||||
|
||||
This project demonstrates how to use server-side sampling with a `fastmcp` server connected to an ADK `MCPToolset`.
|
||||
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. **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 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.
|
||||
@@ -41,10 +41,10 @@ pip install fastmcp openai litellm
|
||||
|
||||
### 3. Run the Example
|
||||
|
||||
Navigate to the `samples` directory and choose this ADK agent:
|
||||
Run this ADK agent:
|
||||
|
||||
```bash
|
||||
adk web .
|
||||
adk run contributing/samples/mcp/mcp_server_side_sampling
|
||||
```
|
||||
|
||||
The agent will automatically start the FastMCP server in the background.
|
||||
|
||||
Executable → Regular
+10
-6
@@ -13,10 +13,11 @@
|
||||
# limitations under the License.
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
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 import McpToolset
|
||||
from google.adk.tools.mcp_tool.mcp_session_manager import StdioConnectionParams
|
||||
from mcp import StdioServerParameters
|
||||
|
||||
@@ -27,17 +28,20 @@ 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.
|
||||
# as a subprocess. The script is addressed by absolute path because the
|
||||
# subprocess inherits the working directory of the ADK process, which is not
|
||||
# this directory. The OPENAI_API_KEY is passed to the server's environment.
|
||||
_current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
server_params = StdioServerParameters(
|
||||
command='python',
|
||||
args=['mcp_server.py'],
|
||||
command=sys.executable, # Use current Python interpreter
|
||||
args=[os.path.join(_current_dir, 'mcp_server.py')],
|
||||
env={'OPENAI_API_KEY': api_key},
|
||||
)
|
||||
|
||||
# Create the ADK MCPToolset, which connects to the FastMCP server.
|
||||
# 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(
|
||||
mcp_toolset = McpToolset(
|
||||
connection_params=StdioConnectionParams(
|
||||
server_params=server_params,
|
||||
),
|
||||
|
||||
@@ -22,7 +22,7 @@ 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
|
||||
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"
|
||||
@@ -34,7 +34,7 @@ root_agent = LlmAgent(
|
||||
Help the user with the tools available to you.
|
||||
""",
|
||||
tools=[
|
||||
MCPToolset(
|
||||
McpToolset(
|
||||
connection_params=StreamableHTTPServerParams(
|
||||
url=MCP_SERVER_URL,
|
||||
),
|
||||
|
||||
Executable → Regular
+3
-7
@@ -21,7 +21,7 @@ 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.mcp_tool.mcp_toolset import McpToolset
|
||||
from google.adk.tools.tool_context import ToolContext
|
||||
|
||||
# Configure logging; the mcp_tool logger must be set to
|
||||
@@ -59,7 +59,7 @@ root_agent = LlmAgent(
|
||||
prompt_name='file_system_prompt',
|
||||
),
|
||||
tools=[
|
||||
MCPToolset(
|
||||
McpToolset(
|
||||
connection_params=connection_params,
|
||||
# don't want agent to do write operation
|
||||
# you can also do below
|
||||
@@ -72,12 +72,8 @@ root_agent = LlmAgent(
|
||||
# ],
|
||||
tool_filter=[
|
||||
'read_file',
|
||||
'read_multiple_files',
|
||||
'list_directory',
|
||||
'directory_tree',
|
||||
'search_files',
|
||||
'get_file_info',
|
||||
'list_allowed_directories',
|
||||
'get_cwd',
|
||||
],
|
||||
require_confirmation=True,
|
||||
)
|
||||
|
||||
@@ -21,6 +21,18 @@ This will generate:
|
||||
- `client.crt`, `client.key` (Client certificate/key)
|
||||
- `certificate_config.json` (Workload certificate configuration for `google-auth`)
|
||||
|
||||
### 2. Application Default Credentials
|
||||
|
||||
ADK builds the mTLS transport through `google.auth.default()`, so the client also
|
||||
needs Application Default Credentials:
|
||||
|
||||
```bash
|
||||
gcloud auth application-default login
|
||||
```
|
||||
|
||||
Without them the mTLS setup fails silently: ADK logs a warning, connects with
|
||||
plain TLS and no client certificate, and the server rejects the handshake.
|
||||
|
||||
______________________________________________________________________
|
||||
|
||||
## Running the Sample
|
||||
@@ -51,7 +63,8 @@ cd adk-python
|
||||
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
|
||||
cat "$(python -c 'import ssl; print(ssl.get_default_verify_paths().cafile)')" \
|
||||
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
|
||||
|
||||
@@ -18,7 +18,7 @@ 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
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
|
||||
|
||||
connection_params = SseConnectionParams(
|
||||
url=os.environ.get('MCP_SERVER_URL', 'https://localhost:3000/sse'),
|
||||
@@ -33,7 +33,7 @@ root_agent = LlmAgent(
|
||||
prompt_name='file_system_prompt',
|
||||
),
|
||||
tools=[
|
||||
MCPToolset(
|
||||
McpToolset(
|
||||
connection_params=connection_params,
|
||||
tool_filter=[
|
||||
'read_file',
|
||||
|
||||
@@ -17,7 +17,8 @@ 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 McpToolset
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import StdioConnectionParams
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import StdioServerParameters
|
||||
|
||||
load_dotenv()
|
||||
@@ -36,11 +37,13 @@ root_agent = LlmAgent(
|
||||
"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},
|
||||
McpToolset(
|
||||
connection_params=StdioConnectionParams(
|
||||
server_params=StdioServerParameters(
|
||||
command="npx",
|
||||
args=["-y", "@notionhq/notion-mcp-server"],
|
||||
env={"OPENAPI_MCP_HEADERS": NOTION_HEADERS},
|
||||
),
|
||||
)
|
||||
)
|
||||
],
|
||||
|
||||
Executable → Regular
+2
-2
@@ -17,7 +17,7 @@ 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 google.adk.tools.mcp_tool.mcp_toolset import McpToolset
|
||||
from mcp import StdioServerParameters
|
||||
|
||||
_allowed_path = os.path.dirname(os.path.abspath(__file__))
|
||||
@@ -30,7 +30,7 @@ Help user accessing their file systems.
|
||||
Allowed directory: {_allowed_path}
|
||||
""",
|
||||
tools=[
|
||||
MCPToolset(
|
||||
McpToolset(
|
||||
connection_params=StdioConnectionParams(
|
||||
server_params=StdioServerParameters(
|
||||
command='npx',
|
||||
|
||||
@@ -13,23 +13,17 @@
|
||||
# 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__))
|
||||
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset
|
||||
|
||||
root_agent = LlmAgent(
|
||||
name='enterprise_assistant',
|
||||
instruction=f"""\
|
||||
instruction="""\
|
||||
Help user accessing their file systems.
|
||||
|
||||
Allowed directory: {_allowed_path}
|
||||
""",
|
||||
tools=[
|
||||
MCPToolset(
|
||||
McpToolset(
|
||||
connection_params=StreamableHTTPServerParams(
|
||||
url='http://localhost:3000/mcp',
|
||||
),
|
||||
@@ -44,12 +38,8 @@ Allowed directory: {_allowed_path}
|
||||
# ],
|
||||
tool_filter=[
|
||||
'read_file',
|
||||
'read_multiple_files',
|
||||
'list_directory',
|
||||
'directory_tree',
|
||||
'search_files',
|
||||
'get_file_info',
|
||||
'list_allowed_directories',
|
||||
'get_cwd',
|
||||
],
|
||||
use_mcp_resources=True,
|
||||
)
|
||||
|
||||
@@ -21,13 +21,13 @@ The toolset authentication flow works in two phases:
|
||||
1. Start the MCP server in one terminal:
|
||||
|
||||
```bash
|
||||
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/oauth_mcp_server.py
|
||||
PYTHONPATH=src python contributing/samples/mcp/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
|
||||
PYTHONPATH=src python contributing/samples/mcp/mcp_toolset_auth/main.py
|
||||
```
|
||||
|
||||
## Expected Behavior
|
||||
@@ -41,7 +41,7 @@ PYTHONPATH=src python contributing/samples/mcp_toolset_auth/main.py
|
||||
You can also test with the ADK web UI:
|
||||
|
||||
```bash
|
||||
adk web contributing/samples/mcp_toolset_auth
|
||||
adk web contributing/samples/mcp/mcp_toolset_auth
|
||||
```
|
||||
|
||||
Note: The web UI will display the auth request and you'll need to manually provide credentials.
|
||||
|
||||
@@ -22,10 +22,10 @@ This script demonstrates the two-phase tool discovery flow:
|
||||
|
||||
Usage:
|
||||
# Start the MCP server first (in another terminal):
|
||||
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/oauth_mcp_server.py
|
||||
PYTHONPATH=src python contributing/samples/mcp/mcp_toolset_auth/oauth_mcp_server.py
|
||||
|
||||
# Run the demo:
|
||||
PYTHONPATH=src python contributing/samples/mcp_toolset_auth/main.py
|
||||
PYTHONPATH=src python contributing/samples/mcp/mcp_toolset_auth/main.py
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -23,11 +23,13 @@ This is used to test the toolset authentication feature in ADK.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections.abc import AsyncIterator
|
||||
import contextlib
|
||||
import logging
|
||||
|
||||
from fastapi import FastAPI
|
||||
from fastapi import HTTPException
|
||||
from fastapi import Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from mcp.server.fastmcp import Context
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
import uvicorn
|
||||
@@ -95,8 +97,22 @@ def list_users(context: Context) -> dict:
|
||||
}
|
||||
|
||||
|
||||
# FastMCP's own Starlette app is what serves the /mcp endpoint, so mounting it
|
||||
# under a FastAPI app is what puts the auth middleware in front of every MCP
|
||||
# request, tool listing included. A mounted app's lifespan is not run by the
|
||||
# mount, so the session manager the endpoint depends on is started from the
|
||||
# FastAPI lifespan instead.
|
||||
mcp_app = mcp.streamable_http_app()
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
||||
async with mcp.session_manager.run():
|
||||
yield
|
||||
|
||||
|
||||
# Create custom FastAPI app to add auth middleware for list_tools
|
||||
app = FastAPI()
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
|
||||
|
||||
@app.middleware('http')
|
||||
@@ -105,16 +121,20 @@ async def auth_middleware(request: Request, call_next):
|
||||
# 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')
|
||||
# Returned rather than raised: an exception from HTTP middleware escapes
|
||||
# the exception handlers and becomes a 500.
|
||||
return JSONResponse(status_code=401, content={'detail': 'Unauthorized'})
|
||||
return await call_next(request)
|
||||
|
||||
|
||||
app.mount('/', mcp_app)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(f'Starting OAuth Protected MCP server on http://localhost:3001')
|
||||
print('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')
|
||||
uvicorn.run(app, host='localhost', port=3001)
|
||||
|
||||
@@ -21,12 +21,13 @@ instruction: |
|
||||
# Declaring a stdio MCP server launches `command` as a local process when this
|
||||
# config loads, so it requires ADK_ALLOW_CONFIG_STDIO_MCP_SERVERS=1. See README.
|
||||
tools:
|
||||
- name: MCPToolset
|
||||
- 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"}'
|
||||
stdio_connection_params:
|
||||
server_params:
|
||||
command: "npx"
|
||||
args:
|
||||
- "-y"
|
||||
- "@notionhq/notion-mcp-server"
|
||||
env:
|
||||
OPENAPI_MCP_HEADERS: '{"Authorization": "Bearer <your_notion_token>", "Notion-Version": "2022-06-28"}'
|
||||
|
||||
Reference in New Issue
Block a user