使用 mcp-builder Skill 构建生产级 MCP Server:mcp-use 框架下 Tools、Resources、Prompts 与交互式 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 仓库中examples/showcases/open-mcp-client/apps/mcp-use-server示例应用所内置的mcp-builderAgent Skill 为核心,系统讲解如何基于mcp-use框架搭建生产可用的 Model Context Protocol(MCP)服务端。你将掌握从需求拆解、API 设计、server.tool()/server.resource()/server.prompt()服务端实现,到响应助手函数、React Widget 交互界面与参数化资源模板的完整技术栈,并能直接对照仓库内真实可运行的product-search示例落地自己的 MCP 应用。
说明:本文主体内容对应 Skill 文档
examples/showcases/open-mcp-client/apps/mcp-use-server/.agent/skills/mcp-builder/SKILL.md,其底层细节来自同目录references/下的五份参考文档。该 Skill 的 frontmatter 标注为DEPRECATED(已废弃),官方建议改用mcp-app-builder(本仓库中的对应实现位于examples/showcases/open-mcp-client/apps/mcp-use-server/.agent/skills/mcp-apps-builder/),但其中关于 mcp-use 服务端开发的核心 API 与设计方法论仍然完全适用,因此本文仍以其为骨架展开。
一、Skill 定位:写代码之前先想清楚“拆什么”
mcp-builder是一条面向 MCP 服务端开发的 Agent Skill,它的核心主张是:先拆解用户需求,再写代码。它把 MCP 服务端的能力抽象为四类 MCP 原语(primitive):
| 原语 | 角色 | 对应 API |
|---|---|---|
| Tool(工具) | AI 模型可调用的后端动作,接收输入并返回数据 | server.tool() |
| Widget tool(组件工具) | 返回可视化 UI 的工具,本质仍是server.tool(),但携带widget配置并指向resources/下的 React 组件 | server.tool()+widget() |
| Resource(资源) | 客户端可读取的只读数据 | server.resource()/server.resourceTemplate() |
| Prompt(提示词) | 可复用的消息模板 | server.prompt() |
Skill 的三步拆解法在 design-and-architecture.md 中有完整展开:
- 确定要构建什么:从用户请求中抽取核心动作,只做用户要求的,不要发明额外功能。例如"weather app"对应"获取当前天气、获取预报";"todo list"对应"添加、列出、完成、删除";"recipe finder"对应"搜索食谱、获取食谱详情";"translator"对应"翻译文本、检测语言";"stock tracker"对应"获取股价、对比股票";"quiz app"对应"生成测验、检查答案"。
- 判断是否需要 Widget:浏览/对比多个条目(搜索结果、商品卡片)、视觉数据能提升理解(图表、地图、图片、仪表盘)、可视化交互选择更方便(座位选择器、日历、取色器)→ 用 Widget;纯文本输出(翻译、计算、状态检查)、输入天然适合对话(日期、金额、描述)、没有任何视觉元素能带来帮助 → 只用 Tool。文档给出的原则是"犹豫时就上 Widget",因为它通常能显著改善体验。
- 设计 API:工具与 Widget 命名以动词开头(
get-weather、search-recipes、add-todo、translate-text);一个工具只做一个聚焦能力(❌manage-todos→ ✅add-todo/list-todos/complete-todo/delete-todo);一个流程只对应一个 Widget(❌search-recipesWidget +view-recipeWidget → ✅ 合并为一个search-recipesWidget,列表与详情都在其中);不做懒加载(工具调用成本高,一次性返回全部数据,❌search-recipes+get-recipe-details→ ✅search-recipes直接返回完整食谱数据);Widget 自己管理状态(选择、筛选、UI 状态放在 Widget 内部,❌select-recipe/set-filter工具 → ✅ Widget 内部用useState管理)。
当用户没有指定真实 API 时,Skill 还给出了Mock 数据策略:使用真实名称(城市、食谱、产品名,而非 "Example 1")、加入轻微随机化让数据显得动态、按真实 API 的返回结构组织数据、并显式注释// Mock data - replace with real API。
二、快速上手:最小可运行 MCP Server
Skill 的 Quick Reference 给出了一个覆盖 Tool、Resource、Prompt 三类原语的完整最小实现:
import { MCPServer, text, object, markdown, html, image, widget, error, } from "mcp-use/server"; import { z } from "zod"; const server = new MCPServer({ name: "my-server", version: "1.0.0" }); // Tool server.tool( { name: "my-tool", description: "...", schema: z.object({ param: z.string().describe("...") }), }, async ({ param }) => text("result"), ); // Resource server.resource( { uri: "config://settings", name: "Settings", mimeType: "application/json" }, async () => object({ key: "value" }), ); // Prompt server.prompt( { name: "my-prompt", description: "...", schema: z.object({ topic: z.string() }), }, async ({ topic }) => text(`Write about ${topic}`), ); server.listen();关键 API 一览:
- 响应助手:
text()、object()、markdown()、html()、image()、audio()、binary()、error()、mix()、widget() - 服务端方法:
server.tool()、server.resource()、server.resourceTemplate()、server.prompt()、server.listen()
在仓库的真实示例 index.ts 中,服务端配置会显式传入更多元信息(title、description、baseUrl、favicon、websiteUrl、icons),并通过环境变量控制地址与端口:
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/svg+xml", sizes: ["512x512"] }], }); server.listen(parseInt(process.env.PORT ?? "3109", 10)).then(() => { console.log(`Server running on port ${process.env.PORT ?? "3109"}`); });从 package.json 可以看到该示例基于mcp-use@^1.22.3、react@^19.2.4、zod@4.3.5,并通过npm run dev(mcp-use build --inline && cross-env NODE_ENV=production npx tsx index.ts)在开发时自动重建 Widget 并重启服务。
三、服务端实现详解:Tools、Resources 与 Prompts
references/tools-and-resources.md 完整讲解了服务端三类原语的实现模式。
3.1 Tool:AI 模型可调用的动作
基础工具在配置对象中声明name、description、schema(Zod 描述输入),回调函数解构输入并返回响应:
import { MCPServer, text, object, error } from "mcp-use/server"; import { z } from "zod"; const server = new MCPServer({ name: "my-server", version: "1.0.0", baseUrl: process.env.MCP_URL || "http://localhost:3000", }); server.tool( { name: "translate-text", description: "Translate text between languages", schema: z.object({ text: z.string().describe("Text to translate"), targetLanguage: z .string() .describe("Target language (e.g., 'Spanish', 'French')"), sourceLanguage: z .string() .optional() .describe("Source language (auto-detected if omitted)"), }), }, async ({ text: inputText, targetLanguage, sourceLanguage }) => { const translated = await translateAPI( inputText, targetLanguage, sourceLanguage, ); return text(`${translated}`); }, );3.1.1 Tool 注释(Annotations)
通过annotations声明工具的性质,帮助客户端与模型判断调用风险:
server.tool( { name: "delete-item", description: "Delete an item permanently", schema: z.object({ id: z.string().describe("Item ID") }), annotations: { destructiveHint: true, // 删除或覆盖数据 readOnlyHint: false, // 有副作用 openWorldHint: false, // 不超出用户账户范围 }, }, async ({ id }) => { await deleteItem(id); return text(`Item ${id} deleted.`); }, );3.1.2 Tool 上下文(Context)
回调的第二个参数ctx提供高级能力:ctx.reportProgress?.(0, 100, "Starting...")上报进度、ctx.log("info", ...)结构化日志、ctx.client.can("sampling")探测客户端能力并配合ctx.sample()请求 LLM 协助处理。
3.1.3 结构化输出(outputSchema)
用outputSchema声明经过类型校验的输出,配合object()返回:
server.tool( { name: "get-stats", schema: z.object({ period: z.string() }), outputSchema: z.object({ total: z.number(), average: z.number(), trend: z.enum(["up", "down", "flat"]), }), }, async ({ period }) => { return object({ total: 150, average: 42.5, trend: "up" }); }, );仓库示例 tools/product-search.ts 即采用此模式:search-tools是触发 Widget UI 的工具,get-fruit-details则声明了outputSchema(fruit: string、color: string、facts: string[]),作为 Widget 内部通过工具调用获取详情的"数据工具"。该文件还展示了_meta中"ui/previewData"的用法——在尚无真实调用时为 UI Studio 提供预览数据。
3.2 Resource:客户端可读取的只读数据
静态资源通过固定uri暴露:
server.resource( { uri: "config://settings", name: "Application Settings", description: "Current server configuration", mimeType: "application/json", }, async () => object({ theme: "dark", version: "1.0.0", language: "en" }), ); server.resource( { uri: "docs://guide", name: "User Guide", mimeType: "text/markdown", }, async () => markdown("# User Guide\n\nWelcome to the app!"), );动态资源则在回调中实时取数后返回(stats://current示例)。参数化资源使用server.resourceTemplate(),详见本文第六节。
3.3 Prompt:可复用的消息模板
server.prompt( { name: "code-review", description: "Generate a code review for the given language", schema: z.object({ language: z.string().describe("Programming language"), focusArea: z.string().optional().describe("Specific area to focus on"), }), }, async ({ language, focusArea }) => { const focus = focusArea ? ` Focus on ${focusArea}.` : ""; return text( `Please review this ${language} code for best practices and potential issues.${focus}`, ); }, );3.4 Zod Schema 最佳实践
Zod schema 直接决定模型能拿到多好的输入描述,因此要求:
- 每个字段都加
.describe()——这是模型理解参数的唯一途径; - 非必填字段用
.optional(); - 适当添加校验(
.min()、.max()、.enum()); - 有固定取值集合时用
z.enum()而非z.string()。
// 好:描述清晰且有约束 const schema = z.object({ city: z.string().describe("City name (e.g., 'New York', 'Tokyo')"), units: z .enum(["celsius", "fahrenheit"]) .optional() .describe("Temperature units"), limit: z.number().min(1).max(50).optional().describe("Max results to return"), }); // 坏:无描述 const schema = z.object({ city: z.string(), units: z.string(), limit: z.number(), });3.5 错误处理与环境变量
错误处理统一走error()响应(返回前捕获异常),例如fetchFromAPI返回空时return error(\No data found for ID: ${id}`),异常时return error(`Failed to fetch data: ${...}`)`。
需要 API Key 时从环境变量读取,未配置时直接返回明确错误提示;同时创建.env.example记录所有必需变量:
const API_KEY = process.env.WEATHER_API_KEY; server.tool( { name: "get-weather", schema: z.object({ city: z.string() }) }, async ({ city }) => { if (!API_KEY) { return error( "WEATHER_API_KEY not configured. Please set it in the Env tab.", ); } // ... }, );3.6 自定义 HTTP 路由与启动
MCPServer继承自 Hono,因此可以直接挂自定义 HTTP 端点:
server.get("/api/health", (c) => c.json({ status: "ok" })); server.post("/api/webhook", async (c) => { const body = await c.req.json(); return c.json({ received: true }); });启动逻辑:
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3000; server.listen(PORT);四、响应助手 API 全参考
references/response-helpers.md 是响应助手的完整参考,所有助手均从mcp-use/server导入:
import { text, object, markdown, html, image, audio, binary, error, mix, widget, resource, } from "mcp-use/server";| 助手 | 返回类型 | 适用场景 |
|---|---|---|
text(str) | 纯文本 | 简单文本响应(支持多行模板字符串) |
object(data) | JSON | 结构化数据(支持嵌套对象) |
markdown(str) | Markdown | 带标题、列表、代码块的格式化文本 |
html(str) | HTML | 富 HTML 内容 |
image(data, mime?) | 图片 | base64 数据、Buffer 或文件路径 |
audio(data, mime?) | 音频 | base64 数据、Buffer 或文件路径 |
binary(data, mime) | 二进制 | PDF、ZIP 等 |
error(msg) | 错误 | 操作失败(会置isError: true通知模型) |
resource(uri, content) | 资源 | 在工具响应中内嵌资源引用 |
mix(...results) | 组合 | 一个响应中包含多种内容类型 |
widget({ props, output }) | Widget | 交互式 UI + 提供给模型的内容 |
几个需要特别注意的用法:
- 图片/音频:三种入参形式——
image(base64Data, "image/png")、image(imageBuffer, "image/jpeg")、await image("/path/to/image.png")(文件路径形式是异步的,需await);audio同理(audio/wav、audio/mp3、文件路径)。 - 内嵌资源:两参形式
resource("report://analysis-123", text("Full report content here...")),三参形式resource("data://export", "application/json", '{"items": [1, 2, 3]}')。 - 混合响应:
mix(text("Analysis complete:"), object({ score: 95 }), markdown("## Recommendations\n- Optimize query")),也可以混合resource(...)内嵌资源。
4.1 Widget 响应
工具返回交互界面时使用widget(),注意区分props(传给 Widget UI,模型不可见)与output(模型看到的文本):
server.tool( { name: "show-data", schema: z.object({ query: z.string() }), widget: { name: "data-display", // resources/ 下的 Widget 名 invoking: "Loading...", // 工具执行期间的提示文本 invoked: "Data loaded", // 工具完成后的提示文本 }, }, async ({ query }) => { const data = await fetchData(query); return widget({ props: { items: data.items, query, total: data.total }, output: text(`Found ${data.total} results for "${query}"`), message: `Displaying ${data.total} results`, // 可选 }); }, );widget()的字段为props(传给组件的数据)、output(模型看到的内容)、message(可选文本);工具配置widget的字段为name(resources/下的文件名)、invoking、invoked。
4.2 自动补全(completable)
对 prompt 参数与资源模板参数提供补全候选,支持静态列表与动态回调:
import { completable } from "mcp-use/server"; // 静态列表 server.prompt( { name: "code-review", schema: z.object({ language: completable(z.string(), ["python", "typescript", "go", "rust", "java"]), }), }, async ({ language }) => text(`Review this ${language} code.`), ); // 动态回调 server.prompt( { name: "get-user", schema: z.object({ username: completable(z.string(), async (value) => { const users = await searchUsers(value); return users.map((u) => u.name); }), }), }, async ({ username }) => text(`Get info for ${username}`), );五、Widget 实战:用 React 构建交互式 UI
references/widgets.md 是 Widget 开发的完整指南。其工作机制为:在resources/下创建 React 组件 → 组件导出widgetMetadata(描述 + props 的 Zod schema)和默认 React 组件 → mcp-use 自动注册为工具与资源 → 工具被调用时 Widget 以工具输出数据渲染。
5.1 文件组织与命名
- 单文件:
resources/weather-display.tsx→ Widget 名weather-display - 文件夹(复杂 Widget):
resources/product-search/下必须含widget.tsx作为入口(组件名固定为widget.tsx),其余components/、hooks/、types.ts自由组织 - 文件名/文件夹名即 Widget 名,统一使用 kebab-case
仓库的真实 Widget 位于 resources/product-search-result/widget.tsx,对应的工具注册在 tools/product-search.ts,其中widget.name必须与resources/下的文件夹名product-search-result完全一致。
5.2 Widget 文件的两个必导出项
每个 Widget 文件必须导出:
widgetMetadata——含description(展示内容说明)与props(Zod schema)的对象;- 默认 React 组件——UI 实现。
WidgetMetadata完整字段:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
description | string | 是 | Widget 展示什么 |
props | z.ZodObject | 是 | Widget 输入数据的 Zod schema |
exposeAsTool | boolean | 否 | 是否自动注册为工具(默认false) |
toolOutput | CallToolResult \| (params => CallToolResult) | 否 | 模型在自动注册工具被调用时看到的内容 |
title | string | 否 | 展示标题 |
annotations | object | 否 | readOnlyHint、destructiveHint等 |
metadata | object | 否 | CSP、边框、尺寸、调用状态文本等配置 |
metadata.invoking | string | 否 | 工具运行期间的状态文本(inspector 中以 shimmer 展示,默认"Loading {name}...") |
metadata.invoked | string | 否 | 工具完成后的状态文本(默认"{name} ready") |
关于exposeAsTool的重要设计:Widget 默认只注册为 MCP 资源,不自动注册为工具。当你在工具配置中写了widget: { name: "my-widget" }时,省略exposeAsTool是正确的做法——由自定义工具负责使 Widget 可被调用;只有不写自定义工具、希望 Widget 被自动注册为工具时才设置exposeAsTool: true。
toolOutput用于控制自动注册工具被调用时模型看到的内容:
export const widgetMetadata: WidgetMetadata = { description: "Recipe card", props: z.object({ name: z.string(), ingredients: z.array(z.string()) }), toolOutput: (params) => text( `Showing recipe: ${params.name} (${params.ingredients.length} ingredients)`, ), };5.3useWidgetHook:Widget 的数据与能力入口
useWidget是访问 Widget 数据与能力的主 Hook:
const { // 核心数据 props, // Widget 输入数据 isPending, // 工具仍在执行时为 true(props 可能不完整) toolInput, // 原始工具输入参数 output, // 额外工具输出数据 metadata, // 响应元数据 // 持久状态 state, // 跨渲染持久化的 Widget 状态 setState, // 更新持久状态:setState(newState) 或 setState(prev => newState) // 宿主环境 theme, // 'light' | 'dark' displayMode, // 'inline' | 'pip' | 'fullscreen' safeArea, // { insets: { top, bottom, left, right } } maxHeight, // 可用最大高度(像素) userAgent, // { device: { type }, capabilities: { hover, touch } } locale, // 用户区域设置(如 'en-US') timeZone, // IANA 时区 // 动作 callTool, // 调用另一个 MCP 工具:callTool("tool-name", { args }) sendFollowUpMessage, // 触发 LLM 响应:sendFollowUpMessage("analyze this") openExternal, // 打开外部 URL requestDisplayMode, // 请求切换显示模式:requestDisplayMode("fullscreen") mcp_url, // MCP 服务端 base URL,用于自定义 API 请求 } = useWidget();加载态处理是硬性要求:Widget 在工具执行完成前就会渲染,因此必须处理isPending,否则在props不完整时直接解构会出错。正确写法是先用McpUseProvider autoSize包裹渲染 Loading 占位,待isPending为 false 后再安全使用props。
Widget 内调用其他工具(仓库product-search场景中 Widget 内调用get-fruit-details即此模式):
const { callTool } = useWidget(); const handleRefresh = async () => { try { const result = await callTool("get-weather", { city: "Tokyo" }); console.log(result.content); } catch (err) { console.error("Tool call failed:", err); } };触发 LLM 继续对话:
const { sendFollowUpMessage } = useWidget(); <button onClick={() => sendFollowUpMessage("Compare the weather in these cities")}> Ask AI to Compare </button>;持久状态:
const { state, setState } = useWidget(); await setState({ favorites: [...(state?.favorites || []), city] }); await setState((prev) => ({ ...prev, count: (prev?.count || 0) + 1 }));5.4 便捷 Hooks 与 McpUseProvider
简单场景可直接使用便捷 Hooks:useWidgetProps<MyProps>()只取 props、useWidgetTheme()只取主题、useWidgetState<MyState>({ count: 0 })提供类似useState的体验。
Widget 内容必须包裹在McpUseProvider中,常用属性:autoSize(自动调整高度以适应内容,默认false)、viewControls(boolean | "pip" | "fullscreen",显示显示模式控制按钮,默认false)、debugger(显示调试 inspector 覆盖层,默认false)。
样式上内联样式与 Tailwind 均可:<div style={{ padding: 20, borderRadius: 12 }}>或<div className="p-5 rounded-xl bg-blue-50">。
5.5 工具侧widget配置
server.tool({ name: "tool-name", schema: z.object({ ... }), widget: { name: "widget-name", // 必须与 resources/ 下文件名一致 invoking: "Loading...", invoked: "Ready", widgetAccessible: true, // Widget 能否调用其他工具(默认 true) }, }, async (input) => { ... });5.6 完整端到端示例
index.ts(服务端):
import { MCPServer, widget, text, object } from "mcp-use/server"; import { z } from "zod"; const server = new MCPServer({ name: "recipe-finder", version: "1.0.0", baseUrl: process.env.MCP_URL || "http://localhost:3000", }); const mockRecipes = [ { id: "1", name: "Pasta Carbonara", cuisine: "Italian", time: 30, ingredients: ["pasta", "eggs", "bacon", "parmesan"] }, { id: "2", name: "Chicken Tikka", cuisine: "Indian", time: 45, ingredients: ["chicken", "yogurt", "spices", "rice"] }, { id: "3", name: "Sushi Rolls", cuisine: "Japanese", time: 60, ingredients: ["rice", "nori", "fish", "avocado"] }, ]; server.tool( { name: "search-recipes", description: "Search for recipes by query or cuisine", schema: z.object({ query: z.string().describe("Search query (e.g., 'pasta', 'chicken')"), cuisine: z.string().optional().describe("Filter by cuisine"), }), widget: { name: "recipe-list", invoking: "Searching recipes...", invoked: "Recipes found", }, }, async ({ query, cuisine }) => { const results = mockRecipes.filter( (r) => r.name.toLowerCase().includes(query.toLowerCase()) || (cuisine && r.cuisine.toLowerCase() === cuisine.toLowerCase()), ); return widget({ props: { recipes: results, query }, output: text(`Found ${results.length} recipes for "${query}"`), }); }, ); server.listen();resources/recipe-list.tsx(Widget):
import { McpUseProvider, useWidget, type WidgetMetadata } from "mcp-use/react"; import { z } from "zod"; export const widgetMetadata: WidgetMetadata = { description: "Display recipe search results", props: z.object({ recipes: z.array( z.object({ id: z.string(), name: z.string(), cuisine: z.string(), time: z.number(), ingredients: z.array(z.string()), }), ), query: z.string(), }), exposeAsTool: false, }; export default function RecipeList() { const { props, isPending } = useWidget(); if (isPending) { return ( <McpUseProvider autoSize> <div style={{ padding: 16 }}>Searching...</div> </McpUseProvider> ); } return ( <McpUseProvider autoSize> <div style={{ padding: 16 }}> <h2 style={{ margin: "0 0 12px" }}>Recipes for "{props.query}"</h2> {props.recipes.length === 0 ? ( <p style={{ color: "#999" }}>No recipes found.</p> ) : ( <div style={{ display: "flex", flexDirection: "column", gap: 12 }}> {props.recipes.map((recipe) => ( <div key={recipe.id} style={{ padding: 16, borderRadius: 8, border: "1px solid #e5e7eb", background: "#fff" }} > <h3 style={{ margin: "0 0 4px" }}>{recipe.name}</h3> <p style={{ margin: 0, color: "#666", fontSize: 14 }}> {recipe.cuisine} · {recipe.time} min · {recipe.ingredients.join(", ")} </p> </div> ))} </div> )} </div> </McpUseProvider> ); }六、参数化资源:URI 模板模式
references/resource-templates.md 讲解了server.resourceTemplate()的用法。它的回调签名支持直接解构路径参数,且支持多参数与查询参数:
// 单参数:user://123/profile server.resourceTemplate( { uriTemplate: "user://{userId}/profile", name: "User Profile", description: "Get user profile by ID", mimeType: "application/json", }, async ({ userId }) => { const user = await fetchUser(userId); return object(user); }, ); // 多参数:org://acme/team/engineering server.resourceTemplate( { uriTemplate: "org://{orgId}/team/{teamId}", name: "Team Details", }, async ({ orgId, teamId }) => object(await fetchTeam(orgId, teamId)), ); // 可选参数:第二个回调参数可访问 searchParams server.resourceTemplate( { uriTemplate: "file://{path}", name: "File Content", }, async ({ path }, { searchParams }) => { const format = searchParams?.get("format") || "text"; const content = await readFile(path); return format === "json" ? object(content) : text(content); }, );Skill 推荐的 URI scheme 约定:
| Scheme | 用途 | 示例 |
|---|---|---|
config:// | 配置数据 | config://settings、config://env |
user:// | 用户相关数据 | user://{id}/profile |
docs:// | 文档 | docs://api、docs://guide |
stats:// | 统计/指标 | stats://current、stats://daily |
file:// | 文件内容 | file://{path} |
db:// | 数据库记录 | db://users/{id} |
api:// | API 端点 | api://weather/{city} |
ui:// | UI 组件 | ui://widget/{name}.html |
完整的资源服务器示例(静态资源 + 参数化模板 + 嵌套模板 + 文档资源)见 references/resource-templates.md 的 Complete Example 一节,其中嵌套模板user://{userId}/posts/{postId}展示了一个模板同时携带两个路径参数的写法。
七、迭代开发与工程化建议
Skill 对"在既有代码上做迭代"给出了明确流程:先读当前index.ts了解已有内容;保留所有既有 tools、resources、widgets;新功能与既有代码并列新增;优先更新既有 Widget 文件而非创建重复文件。这一原则在仓库示例中得到了严格执行——index.ts 通过register(server)模式将工具注册函数拆到tools/目录(如 tools/product-search.ts),并在文件头注释里清晰列出"新增一个 MCP App Widget"的三步流程:创建resources/<widget-name>/widget.tsx→ 创建tools/<tool-name>.ts并导出register(server)→ 在index.ts标记位置 import 并调用register()。
此外,本 Skill 自身带有一组评估用例(evals/目录下的skill.json、implementation.json、architecture.json、widgets.json),覆盖需求拆解、服务端实现、架构设计与 Widget 开发四类场景,可作为验证 Agent 行为是否符合上述规范的参考。
八、从 Skill 到生产:落地清单
综合以上内容,基于 mcp-use 构建一个生产级 MCP Server 的完整落地路径是:
- 拆解需求:按"工具 vs Widget vs 资源"四类原语划分核心动作,命名动词开头、一个工具一个能力、一个流程一个 Widget、不做懒加载。
- 搭建骨架:以 index.ts 为模板初始化
MCPServer,配置baseUrl、端口(process.env.PORT ?? "3109")、favicon 与图标;用npm install && npm run dev启动开发服务(自动重建 Widget 并热重启)。 - 实现服务端:按 tools-and-resources.md 编写带
outputSchema与annotations的工具、静态与参数化资源、可复用 prompt;Zod schema 全程.describe()。 - 接入 Widget:在
resources/创建 React 组件,导出widgetMetadata与默认组件,用useWidget()获取 props/state,牢记isPending加载态,用McpUseProvider autoSize包裹,通过callTool实现 Widget 内工具联动。 - 统一响应:按 response-helpers.md 选择
text/object/markdown/html/image/audio/binary/error/mix/widget/resource,用completable()提升参数体验。 - 保护与部署:API Key 走环境变量并配套
.env.example;必要时用 Hono 路由挂自定义 HTTP 端点;最后用mcp-use build/mcp-use deploy(或 Manufact Cloud 的npm run deploy)发布,并通过http://localhost:3109/inspector在浏览器中调试工具与 Widget 的交互效果。
通过本指南,你已具备在 mcp-use 框架下从零构建带交互式 UI 的 MCP Server 的完整知识,并可随时回到仓库中 SKILL.md 及其references/参考文档与 product-search 真实示例中对照验证。
【免费下载链接】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),仅供参考