ChatGPT 兼容小部件服务器

一个集成了 OpenAI Apps SDK 的服务器模板,用于构建与 ChatGPT 兼容的小部件,提供自动注册、主题支持和类型安全等功能。

  • by teilorbarcelos
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 teilorbarcelos/tsb-mcp

使用说明

MCP应用服务器

MCP服务器模板与OpenAI Apps SDK集成,用于兼容ChatGPT的小部件。

特性

  • 🤖 OpenAI应用软件开发工具包:与ChatGPT小部件完全兼容
  • 🎨 官方UI组件:集成 OpenAI Apps SDK UI组件 用于一致、可访问的小部件
  • 🛒 电子商务小工具:完整的电子商务示例,包括旋转木马、搜索、地图和订单确认
  • 🔄 自动配准:小部件自动注册 resources/ 文件夹
  • 📦 Props架构:小部件道具的Zod模式验证
  • 🌙 主题支持:通过暗/亮主题检测 useWidget 钩子
  • 🛠️ TypeScript:完全类型安全
  • 🔧 小部件功能:全力支持 callTool, sendFollowUpMessage,以及持久状态

新增功能:Apps SDK集成

此模板演示了如何使用OpenAI的Apps SDK构建与ChatGPT兼容的小部件:

import { useWidget } from 'mcp-use/react';

const MyWidget: React.FC = () => {
  const { props, theme } = useWidget<MyProps>();

  // props contains validated inputs from OpenAI
  // theme is 'dark' or 'light' based on ChatGPT setting
}

入门指南

发展

# Install dependencies
npm install

# Start development server
npm run dev

这将开始:

  • 端口3000上的MCP服务器
  • 小工具服务于 /mcp-use/widgets/*
  • 检查器UI位于 /inspector

生产

# Build the server and widgets
npm run build

# Run the built server
npm start

项目结构

mcp-apps/
├── resources/                          # React widget components
│   ├── display-weather.tsx              # Weather widget example
│   ├── ecommerce-carousel.tsx           # Ecommerce product carousel
│   ├── product-search-result.tsx        # Product search with filters
│   ├── stores-locations-map.tsx         # Store locations map
│   └── order-confirmation.tsx           # Order confirmation widget
├── index.ts                             # Server entry point (includes brand info tool)
├── package.json
├── tsconfig.json
└── README.md

自动注册的工作原理

所有React组件 resources/ 导出时,文件夹会自动注册为MCP工具和资源 widgetMetadata:

import { z } from 'zod';
import type { WidgetMetadata } from 'mcp-use/react';

const propSchema = z.object({
  city: z.string().describe('The city name'),
  temperature: z.number().describe('Temperature in Celsius'),
});

export const widgetMetadata: WidgetMetadata = {
  description: 'My widget description',
  props: propSchema,
};

const MyWidget: React.FC = () => {
  const { props } = useWidget<z.infer<typeof propSchema>>();
  // Your widget implementation
};

export default MyWidget;

这会自动创建:

  • 工具: display-weather -通过OpenAI接受参数
  • 资源: ui://widget/display-weather -静态访问

使用Apps SDK构建小部件

使用 useWidget 钩子

import { useWidget } from 'mcp-use/react';

interface MyProps {
  title: string;
  count: number;
}

const MyWidget: React.FC = () => {
  const { props, theme, isPending } = useWidget<MyProps>();

  // IMPORTANT: Widgets render before tool execution completes
  // Always check isPending to handle the loading state
  if (isPending) {
    return <div>Loading...</div>;
  }

  // Now props are safely available
  // props are validated and typed based on your schema
  // theme is automatically set by ChatGPT

  return (
    <div className={theme === 'dark' ? 'dark-theme' : 'light-theme'}>
      <h1>{props.title}</h1>
      <p>Count: {props.count}</p>
    </div>
  );
};

备注:小部件在工具执行完成之前呈现。在第一次渲染时, props 将为空 {} 和 isPending 将是 true。始终检查 isPending 在使用道具之前。请参阅 小部件生命周期文档 了解更多详情。

定义小部件元数据

使用Zod模式定义小部件输入:

import { z } from 'zod';
import type { WidgetMetadata } from 'mcp-use/react';

const propSchema = z.object({
  name: z.string().describe('Person name'),
  age: z.number().min(0).max(120).describe('Age in years'),
  email: z.string().email().describe('Email address'),
});

export const widgetMetadata: WidgetMetadata = {
  description: 'Display user information',
  props: propSchema,
};

主题支持

自动适应ChatGPT的主题:

const { theme } = useWidget();

const bgColor = theme === 'dark' ? 'bg-gray-900' : 'bg-white';
const textColor = theme === 'dark' ? 'text-gray-100' : 'text-gray-800';

官方UI组件

此模板使用 OpenAI Apps SDK UI组件库 用于构建一致、可访问的小部件。图书馆提供:

  • 按钮:主要、次要和轮廓按钮变体
  • 卡:内容部分的容器组件
  • 旋转木马:带有过渡的图像和内容轮播
  • 输入:表单输入字段
  • 图标:一致的图像
  • 过渡:平滑的动画和过渡

导入以下组件:

import {
  Button,
  Card,
  Carousel,
  CarouselItem,
  Transition,
  Icon,
  Input,
} from '@openai/apps-sdk-ui';

电子商务小工具

此模板包括一个完整的电子商务示例,其中包含四个小部件:

1.电子商务旋转木马(ecommerce-carousel.tsx)

产品转盘小部件具有以下特点:

  • 标题和说明
  • 带有占位符图像的产品项目旋转木马
  • 每个商品的“信息”按钮和“添加到购物车”按钮
  • 使用官方旋转木马、卡片、按钮、图标和过渡组件
  • 与集成 callTool 用于推车操作
  • 持续状态管理

2.产品搜索结果(product-search-result.tsx)

搜索结果小部件,包括:

  • 具有实时过滤功能的搜索输入
  • 价格范围过滤器和库存状态过滤器
  • 产品卡片网格布局
  • 用途 callTool 执行搜索
  • 用途 sendFollowUpMessage 更新对话
  • 持久过滤器状态

3.店铺位置图(stores-locations-map.tsx)

商店定位器小部件具有以下功能:

  • 交互式地图显示(占位符)
  • 店铺位置列表及详细信息
  • 距离计算
  • 获取方向功能
  • 点击商店详细信息
  • 用途 callTool 获取路线和店铺信息

4.订单确认(order-confirmation.tsx)

订单确认小部件,包括:

  • 订单摘要和项目列表
  • 运输信息
  • 订单状态跟踪
  • 跟踪订单并查看收货操作
  • 用途 callTool 用于订单跟踪

品牌信息工具

该模板包括 get-brand-info 返回品牌信息的工具(普通MCP工具,不是小部件):

// Call the tool
await client.callTool('get-brand-info', {});

// Returns brand details including:
// - Company name, tagline, description
// - Mission and values
// - Contact information
// - Social media links

示例:天气小工具

包括 display-weather.tsx 小部件演示:

  1. 架构定义:用于验证的Zod模式
  2. 元数据导出:小部件注册信息
  3. 主题检测:暗/亮模式支持
  4. 类型安全:完全支持TypeScript
// Get props from OpenAI Apps SDK
const { props, theme } = useWidget<WeatherProps>();

// props.city, props.weather, props.temperature are validated

在ChatGPT中使用小部件

通过工具调用

await client.callTool('display-weather', {
  city: 'San Francisco',
  weather: 'sunny',
  temperature: 22
});

通过资源访问

await client.readResource('ui://widget/display-weather');

定制指南

添加新小部件

  1. 在中创建一个React组件 resources/my-widget.tsx:
import React from 'react';
import { z } from 'zod';
import { useWidget, type WidgetMetadata } from 'mcp-use/react';

const propSchema = z.object({
  message: z.string().describe('Message to display'),
});

export const widgetMetadata: WidgetMetadata = {
  description: 'Display a message',
  props: propSchema,
};

type Props = z.infer<typeof propSchema>;

const MyWidget: React.FC = () => {
  const { props, theme } = useWidget<Props>();

  return (
    <div>
      <h1>{props.message}</h1>
    </div>
  );
};

export default MyWidget;
  1. 该小部件已自动注册!

添加传统MCP工具

您可以将Apps SDK小部件与常规MCP工具混合使用:

import { text } from 'mcp-use/server';

server.tool({
  name: 'get-data',
  description: 'Fetch data from API',
  cb: async () => {
    return text('Data');
  },
});

测试您的小部件

通过检查器UI

  1. 启动服务器: npm run dev
  2. 打开: http://localhost:3000/inspector
  3. 交互式测试小部件

直接浏览器访问

访问: http://localhost:3000/mcp-use/widgets/display-weather

通过MCP客户端

import { createMCPClient } from 'mcp-use/client';

const client = createMCPClient({
  serverUrl: 'http://localhost:3000/mcp',
});

await client.connect();

// Call widget as tool
const result = await client.callTool('display-weather', {
  city: 'London',
  weather: 'rain',
  temperature: 15
});

应用程序SDK与其他小部件类型

功能 应用程序SDK 外部URL 远程DOM
ChatGPT兼容 ✅ 是 ❌ 否 ❌ 没有
主题检测 ✅ 自动 ❌ 手册 ❌ 手册
道具验证 ✅ Zod架构 ❌ 手册 ❌ 手册
React支持 ✅ 已满 ✅ 已满 ❌ 有限
OpenAI元数据 ✅ 是 ❌ 否 ❌ 没有

Apps SDK的好处

✅ ChatGPT原生 -在ChatGPT中无缝工作 ✅ 主题感知 -自动暗/亮模式 ✅ 类型安全 -带有Zod验证的完整TypeScript ✅ 简单API -所有道具都有一个钩子 ✅ 自动注册 -导出元数据并完成

故障排除

小部件未加载

  • 确保小部件具有 widgetMetadata 出口
  • 检查Zod架构是否有效
  • 验证小部件是否存在于 dist/resources/mcp-use/widgets/

道具未通过

  • 确保架构包括所有道具
  • 检查 .describe() 对于每个道具
  • 验证 useWidget 钩子被称为

主题未应用

  • 主题仅在ChatGPT中可用
  • 使用 theme 从 useWidget() 钩子
  • 在实际ChatGPT接口中测试

从其他模板迁移

从移动 starter 到 mcp-apps:

// Before: Manual props handling
const params = new URLSearchParams(window.location.search);
const city = params.get('city');

// After: Apps SDK hook
const { props } = useWidget();
const city = props.city;

使用小部件功能

此模板中的小部件展示了Apps SDK的全部功能:

呼叫工具(callTool)

小部件可以调用其他MCP工具:

const { callTool } = useWidget();

const handleAction = async () => {
  const result = await callTool('add-to-cart', {
    productId: '123',
    productName: 'Product Name',
    price: 29.99
  });
};

发送后续消息(sendFollowUpMessage)

小部件可以向ChatGPT对话发送消息:

const { sendFollowUpMessage } = useWidget();

await sendFollowUpMessage('Product added to cart successfully!');

持续状态(setState)

小部件可以在交互中保持状态:

const { setState, state } = useWidget();

// Save state
await setState({ cart: [...cart, newItem] });

// Read state
const savedCart = state?.cart || [];

组件库注释

此模板使用 OpenAI Apps SDK UI组件库.确切的组件API可能因库版本而异。如果遇到导入错误,请检查 官方文档 获取正确的组件名称和道具。

如果官方库不可用,您可以用自定义React组件或其他UI库替换导入,同时保持相同的小部件结构。

了解更多

快乐建筑! 🚀