跳转到主要内容
MCP 服务器可能需要 OAuth 2.1 令牌。你可以选择使用 DAuth(Dedalus Auth)实现具备凭据隔离的托管认证,或接入你自己的授权服务器。

DAuth(Dedalus Auth)

DAuth 是 Dedalus 提供的托管授权系统。它提供符合 OAuth 2.1 标准的令牌签发能力,并具备一个关键的安全特性:凭证绝不会离开封闭的执行边界

为什么选择 DAuth?

传统的凭证处理方式会将密钥等机密暴露给你的应用代码。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。Scope 名称是你自定义的任意字符串——常见模式包括用于通用访问的 read/write,或用于细粒度控制的 resource:action(例如 files:delete)。
authorization=AuthorizationConfig(
    enabled=True,
    required_scopes=["read", "write"],  # 所有 tool 都需要

针对每个 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,
)
选项默认值描述
enabledFalse启用授权校验与强制执行
authorization_servers["https://as.dedaluslabs.ai"]DAuth 或自定义 OAuth AS URL
required_scopes[]所有请求统一要求的 scopes
metadata_path/.well-known/oauth-protected-resourcePRM 端点路径
cache_ttl300元数据缓存时长
fail_openFalse验证失败时是否仍然允许请求通过

在工具中访问声明(claims)

在你的工具中检查已认证用户信息:
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,
    }

DPoP 支持

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))

Auth0

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",
)

Okta

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",
)

Keycloak

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()