尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CopilotKit 仓库实战:用 mcp-use 设计 MCP 服务的 Tool、Widget 与 Resource 架构

CopilotKit 仓库实战:用 mcp-use 设计 MCP 服务的 Tool、Widget 与 Resource 架构 CopilotKit 仓库实战用 mcp-use 设计 MCP 服务的 Tool、Widget 与 Resource 架构【免费下载链接】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本篇文章以 CopilotKit 开源仓库中open-mcp-clientshowcase 的 MCP Builder 设计文档为骨架系统讲解在基于mcp-use构建 MCP 服务时如何把用户的自然语言需求拆解为 Tool、Widget tool、Resource、Prompt 四种 MCP 原语如何判断什么时候该做可视化 Widget以及如何命名 API、组织数据与进行迭代式开发。读完本文你将掌握一套可直接套用的 MCP 服务设计方法论并能对照仓库内真实的mcp-use-server实现入口、工具、Widget 组件落地自己的 Agent 服务。先认识四个核心概念Tool / Widget tool / Resource / Prompt设计文档开篇即强调在写代码之前先思考用户真正想要什么以及如何把它拆解为 MCP 原语。mcp-use暴露给服务端的是四类原语它们在仓库的.agent/skills/mcp-builder/references/tools-and-resources.md中有完整 API 说明原语作用注册 API典型返回ToolAI 模型可调用的后端动作接收输入、返回数据server.tool()text()、object()Widget tool返回可视化 UI 的工具本质仍是server.tool()但带widget配置并在resources/下有对应 React 组件server.tool()widget配置widget({ props, output })Resource客户端可获取的只读数据server.resource()/server.resourceTemplate()object()、text()、markdown()Prompt可复用的消息模板server.prompt()模板化提示词文本仓库中examples/showcases/open-mcp-client/apps/mcp-use-server/index.ts是这几类原语的典型落地载体import { MCPServer } from mcp-use/server; const server new MCPServer({ name: mcp-use-server, title: mcp-use-server, version: 1.0.0, description: MCP server with MCP Apps integration, baseUrl: process.env.MCP_URL || http://localhost:3109, favicon: favicon.ico, websiteUrl: https://mcp-use.com, icons: [{ src: icon.svg, mimeType: image/svgxml, sizes: [512x512] }], }); server.listen(parseInt(process.env.PORT ?? 3109, 10));从源码结构看MCPServer同时具备三种能力注册原语、监听端口默认3109、以及作为 Hono 实例扩展自定义 HTTP 路由server.get()、server.post()可直接用于健康检查或 Webhook。baseUrl与端口均可通过环境变量MCP_URL、PORT覆盖便于本地联调与部署切换。ToolAI 可调用的动作tools-and-resources.md给出的基础 Tool 定义模式是配置对象name、description、schema 异步处理函数返回值用text()/object()等响应助手包装。所有输入字段都应通过.describe()说明含义非必填字段加.optional()固定取值范围用z.enum()并用.min()/.max()做约束——这既是 Zod Schema 最佳实践也是让模型准确调用工具的关键。Resource 与 Resource Template只读数据面只读数据用server.resource()静态资源或server.resourceTemplate()参数化资源暴露。resource-templates.md提供了完整的 URI Scheme 约定表建议按用途选择 schemeScheme用途示例config://配置数据config://settingsuser://用户数据user://{id}/profiledocs://文档docs://apistats://统计指标stats://currentfile://文件内容file://{path}db://数据库记录db://users/{id}api://API 端点api://weather/{city}ui://UI 组件ui://widget/{name}.html参数化模板支持单参数、多参数与查询参数三种形态例如org://{orgId}/team/{teamId}这类嵌套模板可直接从 URI 中解构出orgId与teamId两个参数。定义资源模板的入口与进阶模式详见 resource-templates.md。Prompt可复用的消息模板Prompt 面向固定指令 可变参数的场景。例如设计文档配套的code-review模板输入language与可选的focusArea输出一段结构化的审查指令文本避免模型每次从零组织提示词。Step 1识别要构建什么——从用户诉求中抽取核心动作设计文档给出的第一步不是写代码而是从用户请求中抽取核心动作并且只做用户要的不要发明额外功能。原文提供的用户诉求 → 核心动作映射表可以当作拆解训练的基准答案用户说核心动作weather app天气应用获取当前天气、获取天气预报todo list待办清单添加待办、列出待办、完成待办、删除待办recipe finder菜谱搜索搜索菜谱、获取菜谱详情translator翻译器翻译文本、检测语言stock tracker股票追踪获取股价、对比股票quiz app测验应用生成测验、检查答案这一步的输出直接决定后面注册多少个 Tool、每个 Tool 的入参是什么。仓库的mcp-use-server恰好演示了请求驱动的拆解tools/product-search.ts把水果搜索拆成了两个职责不同的工具——search-tools触发 Widget 的搜索工具与get-fruit-details返回单一水果详细信息的配套数据工具而水果目录数组在两者之间共享避免数据重复。Step 2判断是否需要 Widget——可视化 UI 何时真正有用设计文档给出两个明确判据需要 WidgetYES需要浏览或对比多个条目搜索结果、商品卡片可视化数据能显著改善理解图表、地图、图片、仪表盘交互式选择在视觉上更自然座位选择器、日历、取色器。仅用 ToolNO输出是简单文本翻译、计算、状态检查输入本身适合对话式表达日期、金额、描述没有任何视觉元素能真正帮上忙。文档给出的总原则是拿不准的时候就用 Widget——它能显著提升体验。这个判断在仓库的 Fruit Shop 示例中体现得很具体搜索结果用轮播卡片Carousel展示每个水果的图片与配色而get-fruit-details这种给定水果名返回事实列表的动作则保持为纯数据工具由 Widget 内部通过useCallTool(get-fruit-details)触发。Step 3设计 API——命名、粒度与状态归属命名动词开头Tool 与 Widget 都以动词开头命名让模型的意图识别更直接get-weather、search-recipes、add-todo、translate-text。仓库中的search-tools、get-fruit-details同样遵循动词 宾语的结构。一个 Tool 只做一件聚焦的事不要造一个大而全的工具。文档给出的正反例❌manage-todos过于宽泛✅add-todo、list-todos、complete-todo、delete-todo拆成聚焦动作窄粒度工具的好处是模型可以按需组合调用例如只完成而不删除单个工具的 schema 更小、校验更严、出错概率更低。仓库中get-fruit-details的outputSchema甚至用z.object({ fruit, color, facts })声明了结构化返回让输出可被校验和复用。一个流程对应一个 Widget不同流程可以有各自的 Widget但同一个流程不要拆成多个 Widget❌search-recipesWidget view-recipeWidget同一流程应合并✅search-recipesWidget同时处理列表与详情视图meal-plannerWidget不同流程对应到仓库实现Fruit Shop 把搜索列表 点击后的详情都放在同一个resources/product-search-result/组件目录内由widget.tsx统一渲染 Carousel 与详情卡片而不是拆成两个组件。不要懒加载一次返回全部数据Tool 调用是有代价的能一次返回的数据就不要拆成第二次调用❌search-recipesWidget get-recipe-detailsTool懒加载详情✅search-recipesWidget 直接返回包含详情的完整数据这条规则的例外是Widget 内部的用户主动交互例如点击某个水果才获取详情因为这是由用户手势触发的按需请求而非模型层面的二次懒加载。仓库中get-fruit-details正是被 Widget 内的点击事件调用属于合规用法。Widget 自己管理状态选择、过滤等 UI 状态应存放在 Widget 内部不要把它们做成独立 Tool❌select-recipeTool、set-filterTool这些是 Widget 状态✅ Widget 通过useState/setState在内部管理选择与过滤仓库的resources/product-search-result/widget.tsx是这条原则的教科书实现收藏的水果列表通过useWidget返回的state/setState持久化toggleFavorite用setState({ favorites: next })更新完整的 pip / fullscreen 显示模式切换通过displayMode与requestDisplayMode完成全部状态都收敛在组件内部服务端没有为此暴露任何 Tool。exposeAsTool默认是falseWidget 默认不会被自动注册为 Tool。当你用widget: { name: my-widget }自定义 Tool 时在 Widget 文件中省略exposeAsTool是正确的——由自定义 Tool 负责让 Widget 可被调用// resources/my-widget.tsx export const widgetMetadata: WidgetMetadata { description: ..., props: z.object({ ... }), // exposeAsTool defaults to false — custom tool definition handles registration };仓库中的widget.tsx正是这样写的exposeAsTool: falseWidget 的调用入口完全由tools/product-search.ts里的search-tools承担。若希望不写自定义 Tool 就让 Widget 直接可调用才需要显式设置exposeAsTool: true。完整字段说明description、props、toolOutput、metadata.invoking/invoked等见 widgets.md。常见应用模式五种可复制的模板设计文档为最常见的五类应用给出了完整蓝图这里是原文的完整继承天气应用Weather AppWidget tool: get-weather - Input: { city } - Widget: temperature, conditions, icon, humidity - Output to model: text summary Tool: get-forecast - Input: { city, days } - Returns: text or object with daily forecast待办清单Todo ListWidget tool: list-todos - Widget: interactive checklist with complete/delete buttons - Widget calls add-todo, complete-todo, delete-todo via callTool Tool: add-todo { title, priority? } Tool: complete-todo { id } Tool: delete-todo { id }注意这里与第 3 步规则的无缝衔接增删改是独立 Tool而勾选完成/删除按钮的交互与当前视图状态全部由 Widget 内部管理。菜谱搜索Recipe FinderWidget tool: search-recipes - Input: { query, cuisine? } - Widget: recipe cards with images, ingredients, instructions - Output to model: text summary of results Resource: recipe://favorites (users saved recipes)收藏的菜谱作为只读数据走 Resource 通道recipe://favorites与搜索动作分离——这演示了 Tool 与 Resource 如何在同一应用内各司其职。翻译器TranslatorTool: translate-text - Input: { text, targetLanguage, sourceLanguage? } - Returns: text (translated result) Tool: detect-language - Input: { text } - Returns: object({ language, confidence })翻译是典型的纯文本输出按第 2 步判据不需要 Widget两个动词开头、粒度聚焦的 Tool 就足够。股票追踪Stock TrackerWidget tool: get-stock - Input: { symbol } - Widget: price chart, key metrics, news - Output to model: price and change summary Tool: compare-stocks - Input: { symbols[] } - Returns: object with comparison dataK 线图与关键指标天然适合可视化因此get-stock走 Widget而对比多只股票返回结构化对象模型可直接消化。Mock 数据策略无真实 API 时如何给出可信数据当用户没有指定真实 API 时用贴近真实的 Mock 数据而不是空壳示例。设计文档给出了可直接使用的天气 Mock 模板// Mock data - replace with real API const mockWeather: Record string, { temp: number; conditions: string; humidity: number } { New York: { temp: 22, conditions: Partly Cloudy, humidity: 65 }, London: { temp: 15, conditions: Overcast, humidity: 80 }, Tokyo: { temp: 28, conditions: Sunny, humidity: 55 }, Paris: { temp: 18, conditions: Light Rain, humidity: 75 }, }; function getWeather(city: string) { // Add slight randomization to feel dynamic const base mockWeather[city] || { temp: 20, conditions: Clear, humidity: 60, }; return { ...base, temp: base.temp Math.round((Math.random() - 0.5) * 4), humidity: base.humidity Math.round((Math.random() - 0.5) * 10), }; }四条 Guideline 必须遵守使用真实名称城市、菜谱、商品名不要用 Example 1加入轻微随机化让数据动起来按真实 API 的返回结构组织数据用注释标注// Mock data - replace with real API提醒后续替换。仓库的 Fruit Shop 是这条策略的另一个实例tools/product-search.ts内置了 16 种水果的真实名称与 Tailwind 配色mango、pineapple、cherries、coconut…搜索时通过setTimeout(2000)模拟网络延迟并在_meta[ui/previewData]中预置一组热带水果预览数据供 UI Studio 在尚无真实调用时展示效果。从package.json可以看到替换真实数据只需要改掉mcp-use^1.22.3之上的业务代码响应助手、Widget 渲染链路完全不变。迭代式开发在既有代码上扩展而非重写当用户要求修改或扩展既有代码时设计文档给出了四条铁律Read——先读当前的index.ts看清已有什么Preserve——保留所有已有 Tool、Resource 与 WidgetAdd——在既有代码旁新增功能不破坏现状Update——更新已有 Widget 文件而不是创建重复文件。仓库的index.ts头部注释把这套流程具象化为新增 MCP App Widget 三步走先在resources/widget-name/widget.tsx写 React 组件通过useWidget()接收 props再在tools/tool-name.ts导出register(server)并在处理器里返回widget({ props, output: text(...) })最后在index.ts的两个标记区import 区与注册区接入register()随后npm run dev会自动重建 Widget 并重启服务。registerProductSearch正是按照这个模式接入的新增工具只需复制同构代码。相关参考文件速查设计总纲design-and-architecture.mdTool / Resource / Prompt APItools-and-resources.mdWidget 组件 API 与useWidget钩子widgets.mdURI 模板资源resource-templates.md服务入口与注册流程index.tsWidget tool 实现范例tools/product-search.tsWidget 组件实现范例resources/product-search-result/widget.tsx小结整套设计方法论可以压缩为一条主线先拆解需求再决定形态Tool 还是 Widget最后用一致的命名与粒度落地。写代码前先问三个问题——用户要的核心动作是什么哪个动作值得可视化每个 Tool / Widget 的职责边界在哪里对照仓库mcp-use-server的实现你既能快速起步npm install npm run dev也能在接手新需求时用迭代式开发流程安全地扩展既有服务。【免费下载链接】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),仅供参考
返回列表