McShake
Health Gecti
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 10 GitHub stars
Code Uyari
- process.env — Environment variable access in scripts/build-demo-snapshot.js
- fs module — File system access in scripts/build-demo-snapshot.js
- process.env — Environment variable access in server.js
- fs module — File system access in server.js
- network request — Outbound network request in server.js
- fs module — File system access in src/demo-mcp.js
- network request — Outbound network request in src/mcp-client.js
Permissions Gecti
- Permissions — No dangerous permissions requested
Bu listing icin henuz AI raporu yok.
麦当劳官方 MCP 驱动的像素风老虎机:营养账本 + 凑单优化 + 1+1 随心配
麦摇摇 McShake · 麦当劳营养账本与凑单优化器
2026 麦当劳程序员创意开发大赛参赛作品 · 全程使用 WorkBuddy 开发
摇一摇老虎机,摇出你这一餐的营养账本和最省买法。
线上试玩:https://mcshake.app.workbuddy.host/?sharecode=AMBVYTIUXU
打开即可摇,不需要任何 Token。页面数据来自官方接口的真实快照(含抓取时点与门店名),
想摇自己常去的门店,在页面顶部填自己的 MCP Token 即可切到实时接口。详见线上试玩与演示模式。
三个滚轮的符号全部取自你选定门店的真实在售菜单,停下后立刻给出营养汇总(对标中国食品标签 NRV 参考值)与 calculate-price 真实试算金额——每件单价、被分摊到的凑单优惠、相当于打几折,都列清楚。
如果摇出的两件单品正好能拼成 1+1 随心配(蓝区一件 + 粉区一件),页面会再算一次套餐价,并把没进套餐的那件一起计入实付:单点合计 ¥50.50 → 本单实付 ¥37.40(套餐 ¥13.90 + 板烧鸡腿堡 ¥23.50),省 ¥13.10。三个数字(套餐价、本单实付、单点合计)都写在画面上,不会出现「上面 ¥41.50、下面 ¥13.90」这种对不上的情况。
老虎机滚轮(符号 = MCP 真实菜单 + 官方餐品图片)
│
├──▶ 营养匹配 ──▶ 热量 / 蛋白质 / 脂肪 / 碳水 / 钠 + NRV 占比 + 提示
│
└──▶ 组合试算 ──▶ 每件单价 + 到手折扣 + 整单实付
所有数据均来自麦当劳中国官方 MCP 实时接口,零第三方依赖(Node 22 标准库实现),不缓存伪造、不虚构数字。
演示
点 SPIN:三个滚轮先后停下,结果面板给出营养账本与真实计价。这一局摇出的 板烧鸡腿堡 + 麦乐鸡5块(蓝区)+ 小杯玉米杯(粉区)里有两件能拼 1+1,于是再走一次套餐价:单点合计 ¥50.50 → 本单实付 ¥37.40(套餐 ¥13.90 + 板烧鸡腿堡 ¥23.50),省 ¥13.10,「一键下单」随之解锁。

动图用演示模式录制(无 Token),所以顶部带黄色「演示数据」条;菜单、营养库与金额仍是官方接口的真实快照响应。
换门店:城市与地标分开填(杭州 + 三里亭),命中后整副滚轮符号立刻换成本店在售菜单。

动图为真实交互录屏(Playwright + CDP screencast 抓帧),未做任何拼接或美化;金额与营养值都是那一次真实接口调用的返回。
线上试玩与演示模式
https://mcshake.app.workbuddy.host/?sharecode=AMBVYTIUXU
线上试玩是公网的,任何人都能打开,所以不能在服务端内置任何人的 MCP Token——那既会泄漏凭据,也会被人刷爆配额,还违反 MCP「个人非商业用途」的条款。这里的做法是把一场真实会话抓成快照,用快照重放:
| 快照里有什么 | 怎么来的 |
|---|---|
| 门店菜单(116 项,含名称与官方餐品图) | query-meals + 逐件 query-meal-detail |
| 营养库 160 条 | list-nutrition-foods 原始响应 |
| 逐件真实单价 32 项(滚轮符号 + 1+1 子件) | 逐件 calculate-price |
| 全部 286 种三连组合的真实计价结果 | 11 个符号的三元组合逐个 calculate-price,含真实优惠与逐件分摊 |
| 两套 1+1 随心配的分区结构与套餐基础价 | query-meal-detail 的 rounds + 套餐价试算,并用另外两组子件验算 |
src/demo-mcp.js 把这个快照包装成一个同签名的 MCP 客户端(callTool / getToolSchema / callToolWithFallback),服务端业务逻辑一行都没改:菜单加载、营养匹配、折扣分摊、1+1 拼单照旧跑,只是数据源从网络换成了快照。
三条不能破的底线:
- 界面必须标明非实时。页面顶部有黄色「演示数据」条,写明门店、抓取时点、以及「286 种组合的计价结果都是当次的真实响应」。
- 快照里没有的组合按真实单价合成,不编折扣;单价也没有的餐品直接返回失败,而不是给个 0。
- 未覆盖的工具直接报错(如
draw-lottery),不返回假数据。
启动行为:
node server.js # 无 Token → 自动进演示模式,读 demo/demo-snapshot.json
MCD_MCP_TOKEN=xxx node server.js # 有 Token → 实时接口,演示模式关闭
MCSHAKE_DEMO=0 node server.js # 强制关掉演示模式
页面顶部的「连接 MCP」随时可以填自己的 Token,连上即切到实时接口(快照带来的菜单、营养库、1+1 索引会全部作废并重新拉取)。
快照过期了想更新,重新抓一份即可(约 20 秒、450 次调用):
MCD_MCP_TOKEN=xxx node scripts/build-demo-snapshot.js
目录
项目介绍
点麦当劳前最难回答的两个问题是:这一餐到底吃了多少、怎么买最省。官方 MCP 已经把菜单、营养库、真实计价都开放出来了,但它们是三套割裂的接口,普通用户根本用不上。
麦摇摇把它们串成一次交互:
- 摇——三个滚轮的符号是这家店此刻真的在卖的东西(含官方餐品图),不是写死的示例数据;
- 看——摇出什么就算什么:营养逐项列出并折算 NRV 单餐占比,价格走
calculate-price真实试算; - 懂——整单优惠被服务端自动算出后,按原价比例分摊回每一件,告诉你「这顿相当于 8.2 折,省了 ¥13.00」。
设计上贯穿一条原则:宁可留空,也不给看起来正确的错数字。套餐不猜营养(营养库没有套餐记录,硬匹配会严重低估),售罄商品不编价格(原样透传官方 code 与文案),所有数字都能追溯到一次真实接口调用。
目标用户
| 人群 | 场景 | 它解决什么 |
|---|---|---|
| 控卡 / 健身人群 | 想吃麦当劳,又怕一餐超掉全天预算 | 三个餐品的热量、蛋白、脂肪、碳水、钠一眼看清,附 NRV 占比 |
| 学生党 / 价格敏感用户 | 想凑到组合优惠,但不知道还差几块、值不值 | 真实试算整单,列出每件分摊后的单价与折扣力度 |
| 选择困难症 | 站在点餐机前刷了五分钟菜单 | 摇一摇直接出结果,还能顺带看这顿"账" |
| 减脂期但嘴馋的人 | 明知道热量高,需要一点情绪价值 | 超标的营养项配无厘头彩蛋(「只要吃得爽,热量不存在」),不制造焦虑 |
| MCP / Agent 开发者 | 想抄一份官方 MCP 的实测接入笔记 | MCP_INTEGRATION.md 记录了大量未公开的参数与响应坑 |
安装方法
环境要求:Node.js 22 或更高。无需 npm install——项目零第三方依赖。
git clone https://github.com/gundamli/McShake.git
cd McShake
node server.js
# 打开 http://127.0.0.1:8787
可选:用环境变量直接带上 Token 启动,省去页面里手动填写。
MCD_MCP_TOKEN=你的Token node server.js
MCP Token 在 open.mcd.cn/mcp 申请(登录 → 控制台 → 激活 → 复制)。Token 只驻留在服务进程内存,不写磁盘、不进浏览器存储、不入库。
需要把麦当劳 MCP 配到其他客户端(Claude / Cherry Studio / WorkBuddy 等)?见
mcp-config.example.json与MCP_INTEGRATION.md。
使用示例
1. 四步就用完
| 步骤 | 操作 | 说明 |
|---|---|---|
| ① 连接 | 点标题下方的「连接 MCP」,粘贴 Token | 也可用 MCD_MCP_TOKEN 环境变量跳过 |
| ② 选店 | 点「换门店」,分「城市」「地标」两栏填(如 城市=杭州、地标=三里亭) |
城市栏只认城市名,两栏混填会搜不到;列表标注「营业中 / 已打烊」 |
| ③ 摇 | 点拉杆或按钮 | 三个滚轮停位由概率表驱动,不是三列独立随机 |
| ④ 看 | 结果面板 | 营养汇总 + 每件单价 + 整单折扣 + 提示;能拼 1+1 时给出套餐价与本单实付,此时底部「一键下单」可点 |
2. 示例输出
摇出「巨无霸 + 中薯条 + 可口可乐(中)」时,页面大致长这样(数值随门店、时段、菜单变化,仅示意版式):
这一餐(三件)
巨无霸 ¥27.00
中薯条 ¥12.50
可口可乐(中) ¥10.50
原价 ¥50.00 · 组合优惠 -¥3.00 · 实付 ¥47.00
相当于 9.4 折,省 ¥3.00
营养(单餐)
能量 1002 kcal 蛋白质 31 g 脂肪 41 g
碳水 129 g 钠 1181 mg
· 钠 1181 mg 已超过单餐参考值(约 660 mg),换杯无糖的会更稳
· 只要吃得爽,热量不存在(本条为彩蛋文案,非营养建议)
巨无霸与中薯条的营养、钠含量取自官方营养库真实记录(513 kcal / 961 mg、289 kcal / 190 mg),饮品按同类中杯估算;加总规则与页面一致:逐项相加后折算 NRV,不做美化。
终端启动输出:
$ node server.js
麦摇摇 McShake · 麦当劳营养账本与凑单优化器
访问 http://127.0.0.1:8787
也可通过环境变量自动连接:MCD_MCP_TOKEN=xxx node server.js
3. 常见问题
- 搜不到门店:
city必须是城市名(杭州),门店全名要塞进「地标」栏。接口无分页,每次返回最近 5 家,弹窗支持多次搜索累积去重。 - 菜单是空的:该店已打烊。
query-meals对打烊门店正常返回但餐品为空,calculate-price会回60003 门店暂停营业。 - 某件商品算不出价:菜单按时段动态变化,商品可能在菜单里但不可售。整单被拒时会自动降级为逐件试算,并指出是哪一件买不了。
- 1+1 没出现:随心配按餐段供应。早餐时段菜单里没有它,页面会写「本时段没有供应」;也可能这单的两件不属于同一套餐的不同分区。
- 营养数据显示「MCP 营养库里没有它」:那是新品——官方营养表更新比菜单慢。本项目不拿相近单品顶替,宁可留空。
工作原理
1. 营养匹配阈值 0.55
低于阈值说明规格或品类可能不同(「中薯条」与「大薯条」相似度仅 0.475),此时关联价格的风险高于收益,宁可留空并说明原因。
2. 参数三级降级
官方只公开了工具名与描述,没有参数 schema。处理顺序:模板优先(data/tool-templates.json)→ tools/list 真实 schema 启发式推断 → 候选参数组逐个重试。门店查询就是第三级救回来的。
3. 老虎机的三个取舍
| 取舍 | 原因 |
|---|---|
| 开奖由概率表驱动,而非三列独立随机 | 独立随机的三连概率仅约 0.2%,玩一百次都中不了,游戏没有正反馈 |
| 滚轮只收单品,套餐全部剔除 | 套餐营养取决于搭配,营养库没有对应记录;价格只有组合价、拆不出单件单价 |
| 装载滚轮时预检可售性 | 菜单按时段动态变化,商品会出现在菜单里却下不了单。预检可提前发现并标注 |
4. 计价归一化与折扣分摊
productList[].subtotal 是服务端给出的已分摊优惠后的单品小计,优先采用;当各行优惠合计与整单 discount 对不上时,按原价比例二次分摊,尾差由最后一行吸收,保证「各行相加 = 整单实付」。
5. 1+1 随心配:套餐不进滚轮,但可以拼
滚轮只收单品(套餐拆不出可信营养与单价),可这两件事并不矛盾——1+1 恰恰是把两个单品拼成一个组合价。所以:
- 装载滚轮时顺手缓存这家店的随心配分区结构(
query-meal-detail的rounds[]:蓝区主食 + 粉区小食/饮品,每项带diffPrice) - 开奖后检查摇出的三件里是否存在「同套餐、不同分区」的一对;命中就用
roundList调一次calculate-price,拿到套餐价 - 与单点合计比较:实测
那么大鸡排 + 小杯玉米杯单点 ¥27.00,套餐 ¥13.90(服务端把-¥1差价也算了进去) - 剩下那件不能丢:套餐只吃掉两件,第三件仍按单点算,所以拼单报价 = 套餐 + 剩余单品。这三样一起再试算一次(实测 1490 + 2350 = 3840),得出的是「本单实付」;「省了多少」以整单单点合计为基准,而不是套餐内那两件——否则 ¥37.40 的单子会被写成「省 ¥13.10、7.4 折」以外的数
- 三个数各司其职:
combo.payable(套餐价)、combo.totalPayable(本单实付)、combo.baseTotal(单点合计),前端据此渲染,避免口径混淆
早餐时段菜单里没有随心配,此时索引为空,页面提示「本时段没有供应」——这是正常结果,不是接口失败。
接口契约与踩坑记录(金额单位是「分」、字段名写错静默返回 0、success:false 不带 data 层等)全部写在 MCP_INTEGRATION.md。
项目结构
.
├── server.js # 零依赖 HTTP 服务:路由 + MCP 会话 + 菜单/开奖/计价编排
├── public/
│ └── slot.html # 单页前端:像素风老虎机、门店弹窗、结果面板、8bit BGM
├── src/
│ ├── mcp-client.js # 自研 MCP Streamable HTTP 客户端(SSE/JSON、会话、限流)
│ ├── store-query.js # 城市/关键词拆分与候选组重试
│ ├── slot.js # 符号精选、概率表开奖、赔付
│ ├── nutrition.js # 餐品→营养匹配、NRV 计算、彩蛋文案
│ ├── combo.js # 1+1 随心配分区索引、拼单匹配、套餐折扣
│ ├── demo-mcp.js # 演示模式:把官方数据快照包装成同签名的 MCP 客户端
│ └── optimizer.js # 计价结果归一化、折扣分摊、折扣文案
├── demo/
│ └── demo-snapshot.json # 线上试玩用的官方数据快照(238 KB,含 286 种组合的真实计价)
├── scripts/
│ └── build-demo-snapshot.js # 重新抓取快照(约 20 秒)
├── test.js # 核心算法自测(155 项,无需 Token)
├── test-integration.js # 协议层集成测试(27 项,内置模拟 MCP 服务)
├── docs/
│ ├── demo-spin.gif # 操作演示:SPIN → 停位 → 营养与计价 → 命中 1+1
│ └── demo-store.gif # 操作演示:换门店弹窗搜索并切换
├── mcp-config.example.json # 脱敏 MCP 配置示例(仅环境变量占位符)
├── MCP_INTEGRATION.md # MCP 接入说明与实测契约
├── workbuddy.md # WorkBuddy 开发对话存档
├── CONTEST_DECLARATION.md # 参赛声明(官方原文)
└── ISSUE_BODY.md # 报名 Issue 的提交文本
data/、public/img/slot/为运行时缓存(菜单缓存、官方 CDN 图片),已在.gitignore中排除,首次运行自动生成。
接口
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | /api/health |
连接状态、工具数、当前门店与营业状态、营养库/菜单数量 |
| POST | /api/mcp/connect |
连接 MCP 并拉取工具列表 |
| GET | /api/mcp/tools |
工具列表与参数 schema |
| POST | /api/mcp/call |
透传调用任意工具(排障用) |
| POST | /api/stores |
查询门店(city + keyword 分离,内部按候选组重试) |
| POST | /api/store/select |
选择门店并加载菜单 |
| POST | /api/store/clear |
清空门店上下文 |
| GET | /api/slot/symbols |
滚轮符号集(真实餐品图片 + 营养 + 单价 + 可售状态) |
| POST | /api/slot/result |
按三个餐品编码返回营养汇总、计价试算、1+1 拼单判断与健康提示 |
页面唯一入口是 /(等价于 /slot.html)。
测试
node test.js # 155 项,核心算法(无需 Token、不联网)
node test-integration.js # 27 项,协议层(内置模拟 MCP 服务)
共 182 项断言,覆盖符号精选、概率支付表、开奖画面一致性(5000 次循环)、营养匹配与 NRV、计价归一化与折扣分摊、1+1 分区索引与拼单匹配、门店参数拆分、SSE/JSON 双格式握手、会话保持、401/429、限流、官方表格格式解析、混合文本 JSON 提取,以及演示快照的自洽性(三连命中快照原样返回、套餐价 = 基础价 + 差价、未知餐品不给假价格)。
边界与合规
- 不提供自动下单。页面底部「一键下单」是占位按钮,点击只提示规划中;
create-order、mall-create-order、party-order-create会产生真实支付链接,本项目一律不调用。 - 不碰抽奖。
draw-lottery原子扣减积分且不可撤销,本项目只在早期调研时用过只读的query-lottery-info。 - 限流。每个 Token 每分钟 600 次请求,客户端内置令牌桶,超额排队而非直接失败。
- Token 安全。仅存于服务进程内存,不落盘、不入库;仓库已排除所有可能含凭证的调试产物。
- 营养口径。数据来自官方
list-nutrition-foods,NRV 按单餐 33% 折算,仅供参考,不构成医疗或营养建议。 - 素材版权。滚轮餐品图片来自麦当劳官方 CDN(
menu-img.mcd.cn),属麦当劳版权素材,不随本仓库分发,首次运行时自动下载到本地缓存。 - 服务条款。麦当劳 MCP 仅限个人非商业用途,使用时须遵守其《使用条款》与《MCP 服务规则》。
- 许可。代码以 MIT 授权;但麦当劳 MCP 服务、餐品图片与商标仍归麦当劳所有,不在本仓库授权范围内。
参赛材料
| 文件 | 对应要求 |
|---|---|
README.md |
项目介绍、安装方法、使用示例、目标用户 |
CONTEST_DECLARATION.md |
参赛声明(官方原文,未作任何改动) |
MCP_INTEGRATION.md |
实际使用的 McDonald's MCP Server、Tool、调用流程与业务价值 |
mcp-config.example.json |
脱敏 MCP 配置示例,仅环境变量占位符 |
workbuddy.md |
WorkBuddy 开发对话存档 |
server.js / src/ / public/ |
项目源代码与可运行内容 |
本项目为麦当劳程序员节创意开发大赛参赛作品,由参赛者独立开发,非麦当劳官方产品。项目输出仅供参考;餐品信息、价格及供应状态以麦当劳官方渠道的实时结果为准。
Yorumlar (0)
Yorum birakmak icin giris yap.
Yorum birakSonuc bulunamadi