无用——跨职业学习应用
一个基于MCP(模型上下文协议)和应用程序SDK构建的ChatGPT应用,具备自定义UI组件。通过交互式、设计精美的学习卡片,了解不同领域专业人士如何执行特定任务。
示例
🎯 概述
没用的 它教你来自各行各业的“无用”但引人入胜的技能——从更换喷气发动机的机油到像建筑师一样测量建筑工地。每张学习卡片都以自定义的红色/橙色品牌UI组件展示,该组件可直接在ChatGPT中渲染。
主要特点
✅ 自定义UI组件 - 红色/橙色主题的交互式卡片,支持深色模式 ✅ MCP协议 - 符合标准的服务器,配备资源和工具 ✅ 应用程序SDK集成 - 使用合适的组件模板 text/html+skybridge ✅ 翻译成中文是:✓(对号,表示正确或确认) 结构化内容 - 数据从服务器到组件的清洁数据流通过 window.openai ✅ 5张学习卡片 - 飞机工程、烹饪艺术、建筑、电气以及急诊医学
📁 项目结构
useless/
├── src/
│ └── index.js # MCP server with component resource handling
├── web/
│ ├── src/
│ │ └── component.js # Vanilla JS component (4KB)
│ └── dist/
│ └── component.js # Built component bundle
├── data/
│ └── learning-cards.json # Learning content (5 cards)
├── Dockerfile # Production container
├── fly.toml # Fly.io deployment config
├── package.json # Server dependencies
└── README.md # This file🚀 快速入门
1. 安装
# Clone and install
cd /path/to/useless
npm install
# Build component (optional - already built)
cp web/src/component.js web/dist/component.js2. 本地测试
启动MCP服务器:
npm start服务器运行在 http://localhost:3000 具有以下终点:
/- 健康检查/mcp- MCP协议端点(POST)
3. 部署到 Fly.io
# Install Fly CLI
curl -L https://fly.io/install.sh | sh
# Login
flyctl auth login
# Deploy (uses existing fly.toml)
flyctl deploy --app useless
# Check status
flyctl status --app useless您的服务器将在以下时间上线: https://useless.fly.dev/mcp
4. 连接到ChatGPT
- 开放 ChatGPT
- 首选 设置 → 应用与连接 → 高级选项
- 启用 开发者模式
- 点击 添加应用:
- 名字没用的 - 描述跨职业定制用户界面学习 - MCP服务器URL: https://useless.fly.dev/mcp - 认证无
- 刷新应用 添加后(对组件支持至关重要!)
- 测试:“告诉我一些没用的事”
🎨 工作原理
建筑学
ChatGPT → MCP Protocol → Express Server → Component Resource
↓ ↓ ↓
tools/list structuredContent HTML Template
↓ ↓ ↓
Tool Metadata window.openai Vanilla JS UI1. MCP 服务器(src/index.js)
服务器实现了四种MCP方法:
initialize
返回服务器功能和元数据:
{
protocolVersion: '2024-11-05',
capabilities: {
tools: {}, // Supports tool calls
resources: {} // Supports component resources
}
}resources/list
广告中提供的可用UI组件:
{
resources: [{
uri: 'ui://widget/learning-card.html',
name: 'Learning Card Widget',
mimeType: 'text/html+skybridge' // Apps SDK component type
}]
}resources/read
为组件提供HTML模板:
{
contents: [{
uri: 'ui://widget/learning-card.html',
mimeType: 'text/html+skybridge',
text: `
${componentCode}
`,
_meta: {
'openai/widgetPrefersBorder': true,
'openai/widgetDescription': '...'
}
}]
}tools/list
定义可用工具及其组件元数据:
{
name: 'get_learning_card',
_meta: {
'openai/outputTemplate': 'ui://widget/learning-card.html', // Links to component
'openai/toolInvocation/invoking': 'Loading learning card...',
'openai/toolInvocation/invoked': 'Learning card displayed'
}
}tools/call
为该组件返回结构化数据:
{
content: [{ type: 'text', text: '...' }], // Fallback text
structuredContent: { // → window.openai.toolOutput
card: {
id, title, description, occupation,
category, steps, funFact, keyTakeaway
}
}
}2. 组件(web/src/component.js)
一个4KB的原生JavaScript组件,其功能包括:
- 读取数据 来自
window.openai.toolOutput - 监听主题变化 通过
openai:set_globals活动/事件 - 渲染(结果/图像) 带有红色/橙色品牌标识的样式化HTML
- 支持深色模式 通过阅读
window.openai.theme
// Get data injected by ChatGPT
const card = window.openai?.toolOutput?.card;
// Get current theme
const theme = window.openai?.theme || 'light';
// Listen for changes
window.addEventListener('openai:set_globals', () => {
const card = getData();
const theme = getTheme();
document.getElementById('root').innerHTML = renderCard(card, theme);
});3. 数据流
Tool Call → structuredContent → ChatGPT → window.openai.toolOutput → Component- ChatGPT通话
get_learning_card - 服务器返回
structuredContent: { card: {...} } - ChatGPT 通过(某种方式)获取组件
resources/read - ChatGPT注入数据:
window.openai.toolOutput = structuredContent - 组件以自定义样式渲染卡片
🛠️ 可用工具
get_learning_card
获取一张跨职业学习卡。
参数 (全部可选):
id- 特定卡片ID(例如,“飞机换油”)occupation- 按职业筛选(例如,“厨师”)category- 按类别筛选(例如,“烹饪艺术”)
如果未提供参数,则返回一张随机卡片。
list_all_cards
列出所有可用的学习卡片,包括标题、职业和类别。
search_cards
按关键词搜索卡片。
参数:
query- 搜索词(必填)
📊 学习卡片
当前收藏包括:
| ID | 职业 | 类别 | 主题 | |
|---|---|---|---|---|
| 分隔线 | 标题 | 标题 | 标题 | aircraft-oil-change |
| 飞机工程师 | 维护与安全 | 更换喷气发动机机油 | chef-knife-skills | |
| 专业厨师 | 烹饪艺术 | 精通刀工技巧 | architect-site-measurement | |
| 建筑师 | 设计与规划 | 测量建筑工地 | electrician-circuit-troubleshooting | |
| 电工 | 技术故障排除 | 电路问题诊断 | paramedic-patient-assessment |
| 急救员 | 紧急响应 | 快速患者评估 |
添加新卡片 data/learning-cards.json编辑
{
"id": "unique-identifier",
"title": "How Profession Does Task",
"description": "Brief overview of the process",
"category": "Category Name",
"occupation": "Professional Title",
"image": "https://unsplash.com/photo-id",
"steps": [
"Step 1 description",
"Step 2 description",
"..."
],
"funFact": "Interesting trivia about this process",
"keyTakeaway": "Main lesson or insight"
}:
🎨 组件样式
- 该UI组件的特点包括: 红色/橙色渐变背景
- - 品牌颜色(#FF6B35,#FF8C42,#FFA559) 暗黑模式支持
- - 适应ChatGPT的主题 响应式设计
- - 适用于桌面和移动设备 专业排版
- - 具有细致层级结构的系统字体 高亮框
- 有趣的事实和要点总结浅色模式
- :
#FF6B35 → #FF8C42 → #FFA559 - 梯度:
#333333
文本:深色模式
- :
#D84315 → #E64A19 → #FF6F00 - 梯度:
#FFFFFF
文本:
🔧 开发
# Run with auto-reload
npm run dev本地开发
- 组件开发
web/src/component.js - 编辑
cp web/src/component.js web/dist/component.js - 复制到 dist:
flyctl deploy --app useless - 重新部署: 在ChatGPT中刷新应用
(设置 → 应用 → 刷新)
测试MCP服务器
# Health check
curl https://useless.fly.dev/
# MCP initialize
curl -X POST https://useless.fly.dev/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# List resources
curl -X POST https://useless.fly.dev/mcp \
-H "Content-Type: application/json" \
-d '{"jsonrpc":"2.0","id":2,"method":"resources/list","params":{}}'直接测试终端点:
# Real-time logs
flyctl logs --app useless
# Recent logs
flyctl logs --no-tail --app useless查看日志
🔑 关键实施细节 text/html+skybridge为什么
? 这是 官方应用程序SDK的MIME类型 text/html 用于组件模板。常规 text/html+skybridge 不会有效果——ChatGPT 特别寻找的是
以识别可渲染的组件。 structuredContent为什么
? structuredContent 应用程序SDK文档中明确规定 window.openai.toolOutput 在工具响应中,自动映射到
在组件 iframe 中。这在服务器和用户界面之间创建了一个清晰的数据契约。
- 为什么选择原生JavaScript而不是React?包大小
- 4KB 对比 1MB(小250倍)无构建步骤
- 直接从源目录(src)复制到目标目录(dist)无依赖项
- 纯JavaScript快速加载
即时iframe初始化
组件刷新需求关键的;至关重要的 _meta 在部署影响组件元数据的服务器更改后(如
(字段),您必须在ChatGPT的开发者设置中刷新应用。ChatGPT会积极缓存工具和资源的元数据。
API 参考
MCP方法 | 方法 | 描述 | 返回值 | |--------|-------------|---------| initialize | { capabilities, serverInfo } | 获取服务器功能 | | resources/list | { resources: [...] } | 列出可用组件 | | resources/read | { contents: [...] } | 获取组件HTML | | tools/list | { tools: [...] } | 列出可用工具 | | tools/call | { content, structuredContent } | 执行工具 |
|
组件API(window.openai) | 属性 | 类型 | 描述 | |----------|------|-------------| toolOutput |(分隔线)|------|-------------| structuredContent | | 对象 | 来自……的数据 theme | 'light' | 'dark' | 字符串 | 或者 displayMode | 'inline'| 'fullscreen'| 字符串 | 'pip' , ,或者 locale | 'en-US'| | 字符串 | 用户的区域设置(例如。, callTool() )| | sendFollowupMessage() | 功能 | 从组件中调用MCP工具 |
|
| 功能 | 在对话中插入消息 |
- 🐛 故障排除 组件未渲染
- 刷新应用
resources/read在ChatGPT开发者设置中 - 检查日志以查看
ls -lh web/dist/component.js - 请求:
验证组件包大小:
- 直接使用curl测试资源端点
- ChatGPT进行总结而非展示组件这意味着资源没有被获取
- 解决方案
text/html+skybridge
在ChatGPT中删除并重新添加该应用
- 确保MIME类型准确无误
- 424错误
structuredContent通常意味着组件响应格式错误 - 检查一下
是有效的JSON
应用程序SDK示例
Fly.io 文档
📄 许可证
麻省理工学院(MIT)
- 🤝 贡献
- 欢迎投稿!请随意:
- 添加更多学习卡片
- 改进组件样式
增强服务器功能
修复漏洞
______________________________________________________________________
Express.js
