ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cloudflare Agents 实战:构建支持 OAuth 与 Human-in-the-Loop 的 MCP 客户端 Agent

Cloudflare Agents 实战:构建支持 OAuth 与 Human-in-the-Loop 的 MCP 客户端 Agent Cloudflare Agents 实战构建支持 OAuth 与 Human-in-the-Loop 的 MCP 客户端 Agent【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents导读本示例examples/mcp-client/README.md展示了一个完全反向的 MCP 集成思路Agent 不再作为 MCP 工具提供者而是扮演MCP 客户端在运行时动态连接远程 MCP 服务器自动完成 OAuth 认证并把所有已连接服务器的 tools、prompts、resources 聚合到一个 React 前端中统一浏览与调用。读完本文你将掌握addMcpServer/removeMcpServer的 Agent 侧连接管理、onMcpUpdate的 WebSocket 实时状态推送、OAuth 弹窗回调处理以及如何通过 Elicitation 机制把服务端需要人工输入的请求转发到浏览器、用callable方法回传结果构建完整的 Human-in-the-Loop 闭环。示例概览与运行方式该示例位于examples/mcp-client前端是一个基于 React 的界面你可以在其中输入 MCP 服务器 URL、查看每个服务器的连接状态connecting/ready/failed/authenticating等、浏览聚合后的 tools / prompts / resources 列表并直接运行工具。当一次工具调用触发 Elicitation需要人工输入时页面右下角会弹出卡片等待你的反馈。启动步骤npm install npm run startstart脚本对应 package.json 中的vite dev本地开发由 Vite 驱动后端 Worker 则由cloudflare/vite-plugin一并拉起。如果只是覆盖 OAuth 回调域名可以复制环境变量模板README 中说明存在.env.example当前仓库中该模板并非必需文件未包含在目录内cp .env.example .env用配套示例做端到端联调README 给出了三组可对接的目标服务器每个端点均为/mcp例如http://localhost:8787/mcp普通无鉴权服务器运行mcp有状态 MCP 服务器直接添加其 URL 即可连接带 OAuth 鉴权运行mcp-worker-authenticated添加其 URL 可走完 OAuth 弹窗授权流程Stateless Elicitation运行mcp-elicitation-mrtr调用其increase-counter工具触发传统LegacyElicitation运行mcp-elicitation可分别测试 form表单与 url链接两种模式。架构一览Agent 侧与 Client 侧的分工整个示例由两个进程协作Server sidesrc/server.tsMyAgent继承自Agent承载全部 MCP 客户端逻辑——注册/移除服务器、OAuth 回调、Elicitation 转发、工具调用同时以callable()方法暴露给前端远程调用routeAgentRequest负责把 HTTP/WebSocket 请求路由到 Agent 的 Durable Object 实例Client sidesrc/client.tsxReact 前端通过useAgent建立 WebSocket 连接接收onMcpUpdate推送的服务器状态通过agent.call(addServer, ...)等调用远程方法并在收到mcp-elicitation消息后渲染人工输入卡片。从源码结构看客户端能力由MCPClientManagerpackages/agents/src/mcp/client/index.ts实现它是一个 Durable Object 能力capability负责持久化连接、目录聚合catalog与 OAuth 状态管理Agent 上的this.mcp即是对该能力的封装入口。服务端核心连接管理、OAuth 与 ElicitationMyAgent的完整连接管理逻辑集中在 src/server.ts要点如下。1. OAuth 回调处理configureOAuthCallbackonStart() { this.mcp.configureOAuthCallback({ customHandler: (result) { if (result.authSuccess) { return new Response(scriptwindow.close();/script, { headers: { content-type: text/html }, status: 200 }); } const error result.authError || Unknown error; return new Response(Authentication Failed: ${error}, { headers: { content-type: text/plain }, status: 400 }); } }); }onStart()是 Agent 实例启动时的钩子在这里完成一次性配置customHandler接收MCPClientOAuthResultauthSuccess/authError本示例在成功后返回一段内联 HTMLscriptwindow.close();/script让 OAuth 弹窗授权完成后自动关闭失败则返回400与错误文案底层实现上回调 URL 会被MCPClientManager.onRequest拦截packages/agents/src/mcp/client/index.ts校验并消费 OAuth state防止 CSRF/陈旧回调成功后自动调用establishConnection恢复连接再交由customHandler若未配置则按successRedirect/errorRedirect重定向。2. Elicitation 处理configureElicitationHandlersthis.mcp.configureElicitationHandlers({ form: (request, serverId) this.forwardElicitationToBrowser(request, serverId), url: (request, serverId) this.forwardElicitationToBrowser(request, serverId) });configureElicitationHandlers同时覆盖两种协议形态Stateless Elicitation通过 MRTRinput_required机制对端是mcp-elicitation-mrtrLegacy Elicitation对端推送elicitation/create对应mcp-elicitation。二者的处理函数签名一致(request, serverId) PromiseElicitResultElicitResult可取accept携带 content、decline或cancel。forwardElicitationToBrowser是示例的人机协作核心private async forwardElicitationToBrowser( request: ElicitRequest, serverId: string ): PromiseElicitResult { const id crypto.randomUUID(); const result new PromiseElicitResult((resolve) { this.pendingElicitations.set(id, resolve); // 无人应答时不要永远挂住工具调用 setTimeout(() { if (this.pendingElicitations.delete(id)) { resolve({ action: cancel, content: {} }); } }, ELICITATION_TIMEOUT_MS); }); this.broadcast( JSON.stringify({ type: mcp-elicitation, id, serverId, params: request.params } satisfies PendingElicitation) ); return result; }关键设计点pendingElicitations是内存 Map以id→ resolve 函数存储挂起的请求。代码注释明确说明挂起的 Elicitation 不跨 hibernation 存活——因为等待它的工具调用本身是活动请求同样无法在休眠后存活这种取舍是自洽的超时上限ELICITATION_TIMEOUT_MS 5 * 60 * 10005 分钟超时自动以cancel收尾避免工具调用被无限挂起通过broadcast把mcp-elicitation消息推送给所有 WebSocket 客户端。3. 前端可调用的callable()方法/** 浏览器回传人工对 Elicitation 的应答 */ callable() respondToElicitation(id: string, result: ElicitResult) { const resolve this.pendingElicitations.get(id); if (resolve) { this.pendingElicitations.delete(id); resolve(result); } } callable() async addServer(name: string, url: string) { await this.addMcpServer(name, url); } callable() async disconnectServer(serverId: string) { await this.removeMcpServer(serverId); } callable() async callTool( serverId: string, name: string, args: Recordstring, unknown ) { return await this.mcp.callTool({ serverId, name, arguments: args }); }addMcpServer(name, url)是Agent的内置方法支持按服务器名或 URL 注册。底层会把服务器记录持久化到cf_agents_mcp_serversSQLite 表见 packages/agents/src/mcp/client/index.ts 的saveServerToStorage因此重启/休眠后可通过restoreConnectionsFromStorage恢复连接URL 形式的注册还支持{ transport: { type: sse } }等选项指定传输协议以及{ id }指定稳定 IDremoveMcpServer(serverId)负责断开并删除服务器记录this.mcp.callTool({ serverId, name, arguments: args })把调用路由到指定服务器上的工具respondToElicitation把浏览器的应答 resolve 回挂起的 Promise从而让被阻塞的 MCP 工具调用继续执行。4. 安全细节SSRF 防护MCPClientManager在连接远程服务器前会校验目标 URLpackages/agents/src/mcp/client/index.ts 的isBlockedUrl屏蔽 RFC 1918 私网段、链路本地地址、唯一本地地址ULA、云元数据端点如metadata.google.internal与格式非法 URL同时放行 loopback127.0.0.1、::1以支持本地开发。这意味着示例默认可以连接http://localhost:8787/mcp之类的本地目标但无法用于探测内网。若需在受限环境中连接非本地内网地址可关注仓库中该模块的配置项如自定义连接校验这属于部署层面的额外考量。前端核心useAgentonMcpUpdate实时状态React 前端通过useAgent来自agents/react实现见 packages/agents/src/react.tsx建立连接const agent useAgent({ agent: my-agent, name: sessionId!, onClose: useCallback(() setConnectionStatus(disconnected), []), onMcpUpdate: useCallback((mcpServers: MCPServersState) { setMcpState(mcpServers); }, []), onMessage: useCallback((event: MessageEvent) { if (typeof event.data ! string) return; let message: PendingElicitation; try { message JSON.parse(event.data); } catch { return; } if (message.type mcp-elicitation) { setElicitations((current) [...current, message]); } }, []), onOpen: useCallback(() setConnectionStatus(connected), []) });要点说明agent/name用于定位 Agent 实例name取自localStorage中的sessionIdnanoid(8)生成保证同一浏览器会话复用同一实例onMcpUpdate接收MCPServersState——该类型定义在 packages/agents/src/index.ts结构为{ servers: { [id]: MCPServer }, tools, prompts, resources }。服务端每次状态变更都会经 WebSocket 推送CF_AGENT_MCP_SERVERS消息前端据此刷新 UI源码onMcpUpdate的派发点在 packages/agents/src/react.tsxonMessage监听自定义广播当消息 JSON 解析成功且type mcp-elicitation时把待应答卡片加入状态队列。连接状态机与授权弹窗页面头部用ConnectionIndicator展示 Agent 连接的三种状态connecting黄、connected绿、disconnected红。已连接服务器的状态则由MCPServer.state驱动徽章颜色ready→ 主色调徽章failed→ 红色徽章并显示server.error错误信息authenticating→ 显示Authorize按钮点击后执行openPopup(server.auth_url)function openPopup(authUrl: string) { window.open( authUrl, popupWindow, width600,height800,resizableyes,scrollbarsyes ); }用户在弹出的 600×800 窗口中完成 OAuth 授权后服务端customHandler返回的脚本会自动关闭该窗口随后MCPClientManager恢复连接前端收到onMcpUpdate状态刷新。工具调用与 Elicitation 卡片ToolCard根据工具的inputSchema.properties动态生成参数表单SchemaFields支持 string / number / boolean / enum 四种字段类型点击 Run 后调用agent.call(callTool, [serverId, name, args])结果以 JSON 展示聚合区分别渲染 Tools / Prompts / Resources 三个区块展示MCPServersState中对应的聚合数组ElicitationCard渲染两种模式form 模式根据requestedSchema.properties生成表单url 模式提供Open按钮新窗口打开params.url。用户点击 Submit / Done / Decline 后调用respondToElicitation(id, { action, content })回传const respondToElicitation async (id: string, result: ElicitResponse) { setElicitations((current) current.filter((e) e.id ! id)); await agent.call(respondToElicitation, [id, result]); };应答卡片被固定在页面右下角fixed bottom-6 right-6 z-50保证无论从页面何处触发工具调用人工输入请求都始终可见。部署配置速览wrangler.jsonc 中值得关注的配置{ name: mcp-client, main: src/server.ts, compatibility_date: 2026-06-11, compatibility_flags: [nodejs_compat], assets: { not_found_handling: single-page-application, run_worker_first: [/agents/*] }, durable_objects: { bindings: [{ class_name: MyAgent, name: MyAgent }] }, migrations: [ { new_sqlite_classes: [MyAgent], tag: v1 } ], observability: { logs: { enabled: true } } }nodejs_compat提供 Node 兼容运行时assets中的run_worker_first保证/agents/*路径Agent 的 HTTP/WebSocket 端点优先交给 Worker 处理而其余请求走 SPA 静态资源MyAgent作为 SQLite-backed Durable Object 注册MCP 服务器注册表与 OAuth 状态即存储于此observability.logs.enabled开启日志观测便于排查连接失败与 Elicitation 链路。生产部署可执行npm run deploy # vite build wrangler deploy实践总结与延伸这个示例把 MCP 客户端能力完整地搬进了 Cloudflare Agents 生态动态接入运行时addMcpServer/removeMcpServer配合持久化存储连接在休眠恢复后自动重建协议完整同时支持 statelessMRTRinput_required与 legacyelicitation/create两种 Elicitation以及 OAuth 弹窗授权双向通信闭环Agent 经broadcast把人工输入请求推给浏览器浏览器经callable方法回传结果形成完整的 Human-in-the-Loop安全默认内置 SSRF 防护仅放行 loopback 以支持本地联调。进一步探索可阅读 mcp-client 配套的服务器端示例mcp、mcp-worker-authenticated、mcp-elicitation、mcp-elicitation-mrtr以及 MCP 客户端底层实现 packages/agents/src/mcp/client/index.ts 与 React 侧useAgent的 packages/agents/src/react.tsx。【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表