MCP 服务器可能需要 OAuth 2.1 令牌。你可以选择使用 DAuth(Dedalus Auth)实现具备凭据隔离的托管认证,或接入你自己的授权服务器。
DAuth 是 Dedalus 提供的托管授权系统。它提供符合 OAuth 2.1 标准的令牌签发能力,并具备一个关键的安全特性:凭证绝不会离开封闭的执行边界。
传统的凭证处理方式会将密钥等机密暴露给你的应用代码。DAuth 将凭证隔离在安全飞地中——你的 MCP 服务器只会接收到不透明的 Connection 句柄,而不是原始的 API 密钥。
- 凭证从不暴露 —— 在客户端加密,只在封闭的执行边界内解密
- 不透明句柄 —— 你的代码仅通过句柄引用 Connection,从不直接接触原始机密
- 发送方绑定令牌 —— 令牌在密码学层面绑定到 Client;被窃取的令牌无法使用
- 无网络执行 —— 凭证解密和 API 调用完全在隔离飞地中完成;原始机密永远不会在网络上传输
DAuth 的工作原理
了解凭证隔离和封闭执行如何保护你的机密。
from dedalus_mcp import MCPServer
from dedalus_mcp.server import AuthorizationConfig
server = MCPServer(
"protected-server",
authorization=AuthorizationConfig(
enabled=True,
required_scopes=["read"],
),
)
默认情况下,authorization_servers 指向 https://as.dedaluslabs.ai(DAuth 控制平面)。
若要查看包含 GitHub 和 Supabase 集成的完整示例:
示例 MCP 服务器
可直接用于生产环境的 MCP 服务器,集成了 GitHub 和 Supabase。
未通过身份验证的请求会返回 401,并带有 WWW-Authenticate 质询头,指向受保护资源的元数据。
所有请求都必须包含这些 scope。Scope 名称是你自定义的任意字符串——常见模式包括用于通用访问的 read/write,或用于细粒度控制的 resource:action(例如 files:delete)。
authorization=AuthorizationConfig(
enabled=True,
required_scopes=["read", "write"], # 所有 tool 都需要
通过附加的作用域要求来限制对敏感工具的访问:
from dedalus_mcp import tool
@tool(description="List files")
def list_files(path: str) -> list[str]:
return os.listdir(path) # 无需额外的作用域
@tool(description="Delete file", required_scopes=["files:delete"])
def delete_file(path: str) -> dict:
os.remove(path)
return {"deleted": path}
具有 read 权限的 token 可以调用 list_files。在没有 files:delete 权限的情况下调用 delete_file 会返回错误:
{
"isError": true,
"content": [{
"type": "text",
"text": "Tool \"delete_file\" requires scopes: ['files:delete']. Missing: ['files:delete']"
}]
}
AuthorizationConfig(
enabled=True,
authorization_servers=["https://as.dedaluslabs.ai"], # DAuth(默认)
required_scopes=["read"],
metadata_path="/.well-known/oauth-protected-resource",
cache_ttl=300,
fail_open=False,
)
| 选项 | 默认值 | 描述 |
|---|
enabled | False | 启用授权校验与强制执行 |
authorization_servers | ["https://as.dedaluslabs.ai"] | DAuth 或自定义 OAuth AS URL |
required_scopes | [] | 所有请求统一要求的 scopes |
metadata_path | /.well-known/oauth-protected-resource | PRM 端点路径 |
cache_ttl | 300 | 元数据缓存时长 |
fail_open | False | 验证失败时是否仍然允许请求通过 |
在你的工具中检查已认证用户信息:
from dedalus_mcp import tool, get_context
@tool(description="获取当前用户")
def whoami() -> dict:
ctx = get_context()
auth = ctx.auth # AuthorizationContext or None
if auth is None:
return {"user": "anonymous"}
return {
"subject": auth.subject,
"scopes": auth.scopes,
"claims": auth.claims,
}
DAuth 默认使用 DPoP(Demonstrating Proof-of-Possession,持有证明)。令牌会以加密方式绑定到 Client 的密钥——即使令牌被窃取,没有对应的私钥也无法使用。
server = MCPServer(
"dpop-server",
authorization=AuthorizationConfig(
enabled=True,
dpop_required=True,
),
)
请记得将这些变量添加到你的运行环境中。由于 DAuth 与 Dedalus SDK 实现了原生集成,因此需要一个 API 密钥。你可以在控制台中获取你的 API 密钥。
# Dedalus 平台(用于 _client.py 测试)
DEDALUS_API_KEY=dsk-live-...
DEDALUS_API_URL=https://api.dedaluslabs.ai
DEDALUS_AS_URL=https://as.dedaluslabs.ai
使用你自己的 OAuth 2.1 提供商,而不是 DAuth。这在集成现有身份管理基础设施时非常有用。
外部授权服务器不提供密封执行模型。你的 MCP 服务器将直接处理凭据。
from dedalus_mcp.server import AuthorizationConfig, JWTValidatorConfig, JWTValidator
server = MCPServer(
"my-server",
authorization=AuthorizationConfig(
enabled=True,
authorization_servers=["https://auth.mycompany.com"],
required_scopes=["api:access"],
),
)
# 为您的提供方配置 JWT 验证
jwt_config = JWTValidatorConfig(
jwks_uri="https://auth.mycompany.com/.well-known/jwks.json",
issuer="https://auth.mycompany.com",
audience="https://my-mcp-server.example.com",
)
server.set_authorization_provider(JWTValidator(jwt_config))
jwt_config = JWTValidatorConfig(
jwks_uri="https://YOUR_DOMAIN.auth0.com/.well-known/jwks.json",
issuer="https://YOUR_DOMAIN.auth0.com/",
audience="https://my-mcp-api",
)
jwt_config = JWTValidatorConfig(
jwks_uri="https://YOUR_DOMAIN.okta.com/oauth2/default/v1/keys",
issuer="https://YOUR_DOMAIN.okta.com/oauth2/default",
audience="api://my-mcp-server",
)
jwt_config = JWTValidatorConfig(
jwks_uri="https://keycloak.example.com/realms/myrealm/protocol/openid-connect/certs",
issuer="https://keycloak.example.com/realms/myrealm",
audience="my-mcp-client",
)
使用完整的服务器和 Client 来测试授权:
import pytest
from dedalus_mcp import MCPServer, tool
from dedalus_mcp.server import AuthorizationConfig
from dedalus_mcp.client import MCPClient, BearerAuth
@tool(description="删除文件", required_scopes=["files:delete"])
def delete_file(path: str) -> dict:
return {"deleted": path}
@pytest.fixture
async def protected_server():
server = MCPServer(
"test",
authorization=AuthorizationConfig(enabled=True, required_scopes=["read"]),
)
server.collect(delete_file)
task = asyncio.create_task(server.serve())
await asyncio.sleep(0.1)
yield server
task.cancel()
async def test_with_valid_token(protected_server):
# Use a test token with required scopes
client = await MCPClient.connect(
"http://127.0.0.1:8000/mcp",
auth=BearerAuth(access_token="test-token-with-scopes")
)
result = await client.call_tool("delete_file", {"path": "/tmp/test.txt"})
await client.close()