zkangHUST/mcp_calc
If you are the rightful owner of mcp_calc and would like to certify it and/or have it hosted online, please leave a comment on the right or send an email to dayong@mcphub.com.
This project is a demonstration of a ChatGPT Widget using the OpenAI Apps SDK and Model Context Protocol (MCP) to create a custom UI component for displaying calculation results.
ChatGPT Widget Demo - 计算器应用
📋 项目概述
这是一个基于 OpenAI Apps SDK 和 Model Context Protocol (MCP) 开发的 ChatGPT Widget 示例项目。该项目演示了如何在 ChatGPT 中创建自定义 UI 组件,将工具的计算结果以美观的界面形式展示给用户。
核心特性
- ✅ MCP 服务器:使用 FastMCP 框架构建 Python 后端服务
- ✅ 自定义 Widget:在 ChatGPT 对话中嵌入交互式 UI 组件
- ✅ 结构化数据:通过
structuredContent传递计算结果 - ✅ 现代化 UI:渐变背景、卡片设计、动画效果
- ✅ 响应式设计:支持深色模式,适配不同设备
🏗️ 技术架构
技术栈
- 后端:Python 3.x + FastMCP
- 前端:HTML + CSS + Vanilla JavaScript
- 协议:Model Context Protocol (MCP)
- 框架:OpenAI Apps SDK
架构图
┌─────────────────┐
│ ChatGPT UI │
│ (用户界面) │
└────────┬────────┘
│
│ HTTP/SSE
│
┌────────▼────────┐
│ MCP Server │
│ (main.py) │
│ │
│ - 工具定义 │
│ - 资源管理 │
│ - 计算逻辑 │
└────────┬────────┘
│
│ 返回 HTML + structuredContent
│
┌────────▼────────┐
│ Widget HTML │
│ (calculatorv4) │
│ │
│ - 读取数据 │
│ - 渲染界面 │
│ - 事件监听 │
└─────────────────┘
🔑 核心概念
1. MCP Server
MCP Server 是提供工具和资源的后端服务。在本项目中:
- 工具 (Tool):
calculator- 执行数学计算 - 资源 (Resource):
calculatorv4.html- Widget 的 HTML 模板 - 资源模板 (Resource Template):定义 Widget 的 URI 模式
2. Widget
Widget 是在 ChatGPT 对话中嵌入的自定义 UI 组件:
- 运行在 iframe 中,与 ChatGPT 隔离
- 通过
window.openaiAPI 与宿主通信 - 接收
structuredContent数据进行渲染
3. 数据流
用户输入 → ChatGPT → MCP Server → 计算 → 返回结果
↓
structuredContent
↓
Widget HTML 渲染
📁 项目结构
mcp-test3/
├── server/
│ └── main.py # MCP 服务器主文件
├── assets/
│ ├── calculator.html # Widget v1 (基础版本)
│ ├── calculatorv2.html # Widget v2 (改进版本)
│ ├── calculatorv3.html # Widget v3 (事件监听版本)
│ └── calculatorv4.html # Widget v4 (当前版本,带美观样式)
└── README.md # 本文档
🚀 快速开始
1. 安装依赖
pip install fastmcp pydantic
2. 启动服务器
cd server
python main.py
服务器将在 http://0.0.0.0:8000 启动。
3. 配置 ChatGPT
在 ChatGPT 中配置 MCP 服务器连接(具体配置方式取决于你的 ChatGPT 客户端)。
4. 测试计算器
在 ChatGPT 中发送消息:
请帮我计算 123 + 456
ChatGPT 会调用 calculator 工具,并在对话中显示美观的计算结果 Widget。
💻 代码详解
服务器端 (main.py)
1. Widget 定义
calculator_widget = CalculatorWidget(
identifier="calculator",
title="Simple Calculator",
template_uri="ui://widget/calculatorv4.html", # Widget URI
invoking="Calculating...", # 调用中提示
invoked="Calculation finished", # 完成提示
html=_load_widget_html("calculatorv4"), # HTML 内容
response_text="Calculator result rendered!",
)
2. 工具定义
@mcp._mcp_server.list_tools()
async def _list_tools() -> List[types.Tool]:
return [
types.Tool(
name="calculator",
title="Simple Calculator",
description="A simple calculator...",
inputSchema=TOOL_INPUT_SCHEMA,
_meta=_tool_meta(widget), # 关键:告诉客户端这是 Widget
annotations={
"destructiveHint": False,
"openWorldHint": False,
"readOnlyHint": True,
},
)
]
关键 Meta 字段:
openai/outputTemplate: Widget 的模板 URIopenai/widgetAccessible: 允许 Widget 访问openai/resultCanProduceWidget: 结果可以生成 Widget
3. 资源暴露
@mcp._mcp_server.list_resources()
async def _list_resources() -> List[types.Resource]:
return [
types.Resource(
name=widget.title,
uri=widget.template_uri,
mimeType="text/html+skybridge", # 关键:Skybridge MIME 类型
_meta=_tool_meta(widget),
)
]
4. 工具调用处理
async def _call_tool_request(req: types.CallToolRequest):
# ... 计算逻辑 ...
# 返回结构化数据
structured = {
"operand1": op1,
"operand2": op2,
"operator": op,
"result": result,
}
return types.ServerResult(
types.CallToolResult(
content=[types.TextContent(...)],
structuredContent=structured, # 关键:结构化数据
_meta=meta,
)
)
客户端 (calculatorv4.html)
1. 数据获取
function renderFromGlobals() {
const globals = window.openai;
const toolOutput = globals.toolOutput;
// 获取结构化数据
let data = toolOutput;
if (toolOutput.structuredContent) {
data = toolOutput.structuredContent;
}
// 渲染界面
if (data && data.result !== undefined) {
// 显示计算结果
}
}
2. 事件监听
根据 OpenAI Apps SDK 文档,应该监听 openai:set_globals 事件:
window.addEventListener("openai:set_globals", (event) => {
renderFromGlobals();
});
3. window.openai API
Widget 可以通过 window.openai 访问:
window.openai.toolOutput- 工具输出数据window.openai.toolInput- 工具输入数据window.openai.locale- 本地化设置window.openai.theme- 主题(light/dark)window.openai.callTool()- 调用其他工具window.openai.sendFollowUpMessage()- 发送后续消息
🎨 UI 设计亮点
calculatorv4.html 特性
- 渐变背景:紫色渐变,现代感强
- 卡片设计:白色卡片,圆角阴影
- 分层显示:表达式和结果分开,结果更突出
- 动画效果:
- 滑入动画
- 加载旋转图标
- 悬停效果
- 深色模式:自动适配系统偏好
- 响应式布局:适配不同屏幕尺寸
📊 Demo 演示步骤
1. 启动服务器
cd server
python main.py
2. 在 ChatGPT 中测试
场景 1:简单加法
用户:帮我算一下 25 + 17
场景 2:乘法运算
用户:计算 123.5 * 4.2
场景 3:除法运算
用户:100 除以 3 等于多少?
场景 4:错误处理
用户:10 除以 0 是多少?
3. 观察效果
- ✅ ChatGPT 调用
calculator工具 - ✅ 显示 "Calculating..." 提示
- ✅ Widget 加载并显示计算结果
- ✅ 美观的 UI 界面展示
🔍 关键实现细节
1. MIME 类型
Widget 必须使用 text/html+skybridge MIME 类型,这是 OpenAI 的 Skybridge 运行时标识。
2. Meta 字段
Meta 字段告诉 ChatGPT 客户端如何处理工具响应:
{
"openai/outputTemplate": "ui://widget/calculatorv4.html",
"openai/widgetAccessible": True,
"openai/resultCanProduceWidget": True,
}
3. structuredContent
structuredContent 是传递给 Widget 的数据,必须是 JSON 可序列化的对象。
4. 事件系统
openai:set_globals- 当全局变量更新时触发- Widget 应该监听此事件以响应数据变化
5. 异步初始化
由于 window.openai 可能异步加载,Widget 需要:
- 初始检查
- 延迟检查(100ms, 500ms, 1000ms)
- 轮询机制(作为后备方案)
🐛 常见问题
Q: Widget 显示 "Waiting for calculation…"
A: 检查以下几点:
window.openai是否已初始化toolOutput是否存在- 事件监听器是否正确设置
- 打开浏览器控制台查看调试信息
Q: 数据格式不匹配
A: 确保服务器返回的 structuredContent 结构与 Widget 期望的一致:
structured = {
"operand1": float,
"operand2": float,
"operator": str,
"result": float,
}
Q: Widget 不显示
A: 检查:
- Meta 字段是否正确设置
- MIME 类型是否为
text/html+skybridge - Resource 是否正确注册
- template_uri 是否匹配
📚 参考资料
🎯 下一步计划
- 添加更多计算功能(科学计算、单位转换等)
- 支持历史记录显示
- 添加交互式按钮(重新计算、复制结果等)
- 支持多语言界面
- 添加更多 Widget 示例
👥 团队协作
开发流程
- 后端开发:修改
server/main.py,添加新工具或功能 - 前端开发:修改
assets/calculatorv4.html,优化 UI - 测试:在 ChatGPT 中测试完整流程
- 版本管理:创建新的 HTML 版本(如 calculatorv5.html)
代码规范
- Python:遵循 PEP 8
- JavaScript:使用现代 ES6+ 语法
- HTML/CSS:使用语义化标签,响应式设计
项目维护者:[你的名字]
最后更新:2024年