mcp-agent OAuth 预授权实战:用workflows-store-credentials为异步工作流预置凭据
【免费下载链接】mcp-agentBuild effective agents using Model Context Protocol and simple workflow patterns项目地址: https://gitcode.com/GitHub_Trending/mc/mcp-agent
mcp-agent 的examples/oauth/pre_authorize示例展示了一种关键的 OAuth 集成模式:在异步(Temporal)工作流真正运行之前,由客户端通过workflows-store-credentials工具把访问令牌缓存进 token store,使后续的工作流在执行时无需任何用户交互即可访问受保护的下游 MCP 服务器。本文以该示例为骨架,结合仓库中服务端工具、OAuth token manager 与配置模型的源码实现,完整讲解预授权模式的配置、运行步骤与底层原理,读完你可以在自己的项目里复刻这套「先种凭据、后跑后台工作流」的认证方案。
为什么需要「预授权」:后台工作流的认证困境
常规的 OAuth 授权流程(如 examples/oauth/interactive_tool 所演示的)需要在授权回调时弹出浏览器、等待用户确认。但对于部署在 Temporal worker 上的异步、长时间运行的工作流(例如examples/oauth/pre_authorize使用的execution_engine: temporal),执行环境往往:
- 没有交互式终端,无法唤起浏览器;
- 在独立进程中运行,用户不可能在工作流执行中途点击授权按钮。
解决思路就是预授权(pre-authorization):在正式运行工作流之前,先由一个具备用户上下文的客户端把 OAuth 令牌「种」进共享的 token store(内存或 Redis),工作流启动后从 store 中读取缓存的令牌,直接访问下游服务器。server-authentication.mdx 将这一模式概括为三步:带外获取令牌 → 调用workflows-store-credentials存入令牌 → 运行工作流读取缓存令牌,全程不再弹出任何授权提示。
示例整体架构
本示例共包含四个文件,各自职责如下:
| 文件 | 角色 |
|---|---|
| main.py | 基于 FastMCP 的工作流服务器,注册github_org_search工具与 Temporal 工作流任务,以 SSE 方式对外提供服务 |
| client.py | 客户端:先调用workflows-store-credentials预置令牌,再调用github_org_search工作流 |
| worker.py | Temporal worker,负责真正执行工作流与活动(activity) |
| mcp_agent.config.yaml | 服务端配置:Temporal 执行引擎、OAuth 参数、GitHub MCP 服务器声明 |
| mcp_agent.secrets.yaml.example | 密钥模板,需要复制为mcp_agent.secrets.yaml并填入真实凭据 |
数据流大致为:client.py→ SSE 连接main.py启动的pre_authorize_server→ 调用workflows-store-credentials把 GitHub 令牌写入 token store → 调用github_org_search工具 → 该工具经app.executor把github_org_search_activity调度到 Temporal → 活动通过gen_client连接 GitHub MCP 服务器,使用已缓存的令牌查询仓库。
前置条件与运行准备
1. 配置 GitHub OAuth 客户端凭据
先复制密钥模板并填充你的 GitHub OAuth App 凭据:
cp examples/oauth/pre_authorize/mcp_agent.secrets.yaml.example examples/oauth/pre_authorize/mcp_agent.secrets.yaml编辑复制出来的文件,使其包含 OAuth App 的client_id与client_secret(mcp_agent.secrets.yaml.example):
mcp: servers: github: auth: oauth: client_id: "your-github-client-id" client_secret: "your-github-client-secret" access_token: "your-github-access-token"也可以不写文件,改用同名的环境变量注入,效果等同。
2. 获取 GitHub 访问令牌并导出
可以通过 examples/oauth 下的交互式示例先走一遍完整授权拿到新令牌,也可以在 GitHub 的 Settings → Developer settings → Personal access tokens 中创建。示例代码(client.py)会校验令牌前缀是否为gho_、ghp_或github_pat_三者之一,并在缺失时打印获取指引。拿到后导出环境变量:
export GITHUB_ACCESS_TOKEN="github_pat_xxx"3. 安装依赖
从仓库根目录安装项目及可选 Redis 支持:
pip install -e . # 可选:Redis 后端支持 # pip install -e .[redis]4.(可选)启用 Redis 持久化
默认令牌只保存在内存中,进程重启即丢失。如需持久化(例如多个 Temporal worker 进程需要共享令牌),启动一个 Redis 并设置OAUTH_REDIS_URL:
docker run --rm -p 6379:6379 redis:7-alpine export OAUTH_REDIS_URL="redis://127.0.0.1:6379"main.py启动时会读取该环境变量并覆盖默认的 token store 配置(见下文)。
服务端配置逐项解析
mcp_agent.config.yaml 是本示例的服务端配置核心,它同时声明了执行引擎、OAuth 全局参数与 GitHub MCP 服务器的认证方式:
$schema: ../../../schema/mcp-agent.config.schema.json execution_engine: temporal temporal: host: localhost:7233 namespace: default task_queue: mcp-agent max_concurrent_activities: 10 logger: transports: [console, file] level: info path_settings: path_pattern: "logs/mcp-agent-{unique_id}.jsonl" unique_id: "timestamp" oauth: loopback_ports: [33418, 33419, 33420] mcp: servers: github: transport: streamable_http url: "https://api.githubcopilot.com/mcp/" auth: oauth: enabled: true scopes: ["read:org", "public_repo", "user:email"] authorization_server: "https://github.com/login/oauth" use_internal_callback: false include_resource_parameter: false关键配置项说明:
execution_engine: temporal:声明工作流由 Temporal 执行引擎调度,因此必须配合temporal.host/namespace/task_queue指向一个可用的 Temporal 服务(默认localhost:7233),并由 worker.py 启动 worker 消费任务。oauth.loopback_ports:客户端本地回环回调端口列表。从源码看,OAuthSettings 中还定义了flow_timeout_seconds(授权回调超时,默认 300 秒)、callback_base_url(使用内部回调时的基础 URL)等参数,本示例采用use_internal_callback: false,即走本机回环端口接收授权回调。mcp.servers.github.auth.oauth:下游 GitHub MCP 服务器的 OAuth 配置,enabled: true开启认证,scopes声明需要的权限范围(read:org、public_repo、user:email),authorization_server指向 GitHub 的授权端点https://github.com/login/oauth。include_resource_parameter: false表示授权请求不携带 resource 参数。
服务端启动逻辑:main.py 源码解读
main.py 的启动流程可拆解为四步:
1. 加载配置并注入 Redis 覆盖。启动时读取OAUTH_REDIS_URL环境变量,若存在则以backend="redis"、redis_url=...覆盖settings.oauth.token_store;否则确保 token store 有默认值。这与 config.py 中OAuthTokenStoreSettings的定义对应:backend取值限定为memory或redis(默认memory),另有redis_prefix(默认mcp_agent:oauth_tokens)与refresh_leeway_seconds(默认 60 秒,即令牌到期前 60 秒触发刷新)。
2. 校验 GitHub OAuth 凭据。代码会检查settings.mcp.servers["github"].auth.oauth是否包含client_id与client_secret,缺失则直接抛出SystemExit并提示从mcp_agent.config.yaml或mcp_agent.secrets.yaml提供。
3. 注册工作流任务与工具。@app.workflow_task(name="github_org_search_activity")定义一个 Temporal 活动,通过gen_client("github", ...)连接 GitHub MCP 服务器并调用search_repositories工具(query=f"org:{query}"、per_page=5、按 best-match 排序),结果解析为 JSON 返回;@app.tool(name="github_org_search")则暴露一个普通 MCP 工具,内部通过app.executor.execute(...)把活动调度给 Temporal 执行,这正是「工具 → 工作流」的桥接点。
4. 以 SSE 模式启动 MCP 服务器。create_mcp_server_for_app(agent_app)为整个应用生成统一的 MCP 服务(会自动挂载workflows-store-credentials等内置工具),随后mcp_server.run_sse_async()监听 SSE 端点供客户端连接。配置中的session_id="workflow-pre-authorize"则用于把服务端会话绑定到固定的上下文。
核心工具workflows-store-credentials深度解析
客户端预置令牌调用的workflows-store-credentials并非示例自定义工具,而是 mcp-agent 服务端内置的能力,实现在 app_server.py 中。其签名与参数如下:
| 参数 | 类型 | 必填 | 说明 |
|---|---|---|---|
workflow_name | str | 是 | 将使用这些令牌的工作流名称,必须是服务器上已注册的工作流 |
tokens | List[Dict] | 是 | 令牌对象列表,至少一项 |
每个令牌对象支持的字段:
| 字段 | 必填 | 说明 |
|---|---|---|
access_token | 是 | OAuth 访问令牌本体 |
server_name | 是 | 令牌对应的 MCP 服务器名称/标识,必须在服务器注册表中存在 |
refresh_token | 否 | OAuth 刷新令牌 |
scopes | 否 | OAuth 权限范围列表 |
expires_at | 否 | 令牌过期时间戳(float) |
authorization_server | 否 | 授权服务器 URL,若提供会与配置比对校验 |
该工具的执行逻辑依次为:校验workflow_name是否已注册、token manager 是否可用;遍历tokens列表逐项校验access_token与server_name的完整性、server_name是否在app_context.server_registry.registry中可解析;随后调用app_context.token_manager.store_user_token(...)持久化。返回结果形如:
{ "success": true, "workflow_name": "github_org_search", "stored_tokens": 1, "total_tokens": 1 }若部分令牌失败,返回中会增加partial_success: true与errors数组;全部失败则抛出ToolError。完整校验过程见 app_server.py。
令牌落库:OAuthTokenManager.store_user_token 底层实现
workflows-store-credentials最终落到的store_user_token位于 oauth/manager.py,它负责把「客户端塞进来的裸令牌」转化为规范化的TokenRecord并写入 store,核心步骤包括:
- 校验服务器启用了 OAuth:若目标服务器的
auth.oauth未配置或enabled为假,抛出OAuthFlowError; - 解析 OAuth 上下文:通过
_resolve_oauth_context根据服务器配置、请求的 scopes 解析出资源标识与授权服务器 issuer; - 校验授权服务器一致性:若调用方提供了
authorization_server字段,会将其规范化后与解析出的 issuer 比对,不一致直接拒绝——防止令牌被「错放」到错误的授权方; - Scope 合规检查:若存储的令牌缺失配置中要求的 scopes,会记录告警日志;令牌实际生效的 scopes 取调用方提供的与配置声明的并集;
- 构建
TokenRecord写入 store:记录中写入access_token、可选的refresh_token、scopes、expires_at、token_type(默认Bearer)、authorization_server、obtained_at时间戳,并在metadata中保留server_name、authorization_server_url、workflow_name、session_id等溯源信息。
此后,工作流执行过程中 token manager 通过get_access_token_if_present按「调用方显式身份 → 当前请求身份 → 会话身份 → 默认身份」的优先级解析用户,命中缓存即直接使用,从而跳过交互式授权(oauth/manager.py)。
客户端调用链:client.py 源码解读
client.py 展示了客户端侧的标准写法:
1. 创建应用上下文。用MCPApp(name="workflow_mcp_client", human_input_callback=..., elicitation_callback=...)初始化,app.run()进入运行上下文后即可访问context.server_registry。
2. 注册远程服务器。通过代码向context.server_registry.registry注入名为pre_authorize_server的MCPServerSettings,指明transport="sse"、url="http://127.0.0.1:8000/sse",指向本地工作流服务器(注释中还保留了用uv run main.py以 stdio 方式启动的备选方案)。
3. 建立会话并启用日志。自定义ConsolePrintingClientSession(继承MCPAgentClientSession)在_received_notification中打印服务端发来的非日志通知,并传入logging_callback以实时输出服务端日志;连接建立后调用set_logging_level("info")(老版本服务器不支持时静默降级)。
4. 预置令牌。默认(除非带--skip-store-credentials参数)调用:
await server.call_tool( "workflows-store-credentials", arguments={ "workflow_name": "github_org_search", "tokens": [ { "access_token": access_token, "server_name": "github", } ], }, )这里正是「先种凭据」的一步:把环境变量里的 GitHub 令牌与github服务器绑定,登记到github_org_search工作流名下。
5. 触发工作流。紧接着调用github_org_search工具并传入查询词(示例中为lastmile-ai),把返回内容尝试按 JSON 解析后美化打印。
另外注意 client.py 中对 stdio 客户端关闭阶段BrokenResourceError良性竞态的容错处理:仅当异常为BrokenResourceError或全部子异常均为该类型时才忽略,否则照常抛出,避免关闭过程被误判为故障。
完整运行流程
按以下顺序启动,即可观察到「先授权、后执行」的完整链路:
第一步:启动 Temporal 服务(若本地尚未运行)。Temporal 是工作流执行引擎,mcp_agent.config.yaml中temporal.host: localhost:7233指向它。
第二步:启动工作流服务器:
python examples/oauth/pre_authorize/main.py该进程会校验 OAuth 凭据、挂载 MCP 服务并监听http://127.0.0.1:8000/sse。
第三步(可选但推荐):启动 Temporal worker:
python examples/oauth/pre_authorize/worker.pyworker.py通过create_temporal_worker_for_app(app)创建并运行 worker,负责消费mcp-agent任务队列中的活动。
第四步:运行客户端(另开终端):
python examples/oauth/pre_authorize/client.py客户端依次完成:连接 SSE 服务器 → 调用workflows-store-credentials缓存 GitHub 令牌 → 调用github_org_search工作流 → 工作流经 Temporal 执行活动 → 活动使用缓存令牌查询 GitHub MCP 服务器并返回仓库列表。
若要验证「跳过预置令牌」的行为,可运行:
python examples/oauth/pre_authorize/client.py --skip-store-credentials此时客户端只触发工作流而不种令牌,可用于对照观察缓存缺失时的表现。
设计要点与扩展建议
- 存储后端选择:内存后端适合单进程演示;一旦涉及多个 Temporal worker 进程共享令牌,务必启用 Redis(
backend: "redis"+OAUTH_REDIS_URL),否则不同进程会各自维护一份互不可见的缓存。specify-secrets.mdx 明确指出这正是让令牌跨重启保持持久、供后台工作流安全取用的方式。 - 身份与作用域校验:
store_user_token对授权服务器 URL、scopes 均做了显式校验与告警,种令牌时建议始终携带与配置一致的scopes和(如适用)authorization_server,避免令牌落库后因 scope 缺失而在运行期才暴露问题。 - 与交互式授权的互补:
examples/oauth/interactive_tool走的是「运行时按需弹浏览器授权」路线,pre_authorize则是「运行前批量种令牌」路线。二者共用同一套settings.oauth与 token store 配置(server-authentication.mdx),可根据执行环境是前台交互还是后台批处理来选择。 - 进一步阅读:完整的认证体系可参考 authentication.mdx,密钥管理规范见 specify-secrets.mdx,Temporal 执行引擎的选型依据见 execution-engine.mdx。
小结
examples/oauth/pre_authorize用不到百行的服务端与客户端代码,示范了一条对异步工作流至关重要的认证路径:把「需要用户在场的交互式授权」前置为「可离线完成的令牌预置」,再用共享 token store 让后台 Temporal 工作流无感消费。配合workflows-store-credentials内置工具的参数校验、store_user_token的规范化落库以及 Redis 持久化,这套模式可以直接平移到你自己的 mcp-agent 项目中,解决后台代理、定时任务与长时间工作流的受保护资源访问问题。
【免费下载链接】mcp-agentBuild effective agents using Model Context Protocol and simple workflow patterns项目地址: https://gitcode.com/GitHub_Trending/mc/mcp-agent
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考