关于 build-mcp-use-server
面向使用 mcp-use 构建 MCP 服务和 ChatGPT 应用的开发者技能,指导从项目检查、实现到验证的流程。重点是依照已安装版本的 API,并遵循 OpenAI Apps SDK UI 规范。
主要功能
- 01检查项目结构与 mcp-use 版本
- 02指导 MCP 服务和视图实现
- 03覆盖 ChatGPT 应用扩展与认证
- 04强调类型约束和无状态请求
- 05使用 CLI 验证工具调用与视图
适用场景
- 01从零搭建 mcp-use ChatGPT 应用
- 02为 MCP 服务添加交互式视图
- 03排查旧版 API 与迁移问题
MCP 相关
该技能指导使用 mcp-use 的 MCPServer 构建 MCP 服务,并通过 mcp-use client 执行真实工具调用以验证实现。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yigitkonur/ skills-by-yigitkonur --skill build-mcp-use-server安装后在 Claude Code 中输入/build-mcp-use-server确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdagents/openai.yamlreferences/advanced-features.mdarchestra-and-multi-host.mdauth.mdchatgpt-apps-ui.mdchatgpt-extensions.mdmigration.mdserver.mdskills-over-mcp.mdverification.mdviews.mdscripts/audit-server-readiness.shaudit-server-readiness.sh.mdcheck-mcp-use-version.shcheck-mcp-use-version.sh.mdscaffold-mcp-use-server.shscaffold-mcp-use-server.sh.md详细介绍
使用 mcp-use 和 ChatGPT Apps 构建
请将已安装的 mcp-use 软件包、其导出的类型、生成的声明文件(mcp-env.d.ts)以及官方文档 docs.mcp-use.com 视为权威信息来源。
本技能以官方 MCPServer API 为基础,采用官方 mcp-use 技能的精简架构,并确保默认情况下,所有使用 mcp-use 构建的 ChatGPT 应用都符合 OpenAI Apps SDK UI(@openai/apps-sdk-ui)规范。
工作流
- 检查项目:检查
package.json、服务入口、导出的工具引用、views/、skills/以及已安装的mcp-use版本(需要 Node.js >= 22.22.2)。 - 搭建或初始化:对于全新项目,使用官方 CLI 搭建:
(无头工具服务请使用npx create-mcp-use-app@latest my-app --template mcp-apps--template mcp-server。) - 阅读相关参考资料:
- 服务端:工具、资源、提示词、中间件、请求上下文、结果封装和
ServerConfig。 - 视图:MCP Apps、React hooks、交互通道、宿主功能和 CSP。
- OpenAI Apps SDK UI 指南:完整的 29 个组件、hooks、Tailwind 4 配置、设计令牌和主题同步。
- ChatGPT 插件扩展:启动入口(
global、thread、file)、原生server.settings()、工具图标、useDeepLink()和useModelContext()。 - 身份验证:OAuth 提供方适配器、
mixedAuth: true、工具级securitySchemes以及ctx.auth中经过验证的身份。 - MCP 优先的技能:
skills/下可复用的 Agent 工作流和约定。 - 高级功能:OpenAPI(
fromOpenAPI)、代理、通知、订阅和交互式信息收集(inputRequired)。 - 多宿主与 Archestra:部署到 ChatGPT、Claude Desktop 和 Archestra(已安装服务路由与自有应用)。
- 迁移:将旧版 v1 模式、已弃用的导入和过时的辅助工具升级到 v2。
- 验证:使用
mcp-use typecheck、mcp-use dev、mcp-use client和mcp-use screenshot进行验证。
- 服务端:工具、资源、提示词、中间件、请求上下文、结果封装和
- 依据已安装的类型实现:始终导出静态工具引用,并将服务实例作为默认导出。
- 以确定的方式验证:通过
mcp-use client执行实际工具调用,并使用mcp-use screenshot捕获视图截图。
核心不变量
- 根服务器导入:始终从
"mcp-use"导入MCPServer和服务器原语(旧版"mcp-use/server"子路径已弃用)。 - 导出静态工具引用:始终导出每个工具引用(
export const myTool = server.tool(...))。TypeScript 编译器会根据导出的工具名称,在mcp-env.d.ts中生成视图和客户端契约。 - 默认服务器导出:始终默认导出服务器实例(
export default server;)。CLI 运行器(mcp-use dev、build、start)负责管理 HTTP 监听器和 Vite 视图管线。 - 定义优先的工具与标准 Schema:使用
server.tool(definition, callback)定义工具。工具参数使用inputSchema;模型提示词使用schema。始终描述由模型填充的 Schema 字段。 - 原始结果封装:工具回调必须返回原始
CallToolResult封装({ content: [...], structuredContent?: ... })。不要使用已弃用的 v1 助手函数(text()、object()、widget())。预期的失败应返回{ isError: true, content: [...] };意外的失败应抛出异常。 - 视图架构:将每个视图放在
views/<name>/view.tsx。每个视图必须且只能有一个归属工具,且该工具必须包含匹配的view: { name: "<name>" }和必需的outputSchema。返回匹配的structuredContent。 - OpenAI Apps SDK UI 合规性:每个 ChatGPT 应用都必须安装
@openai/apps-sdk-ui、导入@openai/apps-sdk-ui/css、使用@source配置 Tailwind 4,并使用<AppsSDKUIProvider linkComponent="a">包装交互式组件。 - 无状态请求模型:服务器实例的作用域为单个请求。切勿使用模块全局变量或内存中的会话注册表来保存跨请求状态或进行身份验证。请使用
ctx.requestState或外部数据库。 - 严格的 CLI 标志语法:在 v2.7.2 及更高版本中,布尔型 CLI 标志不接受内联值(
--tunnel=false会失败;请使用--tunnel或省略)。
防护措施与常见反模式
- 绝不要导入不存在的
@openai/apps-sdk-ui组件:Card在@openai/apps-sdk-ui中不存在。界面卡片是标准的<div>元素,使用 Tailwind 工具类设置样式(rounded-2xl border border-default bg-surface p-4 shadow-sm)。AvatarGroup和CopyTooltip是官方的@openai/apps-sdk-ui组件(分别来自@openai/apps-sdk-ui/components/Avatar和@openai/apps-sdk-ui/components/Tooltip)。Cart和Checkmark在@openai/apps-sdk-ui/components/Icon中不存在。请使用Check、CheckCircle、Search、Plus、Settings等。
- 绝不要在
<AppsSDKUIProvider>上省略linkComponent:TypeScript 契约要求提供linkComponent(例如<AppsSDKUIProvider linkComponent="a">)。 - 绝不要不做类型收窄就读取
useToolContext().toolOutput:useToolContext()是一个可辨识联合类型。当status === "pending"时,toolOutput为undefined。访问toolOutput前,务必先处理status === "pending"和status === "error"。对于已取消的操作,请将error收窄为ToolCancelledError。 - 在
MCPServer的顶层配置websiteUrl、icons和logging:icons?: Icon[]、websiteUrl?: string和logging?: LoggingOptions是ServerConfig的顶层属性。不要将它们嵌套在虚构的branding: { ... }对象中(server.branding是只读 getter)。- 服务日志级别通过
logging: { enabled: true, level: "info" }配置,也可通过MCP_USE_LOG_LEVEL环境变量覆盖。
- 绝不要使用旧版 v1 的
ctx.elicit():它在迁移到无状态请求模型期间已于 v2.0.0 移除。请使用官方协议原语:inputRequired、inputRequired.elicit、inputRequired.elicitUrl、inputResponse和acceptedContent。 - 绝不要使用错误的 CLI 命令:
- 客户端连接语法:
mcp-use client connect <name> <url> [options](例如npx mcp-use client connect dev http://localhost:3000/mcp)。 - 截图语法:
mcp-use screenshot (--server <name> | --mcp <url>) --tool <name> [args...] [options]。
- 客户端连接语法:
MCPServer 基础架构
import { MCPServer } from "mcp-use";
const server = new MCPServer({
name: "catalog-server",
version: "1.0.0",
title: "Inventory & Catalog Manager",
description: "Manage product catalog, stock levels, and search items.",
instructions: "Use lookup-item before creating an order. Confirm stock availability.",
basePath: "/mcp", // default route
host: "127.0.0.1", // "0.0.0.0" for public hosting
port: 3000,
legacy: "stateless", // "stateless" (default) or "reject"
websiteUrl: "https://example.com",
icons: [
{ src: "icons/logo.svg", mimeType: "image/svg+xml", sizes: ["20x20"] }
],
logging: { enabled: true, level: "info" }, // Overridden by MCP_USE_LOG_LEVEL env var
skills: true, // Auto-discover conventional skills/ directory
cors: {
origin: "*",
credentials: true,
},
});
export default server;
工具定义与结果封装
文档:服务工具
import { MCPServer } from "mcp-use";
import { z } from "zod";
const server = new MCPServer({ name: "catalog", version: "1.0.0" });
export const getProduct = server.tool(
{
name: "get-product",
title: "Get Product Details",
description: "Fetch product metadata, pricing, and live inventory by SKU.",
icons: [
{ src: "icons/product.svg", mimeType: "image/svg+xml", sizes: ["20x20"] }
],
inputSchema: z.object({
sku: z.string().describe("Stock Keeping Unit identifier (e.g. 'PROD-100')"),
}),
outputSchema: z.object({
sku: z.string(),
title: z.string(),
price: z.number(),
inStock: z.boolean(),
}),
annotations: {
readOnlyHint: true,
openWorldHint: false,
},
visibility: "model", // "model" (default) or "app" (hidden from LLM, visible to UI views)
view: {
name: "product-card", // binds to views/product-card/view.tsx
description: "Interactive product display card",
},
},
async ({ sku }, ctx) => {
// Expected operational failure
if (!sku.startsWith("PROD-")) {
return {
isError: true,
content: [{ type: "text", text: `Invalid SKU format: ${sku}` }],
};
}
const data = { sku, title: "Precision Screwdriver", price: 29.99, inStock: true };
return {
content: [{ type: "text", text: `Product ${data.title} (${sku}) is in stock at $${data.price}` }],
structuredContent: data,
_meta: { internalId: "db_98213" }, // Private to view, hidden from LLM
};
}
);
export default server;
ChatGPT Apps 与 OpenAI Apps SDK UI 合规性
文档:
1. 安装与样式设置
npm install @openai/apps-sdk-ui tailwindcss @tailwindcss/vite
在 views/main.css(或视图专用 CSS)中:
@import "tailwindcss";
@import "@openai/apps-sdk-ui/css";
@source "../node_modules/@openai/apps-sdk-ui";
2. 使用 Apps SDK UI 与主题同步的视图组件
创建 views/product-card/view.tsx:
import "./main.css";
import React, { useEffect } from "react";
import { useToolContext, useModelContext, useViewTheme, ToolCancelledError, type ViewConfig } from "mcp-use/react";
import { AppsSDKUIProvider } from "@openai/apps-sdk-ui/components/AppsSDKUIProvider";
import { Button } from "@openai/apps-sdk-ui/components/Button";
import { Badge } from "@openai/apps-sdk-ui/components/Badge";
import { applyDocumentTheme } from "@openai/apps-sdk-ui/theme";
import { Check, Plus } from "@openai/apps-sdk-ui/components/Icon";
export const viewConfig = {
autoResize: true,
displayModes: ["inline", "fullscreen"],
preferredDisplayMode: "inline",
} satisfies ViewConfig;
export default function ProductCardView() {
const { status, toolInput, toolOutput, error } = useToolContext<"get-product">();
const theme = useViewTheme();
const { add } = useModelContext();
// Synchronize host theme with Apps SDK UI data-theme attribute
useEffect(() => {
if (theme) applyDocumentTheme(theme);
}, [theme]);
if (status === "pending") {
return <div className="p-4 text-secondary text-sm">Loading SKU {toolInput?.sku}…</div>;
}
if (status === "error") {
if (error instanceof ToolCancelledError) {
return <div className="p-4 text-secondary text-sm">Action cancelled: {error.reason}</div>;
}
return <div className="p-4 text-danger text-sm">Error: {error.message}</div>;
}
return (
<AppsSDKUIProvider linkComponent="a">
{/* Standard surface card styled with semantic tokens */}
<div className="w-full max-w-md rounded-2xl border border-default bg-surface p-4 shadow-sm">
<div className="flex items-start justify-between">
<div>
<h2 className="heading-md">{toolOutput.title}</h2>
<p className="text-secondary text-sm">SKU: {toolOutput.sku}</p>
</div>
<Badge color={toolOutput.inStock ? "success" : "warning"}>
{toolOutput.inStock ? "In Stock" : "Backorder"}
</Badge>
</div>
<div className="mt-4 flex items-center justify-between border-t border-subtle pt-3">
<span className="text-lg font-semibold">${toolOutput.price.toFixed(2)}</span>
<Button
color="primary"
onClick={() => {
// Attach verified evidence to next ChatGPT model turn (synchronous void)
add("selected:product", {
type: "text",
text: `Selected SKU ${toolOutput.sku}: $${toolOutput.price}`,
});
}}
>
<Plus className="size-4" />
Add to Chat
</Button>
</div>
</div>
</AppsSDKUIProvider>
);
}
3. 完整的 Apps SDK UI 组件清单(29 个组件)
全部从 @openai/apps-sdk-ui/components/<Component> 导入:
| 类别 | 组件 | 用途 |
|---|---|---|
| 反馈与状态 | Alert、Badge、Indicator、EmptyMessage、ShimmerText | 状态横幅、实时徽章、空状态和加载闪烁效果。 |
| 身份与图标 | Avatar、Image、Icon | 用户资料、图片回退和 700 多个单色 SVG。 |
| 操作 | Button、ButtonLink(以及 CopyButton) | 主要、次要、柔和、轮廓样式的操作按钮和复制控件。 |
| 表单输入 | Input、Textarea、Checkbox、Switch、RadioGroup、Slider、SegmentedControl、Select、SelectControl、TagInput | 无障碍且经过验证的输入项和分组控件。 |
| 日期选择器 | DatePicker、DateRangePicker | 日历弹出窗口和日期范围选择器。 |
| 浮层 | Menu、Popover、Tooltip | 下拉菜单、锚定弹出窗口和上下文工具提示。 |
| 排版与内容 | Markdown、CodeBlock、TextLink | 样式化 Markdown 渲染器、语法高亮代码块和主题超链接。 |
| 动效与过渡 | Transition(Animate、AnimateLayout、SlotTransitionGroup、TransitionGroup) | 用于 iframe 的平滑高度、淡入淡出和布局变形过渡效果。 |
| 提供程序 | AppsSDKUIProvider | 提供路由器 linkComponent 配置的根上下文。 |
4. 官方 Hook 套件(@openai/apps-sdk-ui/hooks)
useBreakpoint(size: BreakpointName):响应式断点匹配布尔值(例如useBreakpoint("md"))。useAutoGrowTextarea():动态自动调整文本区域高度。useAnimatedScrollTo():支持取消的平滑程序化滚动。useDocumentVisible():跟踪 iframe 可见性。useIsMounted()、useLatestValue()、usePrevious()、useScrollable()、useSimulatedProgress()、useStableCallback()、useTextSelection()、useTrailingValue()。
ChatGPT 插件扩展
文档:ChatGPT 插件扩展 | 扩展参考
1. 类型化启动入口
在 tool.view.entrypoints 上声明启动界面:
global:从 ChatGPT 顶层启动器启动应用(输入架构必须接受{})。thread:在对话标签页中启动应用(输入架构必须接受{})。file:将应用作为文件查看器启动,以处理匹配的扩展名(输入架构必须接收{ file: { name: string, resourceUri: string } })。
export const openCsvViewer = server.tool(
{
name: "open-csv",
title: "CSV Table Viewer",
inputSchema: z.object({
file: z.object({ name: z.string(), resourceUri: z.string() }),
}),
outputSchema: z.object({ file: z.object({ name: z.string(), resourceUri: z.string() }) }),
view: {
name: "csv-viewer",
entrypoints: [{ type: "file", extensions: [".csv", ".tsv"] }],
},
},
async ({ file }) => ({ content: [], structuredContent: { file } })
);
2. 原生插件设置(server.settings)
通过类型化字段和布局分区注册原生 ChatGPT 插件设置:
server.settings({
fields: {
theme: { type: "string", label: "Theme", default: "system" },
autoSync: { type: "boolean", label: "Automatic Sync", default: true },
},
layout: [
{
title: "Preferences",
fields: ["theme", "autoSync"],
},
],
read: async (ctx) => ({ theme: "system", autoSync: true }),
update: async (patch, ctx) => ({ theme: "system", autoSync: true, ...patch }),
});
3. 接收深层链接(useDeepLink)
读取发送至全局入口的传入 URL:
import { useDeepLink } from "mcp-use/react";
export function AppRouter() {
const { url } = useDeepLink(); // e.g. "/items?tag=hardware"
}
4. 所选模型上下文(useModelContext)
管理随下一轮用户模型对话发送的证据附件(attachments 是 Record<string, ModelContextBlock>,add 是同步的 void):
import { useModelContext } from "mcp-use/react";
const { attachments, add, remove, clearAttachments } = useModelContext();
// Attach text summary (synchronous void, no title)
add("summary", { type: "text", text: "Order Summary: Total $120.00" });
// Attach image with Base64 data (PNG/JPEG/GIF/WebP, max 10MB)
add("receipt", { type: "image", data: receiptBase64, mimeType: "image/png" });
// Enumerate attached blocks
Object.entries(attachments).map(([key, block]) => (
<span key={key}>{key}: {block.type}</span>
));
身份验证与混合身份验证
在 MCPServer 上配置 mixedAuth: true,即可通过一个端点提供公开发现、可选身份验证和受保护工具:
import { MCPServer } from "mcp-use";
import { oauthAuth0Provider } from "mcp-use/oauth/auth0";
import { z } from "zod";
const server = new MCPServer({
name: "store",
version: "1.0.0",
oauth: oauthAuth0Provider({ domain: process.env.AUTH0_DOMAIN! }),
mixedAuth: true, // Enables unauthenticated tools/list discovery
});
// Public tool: Anyone may call without token
export const searchCatalog = server.tool(
{
name: "search-catalog",
inputSchema: z.object({ query: z.string() }),
securitySchemes: [{ type: "noauth" }],
},
async ({ query }, ctx) => {
return { content: [{ type: "text", text: `Results for ${query}` }] };
}
);
// Protected tool: Requires sign-in with specific scopes
export const checkout = server.tool(
{
name: "checkout",
inputSchema: z.object({ cartId: z.string() }),
securitySchemes: [{ type: "oauth2", scopes: ["orders:write"] }],
},
async ({ cartId }, ctx) => {
const userId = ctx.auth.user.id;
return { content: [{ type: "text", text: `Order placed for user ${userId}` }] };
}
);
支持的提供程序:Clerk(mcp-use/oauth/clerk)、Auth0(mcp-use/oauth/auth0)、WorkOS(mcp-use/oauth/workos)、Supabase(mcp-use/oauth/supabase)、Keycloak(mcp-use/oauth/keycloak)、Better Auth(mcp-use/oauth/better-auth)、Scalekit(mcp-use/oauth/scalekit)、Convex(mcp-use/oauth/convex)和 Custom(oauthCustomProvider)。
交互式信息收集
在迁移到无状态请求模型的过程中,ctx.elicit() 已在 v2.0.0 中移除。请使用官方协议原语:
import { MCPServer, inputRequired, inputResponse, acceptedContent } from "mcp-use";
import { z } from "zod";
const confirmSchema = z.object({ approve: z.boolean().describe("Confirm action") });
server.tool(
{
name: "delete-resource",
inputSchema: z.object({ id: z.string() }),
},
async ({ id }, ctx) => {
const response = inputResponse(ctx.inputResponses, "delete-approval");
if (response.kind === "elicit" && response.action !== "accept") {
return { isError: true, content: [{ type: "text", text: "Action cancelled" }] };
}
const confirmation = acceptedContent(ctx.inputResponses, "delete-approval", confirmSchema);
if (!confirmation) {
return inputRequired({
inputRequests: {
"delete-approval": inputRequired.elicit({
message: `Permanently delete resource ${id}?`,
requestedSchema: confirmSchema,
}),
},
});
}
if (!confirmation.approve) {
return { isError: true, content: [{ type: "text", text: "Action rejected" }] };
}
// Side-effects execute only after confirmed, validated input
await db.delete(id);
return { content: [{ type: "text", text: `Deleted ${id}` }] };
}
);
对于外部授权:inputRequired.elicitUrl({ message: "...", url: authUrl })。
通过 MCP 提供技能(SEP-2640)
文档:通过 MCP 提供技能 | 技能参考
与工具一同提供可复用的指令工作流:
- 将技能放在
skills/<skill-name>/SKILL.md下。 - 默认启用自动发现。可在
MCPServer上通过skills: true强制启用,或通过skills: false禁用。 - 主机通过
skills/list和skills/get获取技能元数据。
CLI 与工具参考
# Scaffold new v2 project
npx create-mcp-use-app@latest my-server --template mcp-apps
# Start development with hot reload, Vite views pipeline, and Inspector
mcp-use dev
# Start development with authenticated public tunnel for live ChatGPT testing
mcp-use dev --tunnel
# Typecheck server and views
mcp-use typecheck
# Build for production
mcp-use build
# Start production server
mcp-use start
# Connect live client session straight from terminal
mcp-use client connect dev http://localhost:3000/mcp
# List tools and call with arguments
mcp-use client dev tools list
mcp-use client dev tools call get-product sku=PROD-100
# Capture automated screenshot of view preview
mcp-use screenshot --server dev --tool get-product sku=PROD-100
# Deploy to mcp-use cloud (requires GitHub repo)
mcp-use deploy