使用 mcp-use 五分钟构建你的第一个 MCP Server:CopilotKit 仓库 open-mcp-client 快速上手实战
【免费下载链接】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
本指南以 quickstart.md 为核心骨架,讲解如何用mcp-use框架在 5 分钟内完成脚手架搭建、模板选择、开发调试,并逐步实现工具(Tool)、模拟数据、结构化响应与资源(Resource)四大基础能力。读完你可以在本地跑通一个带 Inspector 调试面板的完整 MCP Server,并为后续接入 ChatGPT、Claude 等 MCP Apps 兼容客户端的 Widget 可视化界面打好基础。
一、先认识 mcp-use 与本文的实战环境
本文的实操对象是 CopilotKit 仓库中的 open-mcp-client 展示项目,其核心是一个基于mcp-use框架实现的 MCP Server 示例:
- 服务器入口:index.ts —— 创建
MCPServer实例并注册工具; - 工具实现:tools/product-search.ts —— 一个返回 Widget 可视化界面的完整工具示例;
- 依赖清单:package.json —— 声明了
mcp-use、zod、react等运行时依赖。
从依赖与源码结构看,mcp-use是一个把 MCP 协议(模型上下文协议)与 HTTP 服务器能力融为一体的 TypeScript 框架:它基于 Hono Web 框架构建,开发者通过server.tool()、server.resource()、server.prompt()声明 MCP 原语,同时还能通过server.app直接添加自定义 HTTP 路由。想深入了解这一架构,可以参考同目录下的 architecture.md 与 concepts.md。
在动手之前,请确认本地环境已安装Node.js(建议 18+)与 npm(或 pnpm),MCP 生态相关的类型定义与依赖都会由脚手架自动安装。
二、Setup:脚手架搭建一个全新项目
创建一个全新的 mcp-use 项目只需三条命令:
npx create-mcp-use-app my-server cd my-server npm run dev执行第一条命令时,脚手架会自动完成依赖安装;npm run dev会启动开发服务器,默认监听 3000 端口,并自动打开内置调试器(Inspector)——地址为http://localhost:3000/inspector。
关于端口与启动细节,仓库中的 package.json 给出了脚手架生成项目的真实脚本参考:
"scripts": { "build": "mcp-use build --inline", "dev": "mcp-use build --inline && cross-env NODE_ENV=production npx tsx index.ts", "start": "mcp-use start", "deploy": "mcp-use deploy" }dev:先构建 Widget(--inline表示内联打包),再用tsx直接运行 TypeScript 入口,支持热重载;build/start:生产构建与启动;deploy:一键部署到生产环境。
提示:示例仓库 index.ts 中通过
process.env.MCP_URL与process.env.PORT支持环境变量覆盖,默认端口为 3109(server.listen(parseInt(process.env.PORT ?? "3109", 10)))。这意味着脚手架默认 3000 端口并非硬编码,你可以随时用环境变量调整,方便与本机其他服务错开端口。
三、选择一个合适的模板
脚手架内置了四种模板,应根据你要构建的内容来选择:
| 模板 | 命令 | 适用场景 |
|---|---|---|
| starter(默认) | npx create-mcp-use-app my-server | 功能完整的服务器,自带 tools、resources、prompts 与 widget 示例 |
| mcp-apps | npx create-mcp-use-app my-server --template mcp-apps | 面向 ChatGPT、Claude 等 MCP Apps 兼容客户端的 Widget 专注型模板 |
| blank | npx create-mcp-use-app my-server --template blank | 空白模板——只含带注释示例的裸服务器 |
| GitHub repo | npx create-mcp-use-app my-server --template owner/repo | 从任意 GitHub 仓库拉取自定义或社区模板 |
不确定选哪个时,直接选mcp-apps。它是官方推荐默认值,内置了对 ChatGPT、Claude 等 MCP Apps 兼容客户端的 Widget 支持,后续若要给工具添加可视化界面,可以省去大量配置工作。
常见命令行参数(Common Flags)
# 选择包管理器 npx create-mcp-use-app my-server --npm npx create-mcp-use-app my-server --pnpm # 跳过交互式提问 npx create-mcp-use-app my-server --install --skills # 列出所有可用模板 npx create-mcp-use-app --list-templates参数说明:
--npm/--pnpm:指定依赖安装工具,不传时按默认策略选择;--install:跳过安装确认,直接安装依赖;--skills:同时写入脚手架自带的开发技能文件(例如本仓库.agent/skills/目录下这套 mcp-apps-builder 文档就是此类技能);--list-templates:查看当前版本全部可用模板与 GitHub 仓库模板列表。
经验之谈:仓库中 SKILL.md 反复强调一个原则——不要手写
MCPServer样板代码、package.json 或项目结构。脚手架一次性配置好了 TypeScript、dev 脚本、Inspector 集成、热重载与 Widget 编译,这些手动复制极易出错,务必优先使用脚手架。
四、每个模板会生成什么
脚手架产物的目录结构决定了你的开发组织方式,下面是三个内置模板的标准产物:
starter 模板:
my-server/ ├── index.ts # 服务器入口,含 tool、resource、prompt 示例 ├── resources/ # Widget 目录(含 display-weather.tsx 示例) ├── public/ # 静态资源(favicon、icon) ├── package.json # 预配置脚本:dev、build、start、deploy └── tsconfig.jsonmcp-apps 模板:
my-server/ ├── index.ts # 服务器入口,含返回 Widget 的工具 ├── resources/ # Widget 目录(含 product-search-result/ 示例) │ └── product-search-result/ │ ├── widget.tsx # React Widget(轮播卡片 UI) │ └── components/ # 可复用 Widget 组件 ├── public/ ├── package.json └── tsconfig.jsonblank 模板:
my-server/ ├── index.ts # 裸 MCPServer,示例均为注释 ├── public/ ├── package.json └── tsconfig.json对照仓库源码可以印证这套结构的真实性:index.ts 顶部注释详细说明了添加一个新 MCP App Widget 的三步流程——在resources/<widget-name>/widget.tsx创建 React 组件、在tools/<tool-name>.ts导出注册函数、最后在入口文件 import 并调用register()。而 tools/product-search.ts 正是这一流程的完整落地范例,可作为你编写第一个自定义工具时的对照样板。
五、脚手架搭建后的开发工作流
完成脚手架之后,标准开发循环如下:
npm run dev—— 启动带热重载 + Inspector 的服务器;- 编辑
index.ts添加 tools、resources、prompts; - 在
resources/目录下以.tsx文件添加 Widget; - 在
http://localhost:3000/inspector中测试一切功能; npm run build—— 生成生产构建;npm run deploy—— 部署到生产环境。
这套循环的核心价值在于热重载:保存.ts/.tsx文件后,服务端会自动重建 Widget 并重启服务器,无需手动中断进程,非常适合快速迭代。
六、你的第一个工具:greet
脚手架生成的index.ts已经包含一个基础服务器,现在给它加一个最简单的工具——greet,接收一个名字参数并返回问候语:
import { MCPServer, text } from "mcp-use/server"; import { z } from "zod"; const server = new MCPServer({ name: "my-server", title: "My Server", version: "1.0.0", baseUrl: process.env.MCP_URL || "http://localhost:3000", }); // 新增这个工具 server.tool( { name: "greet", description: "Greet a user by name", schema: z.object({ name: z.string().describe("User's name"), }), }, async ({ name }) => { return text(`Hello, ${name}! Welcome to MCP.`); }, ); server.listen();代码要点拆解:
MCPServer构造参数中的name/title/version是服务器元信息;baseUrl用process.env.MCP_URL提供默认值兜底,这正是仓库 index.ts 采用的环境变量模式;schema使用zod声明入参结构,每个字段都要调用.describe()补充描述——模型依赖这些描述理解参数含义(这也是 SKILL.md 中列出的第一条 Tool 定义最佳实践);- 返回值必须用响应辅助函数
text()包装,而不是直接return一个普通字符串。
保存文件——服务器会自动重载!
在 Inspector 中测试
- 打开 Inspector(
http://localhost:3000/inspector); - 点击List Tools;
- 找到
greet工具; - 点击Call Tool;
- 输入参数
{"name": "Alice"}; - 看到响应:
"Hello, Alice! Welcome to MCP."
Inspector 是 mcp-use 开发体验的核心:它能列出全部已注册工具、资源和提示词,也能直接发起工具调用,是验证每个新功能的第一站。
七、添加模拟数据:一个天气工具
开发阶段的惯例是"Mock 数据优先,之后再接真实 API"。下面用一段硬编码数据实现天气查询:
// 模拟天气数据 const mockWeather: Record<string, { temp: number; conditions: string }> = { "New York": { temp: 22, conditions: "Partly Cloudy" }, London: { temp: 15, conditions: "Rainy" }, Tokyo: { temp: 28, conditions: "Sunny" }, Paris: { temp: 18, conditions: "Overcast" }, }; server.tool( { name: "get-weather", description: "Get current weather for a city", schema: z.object({ city: z.string().describe("City name"), }), }, async ({ city }) => { const weather = mockWeather[city]; if (!weather) { return text(`No weather data for ${city}`); } return text(`Weather in ${city}: ${weather.temp}°C, ${weather.conditions}`); }, );测试方法与上一节相同:
- 用
{"city": "Tokyo"}调用工具; - 期望响应:
"Weather in Tokyo: 28°C, Sunny"。
这段代码展示了工具处理的两个典型分支:数据命中返回正常结果、数据未命中返回友好的兜底文本。真实的工具实现通常在此基础上补充异常捕获与参数校验,可参考 tools/product-search.ts 中get-fruit-details的写法(用outputSchema声明结构化输出,并对未找到的水果返回默认字段)。
八、添加结构化数据:object() 返回
文本返回适合对话场景,但 AI 或客户端需要解析结构化信息时,应使用object()辅助函数返回 JSON:
import { MCPServer, text, object } from "mcp-use/server"; server.tool( { name: "get-weather-detailed", description: "Get detailed weather information", schema: z.object({ city: z.string().describe("City name"), }), }, async ({ city }) => { const weather = mockWeather[city]; if (!weather) { return object({ error: `No data for ${city}` }); } return object({ city, temperature: weather.temp, conditions: weather.conditions, unit: "celsius", timestamp: new Date().toISOString(), }); }, );注意这里的错误分支同样用object()返回结构化错误对象,而不是抛出异常——这是 mcp-use 的约定:错误应作为响应的一部分优雅返回,让模型能读懂失败原因。
从源码层面看,response-helpers.md 列出了完整的辅助函数族:text()、object()、markdown()、image()、error()、widget()、mix()、resource()。它们统一负责正确设置 MIME 类型、保证序列化并支持多内容响应。永远用辅助函数包装返回值,不要直接 return 原始对象——这是该文档反复强调的第一原则。
九、添加一个资源(Resource)
Resource 用于向客户端暴露只读数据,适合配置信息、静态列表等场景:
server.resource( { name: "available_cities", uri: "weather://available-cities", title: "Available Cities", description: "List of cities with weather data", }, async () => object({ cities: Object.keys(mockWeather), }), );在 Inspector 中测试:
- 打开 Inspector →List Resources;
- 找到 "Available Cities";
- 点击Read Resource;
- 看到:
{"cities": ["New York", "London", "Tokyo", "Paris"]}。
Resource 与 Tool 的定位差异很关键:Tool 是 AI 可调用的动作,Resource 是客户端可拉取的只读数据。如果数据需要参数化(例如按城市 ID 取详情),则应使用 Resource 模板(动态 URI),具体可参考 resources.md。
十、进阶路线:从工具到可视化 Widget
掌握了 Tool、结构化响应与 Resource 之后,你的下一个能力跃迁点是Widget——让工具返回可视化 React 界面。在 concepts.md 中,Widget 被定义为"返回视觉 UI 的工具":本质仍是server.tool(),但声明了widget: { name }配置,并在处理器中通过widget({ props, output })返回组件所需数据与模型可见的文本摘要。
仓库里 tools/product-search.ts 就是完整的 Widget 工具范例:search-tools工具返回widget({ props: { query, results }, output: text(...) }),并通过_meta["ui/previewData"]提供尚未调用时的预览数据;配套的get-fruit-details数据工具则供 Widget 内部通过useCallTool()调用,演示了"Widget 外壳 + 数据工具"的组合模式。
建议按以下顺序继续学习(均位于本仓库的 skills 文档体系内):
- 学习响应辅助函数→ response-helpers.md
- 构建你的第一个 Widget→ basics.md
- 查看完整示例→ common-patterns.md
如果只想在本地快速跑通一个带 Widget 的成品,可以直接运行仓库中的 mcp-use-server 示例(package.json 中的npm run dev),在 Inspector 中调用search-tools观察轮播式水果卡片如何渲染。
十一、开发建议与常见误区
综合 quickstart 与 SKILL.md 的实践总结,快速上手阶段最容易踩的坑如下:
| 场景 | ❌ 错误做法 | ✅ 正确做法 |
|---|---|---|
| 返回值 | 直接return { data }原始对象 | 用object()/text()等辅助函数包装 |
| 参数描述 | Zod schema 字段不加说明 | 所有字段调用.describe() |
| 错误处理 | throw new Error(...)抛出异常 | 返回error("...")优雅降级 |
| 工具粒度 | 一个manage-users大而全 | 拆成create-user、list-users等单一职责工具 |
| 数据获取 | list-products+get-product-details两次调用 | 一次返回完整数据,避免懒加载 |
| Widget 状态 | 用select-item工具管理 UI 状态 | Widget 内部用useState自行管理 |
Mock 数据优先是快速上手阶段最重要的方法论:先用硬编码数据把工具链路跑通、把界面效果调好,再在后续迭代中替换为真实 API——tools/product-search.ts 中的水果目录就是这么做的,切换数据源时只需改动fruits常量即可。
【免费下载链接】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),仅供参考