ARTICLE DETAIL

资讯详情

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

CopilotKit Open MCP Client 实战:基于 mcp-use 定义渲染 Widget 的 MCP 服务端处理器与组件

CopilotKit Open MCP Client 实战:基于 mcp-use 定义渲染 Widget 的 MCP 服务端处理器与组件 CopilotKit Open MCP Client 实战基于 mcp-use 定义渲染 Widget 的 MCP 服务端处理器与组件【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit本篇技术指南以examples/showcases/open-mcp-client中 MCP App 构建技能chatgpt-app-builder的参考文档 server-and-widgets.md 为核心骨架完整讲解「服务端工具如何返回 Widget 响应、Widget 组件如何接收 props 渲染、数据如何在服务端与 UI 之间流转」这一 MCP App 开发闭环。读完本文你将掌握在 CopilotKit 的 Open MCP Client 演示仓库中用mcp-use框架注册带 Widget 的 MCP 工具、编写可交互的 React Widget 组件、以及通过callTool/useCallTool实现 Widget 内部二次调用工具的完整实战方法。整体数据流从用户提示到 Widget 渲染先建立全局心智模型。一个带 Widget 的 MCP 工具其数据流可以概括为User prompt → LLM calls tool → Server handler runs → Returns widget() response ↓ Widget component renders ← props (hidden from LLM) LLM sees ← output (text/object/markdown)拆解为四个关键步骤LLM 调用工具大模型根据用户输入决定调用哪个 MCP 工具并把参数填入 schema。服务端处理器执行服务端 handler 负责取数、加工、执行业务逻辑例如查询数据库、调用第三方 API、模拟网络延迟。Handler 返回widget({ props, output })props→ 发送给 Widget UI组件内通过useWidget().props读取LLM 永远看不到这部分output→ 是text()、object()等响应辅助函数的结果LLM 用于对话回复。Widget 组件渲染前端在 iframe 中渲染对应的 React 组件向用户呈现可视化结果。这一设计把「机器可读的结果」与「人可看的界面」彻底解耦output让 LLM 保持对话能力props让 UI 拥有完整数据。在 product-search.ts 的真实实现中search-tools工具模拟了 2 秒网络延迟await new Promise((resolve) setTimeout(resolve, 2000))来演示 Widget 的加载态随后返回widget({ props: { query, results }, output: text(Found N fruits matching ...) })——output同时以文本形式告知 LLM 查询结果数量而完整的水果列表只通过props交给界面。服务端工具注册配置 Widget 的 Tool 定义一个「渲染 Widget 的工具」在MCPServer实例上通过server.tool()注册。参考文档给出了完整示例本文以仓库实际代码为准做对齐说明import { MCPServer, widget, text, object } from mcp-use/server; import { z } from zod; const server new MCPServer({ name: my-app, version: 1.0.0, baseUrl: process.env.MCP_URL || http://localhost:3000, }); server.tool( { name: search-restaurants, description: Search for restaurants by cuisine and location, schema: z.object({ cuisine: z.string().describe(Type of cuisine (e.g., Italian, Japanese)), location: z.string().describe(City or neighborhood), }), widget: { name: restaurant-list, // Must match resources/restaurant-list.tsx invoking: Searching restaurants..., invoked: Restaurants found, }, annotations: { readOnlyHint: true, // Only reads data, no side effects }, }, async ({ cuisine, location }) { const restaurants await searchRestaurants(cuisine, location); return widget({ props: { restaurants, // Full data for the widget cuisine, location, }, output: text( Found ${restaurants.length} ${cuisine} restaurants near ${location}, ), }); }, );服务端入口的注册方式在仓库中服务端入口 index.ts 展示了标准的组织方式每个工具或工具组拆成独立模块导出register(server)函数入口文件统一import { register as registerProductSearch } from ./tools/product-search并调用registerProductSearch(server)MCPServer支持配置name、title、version、description、baseUrl默认http://localhost:3109、favicon、icons等元信息server.listen(parseInt(process.env.PORT ?? 3109, 10))启动服务。在 product-search.ts 中widget.name被设为product-search-result注释明确要求它必须与resources/下的文件夹名一致真实仓库为resources/product-search-result/widget.tsx而非扁平的单文件。widget()响应辅助函数字段类型说明propsRecordstring, any通过useWidget().props发送给 Widget 的数据对 LLM 隐藏。outputCallToolResult响应辅助函数text()、object()等的结果LLM 可读。messagestring可选覆盖发送给 LLM 的文本消息。工具上的widget配置项字段类型默认值说明namestring必填Widget 名称与resources/下的文件/文件夹匹配invokingstringLoading {name}...工具执行期间展示的文本invokedstring{name} ready工具执行完成时展示的文本widgetAccessiblebooleantrueWidget 是否可以调用其他工具仓库实现中还展示了额外的元数据能力_meta[ui/previewData]用于在尚未发生真实调用时如 MCP UI Studio 侧边栏预览展示离线预览数据见 product-search.ts。widget 元数据还支持invoking/invoked文案覆盖与 CSP 域名白名单见 widget.tsx 中的metadata配置。Widget 组件接收 props 并渲染界面服务端返回widget({ props })后前端需要在resources/目录下提供同名组件。参考文档给出了restaurant-list的 React 组件写法核心要点如下// resources/restaurant-list.tsx import { McpUseProvider, useWidget, type WidgetMetadata } from mcp-use/react; import { z } from zod; export const widgetMetadata: WidgetMetadata { description: Display restaurant search results, props: z.object({ restaurants: z.array( z.object({ id: z.string(), name: z.string(), cuisine: z.string(), rating: z.number(), priceRange: z.string(), }), ), cuisine: z.string(), location: z.string(), }), exposeAsTool: false, // Custom tool in index.ts handles registration }; export default function RestaurantList() { const { props, isPending, callTool } useWidget(); if (isPending) { return ( McpUseProvider autoSize div style{{ padding: 16, textAlign: center }} Searching restaurants... /div /McpUseProvider ); } // ... 渲染 props.restaurants并可通过 callTool 调用 make-reservation }仓库中的真实组件 widget.tsx 把这个模式进一步做实组件根节点包裹McpUseProvider并使用AppsSDKUIProvider提供 UI 基础组件按钮、图标等isPending分支渲染CarouselSkeleton骨架屏配合服务端 2 秒延迟演示加载态props经propSchemaZod schema定义在 types.ts做运行时校验保证服务端与前端的数据契约一致。必导出项widgetMetadata 与默认组件参考文档强调每个 Widget 文件必须导出两样东西widgetMetadata包含description与propsZod schema默认 React 组件。export const widgetMetadata: WidgetMetadata { description: What this widget shows, props: z.object({ /* ... */ }), // exposeAsTool defaults to false — custom tool in index.ts handles registration }; export default function MyWidget() { /* ... */ }exposeAsTool默认值为false意味着工具注册由index.ts中的自定义工具负责——这正是服务端widget.name必须与resources/目录名匹配的原因。真实组件中widgetMetadata还声明了metadata.csp.resourceDomains以允许加载https://cdn.openai.com上的资源以及prefersBorder: false等外观偏好见 widget.tsx。useWidget组件侧的交互核心组件通过useWidget()钩子获取运行时状态与能力const { props, // Widget input data isPending, // true while tool still running (props may be partial!) output, // Additional output data from the tool callTool, // Call another tool: await callTool(name, { args }) } useWidget();关键提示必须先检查isPending。工具执行期间props可能是空或部分数据如果组件在isPending为true时直接解构props.restaurants必然出错。仓库真实组件展示了更完整的解构见 widget.tsxconst { props, isPending, displayMode, // inline | fullscreen | pip requestDisplayMode, // 切换 inline/fullscreen/pip 显示模式 sendFollowUpMessage, // 从 Widget 向 LLM 发送追问消息 locale, // 当前区域设置如 en-US state, // 持久化状态 setState, // 更新持久化状态 } useWidgetProductSearchResultProps, FavoritesState(); const { callTool: getFruitDetails, data: fruitDetails, isPending: isLoadingDetails, } useCallTool(get-fruit-details);由此可以看到两个调用其他工具的入口文档示例中的useWidget().callTool以及仓库实际采用的独立useCallTool(toolName)钩子返回callTool、data、isPending。state/setState用于维护如「收藏的水果列表」这类跨渲染持久状态sendFollowUpMessage则让 Widget 主动向对话中的 LLM 发起追问真实组件中的「Ask the AI for more about mango」按钮即此用途。Widget 内部调用工具make-reservation 模式Widget 不只是被动展示还能反哺业务。参考文档给出了后端配套工具的定义方式// index.ts server.tool( { name: make-reservation, description: Make a restaurant reservation, schema: z.object({ restaurantId: z.string().describe(Restaurant ID), partySize: z.number().describe(Number of guests), date: z.string().describe(Reservation date), }), annotations: { readOnlyHint: false, destructiveHint: false, }, }, async ({ restaurantId, partySize, date }) { const reservation await createReservation(restaurantId, partySize, date); return object({ confirmationId: reservation.id, time: reservation.time }); }, );组件侧点击按钮时调用const handleReserve async (restaurantId: string) { try { const result await callTool(make-reservation, { restaurantId, partySize: 2, date: new Date().toISOString(), }); alert(Reservation made!); } catch (err) { console.error(Reservation failed:, err); } };仓库中的对应实现是get-fruit-details一个仅提供结构化数据object()返回的工具被 Widget 内useCallTool(get-fruit-details)调用用于点击水果后异步加载详情。注意这类「数据型工具」没有配置widget字段说明只有需要渲染界面的工具才声明 widget 配置其余仍是普通 MCP 工具见 product-search.ts。静态资源public/ 目录与 Image 组件Widget 内引用图片、字体等静态资源时统一放在public/目录并使用mcp-use/react提供的Image组件import { Image } from mcp-use/react; function Logo() { return Image src/images/logo.svg altLogo /; }以/开头的相对路径会自动解析到 MCP 服务器的 public URL。仓库中的apps/mcp-use-server/public/fruits/存放了 16 张水果图片apple.png、mango.png 等真实组件在详情视图通过src{/fruits/${selectedFruit.fruit}.png}动态加载对应图片见 widget.tsx。在仓库中运行与验证独立运行 MCP 服务器进入apps/mcp-use-server目录执行npm run dev服务默认监听3109端口入口文件 index.ts 顶部注释给出了新增 Widget 的标准三步流程创建resources/widget-name/widget.tsx、创建tools/tool-name.ts、在入口导入并调用register()。整个 Open MCP Client 演示参考 README.md从仓库根目录执行pnpm i、配置.env的OPENAI_API_KEY后运行pnpm devapps/mcp-use-server还可通过 template.ts 预烘焙成 E2B 沙箱模板npm run build预构建 Widget、启动时npx tsx index.ts并等待 3109 端口就绪实现秒级冷启动。服务端定义的 widget.tsx 会随npm run dev自动重新构建编辑保存即可看到效果是验证本篇文章所有模式最快的方式。小结「服务端widget()响应 前端useWidget()渲染」是 MCP App 的核心开发范式props与output的分离保证了 LLM 对话质量与 UI 表现力互不干扰widget.name与resources/目录的强约定让工具与组件一一对应callTool/useCallTool让 Widget 从「展示层」升级为「可交互应用层」。参考文档 server-and-widgets.md 与仓库中product-search-result的完整实现互为印证是快速上手 MCP App 开发的最佳样板。【免费下载链接】CopilotKitThe Frontend Stack for Agents Generative UI. React, Angular, Mobile, Slack, and more. Makers of the AG-UI Protocol项目地址: https://gitcode.com/GitHub_Trending/co/CopilotKit创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表