对于需要用户同意的 MCP 服务器(例如 Gmail、Google Calendar 或其他通过委托访问的服务),请使用 OAuth。
当你调用需要用户认证的 MCP 服务器时,就会触发 OAuth 流程。
Request Without Token
SDK 调用 MCP 服务器。如果不存在有效的 token,服务器会返回带有 WWW-Authenticate 头的 401。
Protected Resource Discovery
SDK 请求 /.well-known/oauth-protected-resource(RFC 9728),以发现授权服务器及其支持的 scopes。
AuthenticationError
SDK 抛出包含 connect_url 的 AuthenticationError——即完整的 OAuth 授权 URL。
Browser Interaction
你的应用在用户的浏览器中打开 connect_url。用户登录并授予(或拒绝)所请求的 scopes。
Token Exchange
获得授权后,授权服务器使用 PKCE 将授权码交换为令牌。DAuth 在服务器端存储这些令牌。
Retry Request
用户返回到你的应用并触发重试。SDK 会重新发送请求,此时已携带有效的凭证。
Authenticated Requests
在后续发送到 MCP 服务器的请求中,访问令牌会被自动附加到请求中。
Token Refresh
如果访问令牌过期,DAuth 会自动使用刷新令牌获取新的访问令牌。
## 工作原理
## OAuth 重试助手
使用带重试机制的封装来处理 OAuth 流程:
import webbrowser
from collections.abc import Awaitable, Callable
from typing import TypeVar
from dedalus_labs import AuthenticationError
T = TypeVar("T")
async def with_oauth_retry(fn: Callable[[], Awaitable[T]]) -> T:
"""运行异步函数,必要时处理 OAuth 浏览器流程。"""
try:
return await fn()
except AuthenticationError as e:
body = e.body if isinstance(e.body, dict) else {}
url = body.get("connect_url") or body.get("detail", {}).get("connect_url")
if not url:
raise
print("\n正在打开浏览器进行 OAuth 认证...")
print("如果浏览器未自动打开,请访问:\n")
print(url)
webbrowser.open(url)
input("\n完成 OAuth 认证后按 Enter 键...")
return await fn()
## 完整示例:DedalusRunner
import asyncio
import webbrowser
from collections.abc import Awaitable, Callable
from typing import TypeVar
from dotenv import load_dotenv
load_dotenv()
from dedalus_labs import AsyncDedalus, AuthenticationError, DedalusRunner
T = TypeVar("T")
async def with_oauth_retry(fn: Callable[[], Awaitable[T]]) -> T:
try:
return await fn()
except AuthenticationError as e:
body = e.body if isinstance(e.body, dict) else {}
url = body.get("connect_url") or body.get("detail", {}).get("connect_url")
if not url:
raise
webbrowser.open(url)
input("\n完成 OAuth 后按 Enter 键...")
return await fn()
async def main():
client = AsyncDedalus()
runner = DedalusRunner(client)
result = await with_oauth_retry(
lambda: runner.run(
input="列出我最近的邮件并进行总结",
model="openai/gpt-4.1",
mcp_servers=["anny_personal/gmail-mcp"],
)
)
print(result.output)
if result.mcp_results:
for r in result.mcp_results:
print(f"{r.tool_name} ({r.duration_ms}ms): {r.result}")
asyncio.run(main())
## 完整示例:原始 Client
适用于需要对应用程序编程接口 API 响应进行完全控制的单次请求:
import asyncio
import webbrowser
from collections.abc import Awaitable, Callable
from typing import TypeVar
from dotenv import load_dotenv
load_dotenv()
from dedalus_labs import AsyncDedalus, AuthenticationError
T = TypeVar("T")
async def with_oauth_retry(fn: Callable[[], Awaitable[T]]) -> T:
try:
return await fn()
except AuthenticationError as e:
body = e.body if isinstance(e.body, dict) else {}
url = body.get("connect_url") or body.get("detail", {}).get("connect_url")
if not url:
raise
webbrowser.open(url)
input("\n完成 OAuth 后按 Enter...")
return await fn()
async def main():
client = AsyncDedalus()
async def do_request():
return await client.chat.completions.create(
model="openai/gpt-4.1",
messages=[
{
"role": "user",
"content": "列出我最近的邮件并进行总结",
}
],
mcp_servers=["anny_personal/gmail-mcp"],
)
resp = await with_oauth_retry(do_request)
print(resp.choices[0].message.content)
if resp.mcp_tool_results:
for r in resp.mcp_tool_results:
print(f"{r.tool_name} ({r.duration_ms}ms): {r.result}")
asyncio.run(main())
## 环境
# .env
DEDALUS_API_KEY=dsk-live-...
DEDALUS_API_URL=https://api.dedaluslabs.ai
DEDALUS_AS_URL=https://as.dedaluslabs.ai
客户端无需配置 OAuth 凭证。OAuth 配置由 MCP 服务器处理,DAuth 负责管理令牌的存储。
适用场景
OAuth 适用于:
- 面向用户的应用程序
- 委托访问(代表用户执行操作)
- Gmail、Google Calendar、Linear、GitHub 等服务
在以下场景请改用 Bearer Auth:
- API 密钥和服务令牌
- 无用户上下文的后端集成
- 服务到服务的调用
Last modified on April 11, 2026