Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions examples/mcp_oauth_agent/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# mcp_oauth_agent

An agent whose MCP tool is guarded by **ADK-native OAuth (OIDC)**. When the MCP
server rejects a `tools/call` with `401` until a bearer token is presented,
google-adk runs the OAuth flow for you: it emits an `adk_request_credential`
event with an authorize URL, the `veadk frontend` web UI shows an authorization
card, the user logs in, and ADK exchanges the code for a token and retries the
call with `Authorization: Bearer <token>`.

Only two `McpToolset` arguments turn this on:

```python
McpToolset(
connection_params=StreamableHTTPConnectionParams(url=...),
auth_scheme=OpenIdConnectWithConfig(authorization_endpoint=..., token_endpoint=..., scopes=[...]),
auth_credential=AuthCredential(
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
oauth2=OAuth2Auth(client_id=..., client_secret=..., redirect_uri=...),
),
)
```

## Setup

1. Register an OAuth client with your OIDC provider (e.g. a Volcengine user
pool). Add your web UI's origin to its allowed redirect URIs so the popup can
capture the callback on its own origin — `http://localhost:8000/` for a
default `veadk frontend`, or `http://localhost:5173/` when running `--dev`.
2. Provide the settings via environment (a `.env` file next to your run works):

```dotenv
MCP_OAUTH_URL=https://<your-mcp-server>/mcp
OIDC_ISSUER=https://<your-user-pool>/ # authorize/token derived from this
OIDC_CLIENT_ID=<client id>
OIDC_CLIENT_SECRET=<client secret>
OIDC_REDIRECT_URI=http://localhost:8000/ # http://localhost:5173/ with --dev
# optional:
# OIDC_SCOPES=openid profile email
# OIDC_AUTHORIZATION_ENDPOINT=https://.../authorize
# OIDC_TOKEN_ENDPOINT=https://.../oauth/token
```

The issuer's endpoints can be read from
`<issuer>/.well-known/openid-configuration`.

3. Serve it and open the UI (`http://localhost:8000/`):

```bash
veadk frontend --agents-dir examples --port 8000
```

Pick **mcp_oauth_agent**, ask something that needs a tool, and complete the
authorization card when it appears. (Add `--dev` to develop the frontend from
the Vite server on :5173 — then set `OIDC_REDIRECT_URI=http://localhost:5173/`.)

## Notes

- `client_secret` stays on the server that runs the agent; it is never sent to
the browser. Sharing the running service is fine — each user logs in with
their own account.
- The redirect URI must exactly match one registered on the OAuth client, or the
provider returns `redirect_uri mismatch`.
17 changes: 17 additions & 0 deletions examples/mcp_oauth_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# 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

__all__ = ["agent"]
95 changes: 95 additions & 0 deletions examples/mcp_oauth_agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# 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 whose MCP tool is protected by ADK-native OAuth (OIDC).

Some MCP servers enforce inbound auth: a `tools/call` returns 401 unless an
`Authorization: Bearer <token>` from an OAuth/OIDC provider is present. Instead
of any provider-specific SDK, this uses google-adk's built-in OAuth2 handling —
give the toolset an OpenID Connect scheme plus client credentials and, on the
first tool call, ADK emits an `adk_request_credential` event carrying an
authorize URL. The web UI (`veadk frontend`) renders an authorization card; the
user logs in at the provider; ADK exchanges the returned code for a token and
replays the call with the bearer header. No extra client code is required.

Configure via environment variables (e.g. in a `.env` file):

MCP_OAUTH_URL the MCP server URL (StreamableHTTP), including any path
OIDC_ISSUER the provider's issuer, e.g. https://<pool>.../ — the
authorize/token endpoints are derived from it, or set
them explicitly with the two vars below
OIDC_AUTHORIZATION_ENDPOINT (optional) overrides "<issuer>/authorize"
OIDC_TOKEN_ENDPOINT (optional) overrides "<issuer>/oauth/token"
OIDC_SCOPES (optional) space-separated, default "openid profile email"
OIDC_CLIENT_ID OAuth client id registered with the provider
OIDC_CLIENT_SECRET its client secret
OIDC_REDIRECT_URI a redirect URI registered on that client; default
http://localhost:8000/ — where `veadk frontend` serves
the UI — so the popup captures the ?code=... callback on
its own origin (use http://localhost:5173/ with `--dev`)
"""

import os

from google.adk.auth import AuthCredential, AuthCredentialTypes
from google.adk.auth.auth_credential import OAuth2Auth
from google.adk.auth.auth_schemes import OpenIdConnectWithConfig
from google.adk.tools.mcp_tool.mcp_session_manager import (
StreamableHTTPConnectionParams,
)
from google.adk.tools.mcp_tool.mcp_toolset import McpToolset

from veadk import Agent

_issuer = os.getenv("OIDC_ISSUER", "").rstrip("/")
_auth_endpoint = os.getenv("OIDC_AUTHORIZATION_ENDPOINT") or f"{_issuer}/authorize"
_token_endpoint = os.getenv("OIDC_TOKEN_ENDPOINT") or f"{_issuer}/oauth/token"
_scopes = os.getenv("OIDC_SCOPES", "openid profile email").split()

auth_scheme = OpenIdConnectWithConfig(
authorization_endpoint=_auth_endpoint,
token_endpoint=_token_endpoint,
scopes=_scopes,
)

auth_credential = AuthCredential(
auth_type=AuthCredentialTypes.OPEN_ID_CONNECT,
oauth2=OAuth2Auth(
client_id=os.getenv("OIDC_CLIENT_ID", ""),
client_secret=os.getenv("OIDC_CLIENT_SECRET", ""),
redirect_uri=os.getenv("OIDC_REDIRECT_URI", "http://localhost:8000/"),
),
)

mcp_tool = McpToolset(
connection_params=StreamableHTTPConnectionParams(
url=os.getenv("MCP_OAUTH_URL", "")
),
auth_scheme=auth_scheme,
auth_credential=auth_credential,
)

agent = Agent(
name="mcp_oauth_agent",
description="An agent whose MCP tool is protected by ADK-native OAuth (OIDC).",
instruction=(
"You can call the connected MCP tools to help the user. Call the "
"relevant tool directly to obtain real results; do not ask the user "
"whether they are authorized — the authorization flow is started "
"automatically when a tool requires it."
),
tools=[mcp_tool],
)

root_agent = agent
17 changes: 17 additions & 0 deletions examples/web_search_agent/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# 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

__all__ = ["agent"]
35 changes: 35 additions & 0 deletions examples/web_search_agent/agent.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Copyright (c) 2025 Beijing Volcano Engine Technology Co., Ltd. and/or its affiliates.
#
# 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.

"""A plain conversational agent with the built-in web_search tool.
Servable by the ADK API server (exposes a module-level `root_agent`). Needs
Volcengine AK/SK in the environment for web_search.
"""

from veadk import Agent
from veadk.tools.builtin_tools.web_search import web_search

agent = Agent(
name="web_search_agent",
description="通用助手,可联网搜索实时信息。",
instruction=(
"你是一个有用的中文助手。当用户的问题需要实时或最新信息时,先调用 "
"web_search 工具检索,再基于结果回答;否则直接用自然语言回答。"
),
tools=[web_search],
)

# Required by the Google ADK agent loader.
root_agent = agent
Loading
Loading