CopilotKit 的 mcp-use 开发基石:掌握 MCP 四大原语(Tool / Resource / Prompt / Widget)
【免费下载链接】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 仓库中mcp-apps-builder技能包的 foundations/concepts 文档为主线,系统讲解用mcp-use框架构建 MCP(Model Context Protocol)服务器时最核心的四种原语:Tool(工具)、Resource(资源)、Prompt(提示词模板)和 Widget(带 UI 的工具)。你将学会如何为每一种场景挑选正确的原语、如何用决策矩阵在四者之间做取舍,以及如何避免exposeAsTool、惰性加载、状态归属等典型陷阱。文中所有示例均可在仓库内对应的完整参考文档与真实源码中找到依据,读完即可直接用于搭建自己的 MCP 服务器。
为什么先理解"原语"
在 MCP(Model Context Protocol)协议中,服务器向 AI 客户端暴露的能力被抽象为有限的几种"原语"。mcp-use框架把这些协议能力封装成了几个等价的 TypeScript 方法:
server.tool()—— 后端动作server.resource()—— 只读数据server.prompt()—— 可复用消息模板- Widget(
widget) —— 带视觉界面的工具
在动手写任何 MCP 服务器之前,先明确"我要暴露的是哪种能力",可以避免写出语义混乱、难以被 AI 正确调用的服务器。参考文档 concepts.md 开篇即指出:这些原语就是构建 mcp-use 服务器时你将反复使用的基本积木。
四大原语详解
1. Tool(工具):AI 可调用的后端动作
定义:Tool 是 AI 可以调用的后端动作,接收输入、返回输出。它适合承载动作、操作、变更类逻辑和 API 调用。
server.tool({ name, description, schema }, async (input) => { // Your logic here return text("result"); });适用场景:发送邮件、创建用户、拉取数据等一切有副作用或有计算逻辑的动作。
纵深解读:从 tools.md 可以看出,一个完整的 Tool 定义远不止name/description/schema三项,还包括:
annotations:声明工具性质,让客户端可以对用户做出风险提示。例如destructiveHint: true表示会删除/覆盖数据,客户端可能要求用户二次确认;readOnlyHint: true表示无副作用可安全重复调用;openWorldHint: true表示会调用用户控制范围之外的外部 API。ctx(第二参数):提供高级能力,包括ctx.reportProgress(current, total, message)上报进度、ctx.log(level, message, data?)结构化日志、ctx.sample(prompt)请求 LLM 协助分析(需客户端支持sampling能力,可用ctx.client.can("sampling")探测)。outputSchema:对工具输出做运行时校验,适合多条代码路径返回不同结构、或需要保证输出一致性的场景。- 错误处理:规范明确要求"用
error()帮助函数优雅失败,不要 throw 异常",并建议在 catch 中记录日志后返回error(message)。 - 性能模式:对昂贵的操作建议加 TTL 缓存;限流需使用 Hono 兼容中间件(如
hono-rate-limiter),因为 mcp-use 底层构建在 Hono 之上,Express 中间件(如express-rate-limit)不兼容。
2. Resource(资源):客户端可拉取的只读数据
定义:Resource 是只读数据,客户端可以直接获取,通常不带输入参数(需要参数时使用资源模板 resource template)。它适合承载配置、静态数据、文档、列表类内容。
server.resource({ uri, name, mimeType }, async () => { return object({ data }); });纵深解读:根据 resources.md 的详细说明:
- URI 规范:推荐使用 scheme 前缀来组织资源,例如
config://settings、docs://user-guide、data://available-cities、state://current-user。不要使用无 scheme 的裸字符串,也不要占用保留的http://。 - 元信息:
name是机器可读标识(kebab-case),title是展示给用户的可读名称,description可选但推荐,mimeType指示内容格式(JSON 对应application/json、Markdown 对应text/markdown、图片对应image/png等)。 - 静态 vs 动态:静态资源返回固定数据;动态资源在请求时实时计算(如当前在线会话数、服务器 uptime),适合"数据随时间变化""计算昂贵按需计算""反映服务器当前状态"的场景。
- 资源模板:当需要参数时使用
server.resourceTemplate(),URI 中用{param}(单个路径段)或{param*}(贪婪匹配多个路径段)占位。处理函数签名是async (uri: URL, params: Record<string, string>),官方建议在函数体内显式提取参数,而非直接解构params,以避免 TypeScript 类型匹配问题。 - 自动补全:通过
callbacks.complete为模板变量提供静态建议列表或动态回调建议,客户端经由 MCP 的completion/complete请求获取。 - 组织与缓存:按 URI scheme 归类资源有助于客户端在列出资源时发现;由于资源只读,缓存收益明显(如对昂贵计算加 10 分钟 TTL 缓存)。
3. Prompt(提示词):带参数的可复用消息模板
定义:Prompt 是可复用的消息模板,带参数。它适合承载通用提示词、指令模板类内容。
server.prompt({ name, description, schema }, async (input) => { return text(`Your prompt template with ${input.param}`); });纵深解读:prompts.md 给出了丰富的实战模式:
- 命名与描述:使用 kebab-case 描述性命名(如
code-review、summarize-document、translate-text),描述要说明模板"能做什么"而非简单名词。 - 参数 Schema:所有字段都用
.describe()描述,尽量用z.enum()约束取值、.optional()标记非必填、.default()提供默认值。 - 常见模式:代码评审、摘要生成、翻译、概念讲解、多步骤重构指导(按激进/温和/保守策略生成步骤)、带环境上下文的优化建议(node/browser/edge/serverless)。
- Markdown 长模板:结构化长提示词使用
markdown()返回,可包含标题、清单、编号步骤。 - 参数自动补全:用
completable(schema, values)(静态列表,自动前缀匹配)或completable(schema, callback)(动态,回调接收(value, ctx),可通过ctx.arguments读取其他字段值做上下文相关建议)。 - Prompt vs Tool 的分界线:Prompt 只提供"指令",无后端逻辑;Tool 执行动作、调用 API/数据库、有副作用。典型反例是"用 tool 跑 linter(执行动作)"与"用 prompt 生成代码评审指令(纯模板)"的区分。
4. Widget(Widget Tool):返回视觉 UI 的工具
定义:Widget 是返回可视化 UI 的工具,与普通 Tool 相同,但会渲染一个 React 组件。它适合浏览数据、交互式选择、需要视觉反馈的场景。
server.tool({ name, schema, widget: { name: "widget-name" } }, async (input) => widget({ props: { data }, output: text("...") }), );纵深解读:Widget 是这套原语体系中信息量最大的一类,参考 widgets/basics.md:
- 实现三要素:(1) 在工具配置中声明
widget: { name };(2) 处理函数返回widget({ props, output });(3) 在resources/目录创建同名组件文件(如resources/{name}.tsx)。 widgetMetadata:每个组件须导出widgetMetadata,包含description(展示什么)、props(Zod schema 定义 props 结构)、可选的metadata.invoking/invoked(加载中/完成时的状态文案,会同步到工具元数据)与metadata.csp(CSP 允许连接的域名)。useWidget()Hook:提供props(来自工具响应的数据)、isPending(props 是否加载中)、state/setState(widget 内部状态)。关键点:Widget 会在工具执行完成前就挂载渲染——首次渲染时isPending = true、props = {},因此访问props字段前必须先检查isPending。<McpUseProvider autoSize>:所有组件(包括 loading 分支)的根节点都必须包裹,它提供上下文并处理 iframe 尺寸(autoSize={true}自动适配内容高度)。- 类型安全四步法:先单独定义
propsSchema常量 → 在widgetMetadata.props中引用该变量(不要内联z.object())→type Props = z.infer<typeof propsSchema>→useWidget<Props>()。否则 TypeScript 会丢失类型信息,props退化为unknown。 - 从 widget 内调用工具:使用专用的
useCallTool()Hook(见 interactivity.md)。
决策矩阵:什么时候用哪种原语
当你不确定该用哪一种时,直接对照这张决策表:
| 需求 | 使用 | 示例 |
|---|---|---|
| 后端动作 | Tool | send-email、create-user、fetch-data |
| 只读数据 | Resource | config、user-profile、api-docs |
| 提示词模板 | Prompt | code-review、summarize、translate |
| 视觉 UI | Widget Tool | search-results、calendar、dashboard |
判断要点:有副作用或需要结构化输入校验的动作选 Tool;纯只读、可能被浏览/枚举的数据选 Resource;只提供"指令"、无后端逻辑的选 Prompt;需要可视化展示与交互的选 Widget。
Tool 还是 Widget?一条经验法则
当输出是简单文本或数据、没有可视化表达的价值、只需快速对话式响应时,使用普通 Tool(不带 widget)。
当需要浏览/比较多个条目、可视化数据能显著提升理解(图表、图片)、可视化选择比文本操作更直观时,使用 Widget。
concepts.md 给出的态度非常明确:拿不准的时候就用 Widget——它通常会带来更好的用户体验。
四个关键设计模式
模式一:一个工具 = 一个能力
❌manage-users(过于宽泛)
✅create-user、delete-user、list-users
把宽泛的"管理用户"拆成语义单一、边界清晰的工具,AI 才能准确判断何时调用哪一个。
模式二:不要惰性加载
工具调用是昂贵的(一轮模型推理 + 网络往返)。应该在一次调用中返回全部所需数据:
❌list-products+get-product-details(两次调用)
✅list-products直接返回含明细的完整数据
模式三:Widget 自己管理自己的状态
UI 状态(选中项、筛选条件)应放在 Widget 内部,通过useState或setState维护:
❌ 专门定义select-item、set-filter这样的工具
✅ 由 Widget 内部自行管理
这条模式与 SKILL.md 中的"Golden Rules"一致:不要在 widget 中"用工具模拟前端状态",这会显著增加交互延迟并让服务器承载本属于前端的逻辑。
模式四:exposeAsTool默认是false
Widget 默认不会被自动注册为工具。当通过自定义工具 +widget: { name }的模式暴露 Widget 时,省略exposeAsTool(或保持false)才是正确的——注册工作由那个自定义工具完成,避免重复注册:
export const widgetMetadata: WidgetMetadata = { description: "...", props: z.object({...}), // exposeAsTool defaults to false — correct for custom-tool pattern };只有当你想让 Widget 以"资源"身份被 AI 自动发现并调用时,才显式设置exposeAsTool: true。
仓库内的完整落地示例
concepts.md 中的抽象概念在仓库里有非常具体的实现:mcp-use-server应用中的 tools/product-search.ts 完整演示了"自定义工具 + Widget"这一核心模式:
server.tool( { name: "search-tools", description: "Search for fruits and display the results in a visual widget", schema: z.object({ query: z.string().optional().describe("Search query to filter fruits"), }), widget: { name: "product-search-result", // 必须与 resources/ 下的目录名一致 invoking: "Searching...", invoked: "Results loaded", }, _meta: { // 尚未发生真实调用时,MCP UI Studio 中展示的预览数据 "ui/previewData": { ... }, }, }, async ({ query }) => { const results = fruits.filter(...); await new Promise((resolve) => setTimeout(resolve, 2000)); // 模拟网络延迟,展示加载态 return widget({ props: { query: query ?? "", results }, output: text(`Found ${results.length} fruits matching "${query ?? "all"}"`), }); }, );这段源码印证了 concepts.md 中的多个要点:
- Tool 配置:
name/description/schema三段式定义,query字段带.describe(); - Widget 声明:
widget.name与resources/下的目录名严格对应(注释明确指出 "must match the folder name under resources/"); - 加载状态:用 2 秒延迟模拟真实网络请求,配合
invoking/invoked文案让 Widget 展示完整的加载态与完成态; - 纯数据工具伴生:同一文件还注册了
get-fruit-details数据工具,供 Widget 内部通过useCallTool()调用,体现"一个工具 = 一个能力"的拆分思想。
一次完整的构建路径:从脚手架到产出
为了让上述原语真正落到可运行的服务器上,concepts.md 的 Next Steps 指向了完整的入门流程:
- 脚手架:
npx create-mcp-use-app my-server创建项目(默认starter模板);widget 优先场景推荐--template mcp-apps;从零开始用--template blank;还支持--template owner/repo拉取 GitHub 社区模板。常用 flag 有--npm/--pnpm选择包管理器、--install --skills跳过交互提示、--list-templates列出所有模板。详见 quickstart.md。 - 开发循环:
npm run dev启动带热重载的服务器,访问http://localhost:3000/inspector打开 MCP Inspector 调试;在index.ts中添加 tools/resources/prompts;widget 组件以.tsx文件放入resources/;生产构建用npm run build,发布用npm run deploy。 - 质量检查:SKILL.md 中的安全清单与常见错误清单可作为提交前的自查项——所有 schema 字段是否有
.describe()、输入是否经 Zod 校验、API 密钥是否走环境变量、错误是否用error()返回、破坏性操作是否设置destructiveHint: true、widget 是否检查isPending并包裹McpUseProvider等。
总结:一条可复用的判断链
把本文内容压缩成一条工作流:
- 先问场景:是"动作"(→ Tool)、"只读数据"(→ Resource)、"指令模板"(→ Prompt)还是"可视化交互"(→ Widget)?
- 再看约束:需要参数就考虑 Resource Template / Prompt schema;需要视觉就优先 Widget;拿不准就选 Widget。
- 最后套模式:一个工具只做一个能力;一次调用返回完整数据;UI 状态留在 Widget 内部;自定义工具 + Widget 时保持
exposeAsTool为false。
mcp-apps-builder技能包把 mcp-use 的这套最佳实践沉淀为可直接遵循的参考手册。深入阅读 tools.md、resources.md、prompts.md、widgets/basics.md 四份细分文档,再对照 product-search.ts 这份真实实现,就能把四大原语从"概念"变成"肌肉记忆"。
【免费下载链接】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),仅供参考