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:
George Weale
2026-08-12 11:15:40 -07:00
committed by Copybara-Service
parent 2353dde8e9
commit c840dbe991
25 changed files with 125 additions and 108 deletions
@@ -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.
+10 -6
View File
@@ -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,
),
+3 -7
View File
@@ -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},
),
)
)
],
+2 -2
View File
@@ -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"}'