Skip to content

About

基于麦当劳mcp的订单卡路里统计

Resources

Stars

102 stars

Watchers

0 watching

Forks

Repository files navigation

🍔 订单卡路里一览 · mcd-order-calories

把麦当劳历史订单,折算成看得懂的卡路里

说一句「麦麦,今天吃了多少卡」或「算算我吃过多少卡」,它拉出你的历史订单、展开套餐成分、对齐营养表,逐单给出热量——并且如实告诉你哪几项没算出来。

Type

MCP

Deps

Tests

License

💛 如果这个思路对你有用,点个 ⭐ Star 支持一下!本作品正在参加「麦当劳程序员节创意开发大赛」,Star 数决定排名 🙏


📖 项目介绍

订单卡路里一览是一个基于麦当劳中国 MCP 的只读 Skill:拉取你的历史订单,展开套餐成分,与官方营养表对齐,逐单算出热量。

算法本身只有一行 Σ(kcal × 数量)。真正的工作量在于商品归一——订单与营养表只能按商品名对齐,而两边命名习惯不同(套餐容器 vs 真实成分、可口可乐中杯 vs 可乐中杯、全角括号 vs 半角…)。本项目用五级级联匹配处理,并且算不出来的部分如实披露,绝不拿一个偏低的下界冒充总量。

输入 你的麦当劳账号(通过 MCD_MCP_TOKEN)
输出 每单热量 + 覆盖率 + 每个数的来源层级与不确定性
调用 仅 order-list 与 list-nutrition-foods 两个查询接口,全程只读
依赖 零第三方依赖,纯 Python 标准库;clone 下来零安装即可运行与测试

😩 你有没有过这种经历

连着几周中午都吃麦当劳,某天突然想知道:我这一个月到底吃进去多少卡?

  • 麦当劳 App 只显示订单,不显示热量;
  • 营养表只有单品数据,你得自己一个个对;
  • 更麻烦的是——订单里写「人气经典随心配」,营养表里写「双层吉士汉堡」+「小杯玉米杯」。套餐是个容器,真正吃进去的是里面的成分;
  • 好不容易对上几个,发现还有「可口可乐中杯」在营养表里叫「可乐中杯」……

这个 Skill 就是把这件事做完,而且做不完的部分如实告诉你。


🎬 真实运行输出

$ mcd-calories

订单卡路里一览
营养表 158 条 · 别名表 2 条 · 已判定缺口 0 条
  ⚠️ 数据源:营养表存在重复条目:小杯玉米杯、纯牛奶(盒装)
==============================================================================
2026-09-18 12:29      482 kcal  2/2       ***得来速餐厅
2026-09-07 18:10     2225 kcal  8/8       麦当劳***餐厅
2026-09-03 10:14      526 kcal  2/2       麦当劳***餐厅
==============================================================================
完整覆盖 10/10 单 · 覆盖商品 45/45 项 · 已覆盖部分合计 12290 kcal

加 --detail 看每一单吃的是什么、每个数是怎么来的:

$ mcd-calories --detail

2026-09-07 18:10:14  ·  麦当劳上海古浪路桃浦星品荟餐厅  ·  实付 ¥62.9
  🔥 2225 kcal
     · 泰式炭烤风味猪猪堡  450 kcal  [supplement·估算 450(430~495),medium 置信度]
     · 风味双层深海鳕鱼堡  485 kcal  [guarded]
     · 麦乐鸡4块          170 kcal  [exact]
     · 麦辣鸡翅2块        224 kcal  [exact]
     · 中薯条             289 kcal  [exact]
     · 草莓麦旋风         295 kcal  [exact]
     · 【美汁源】“多汁柠柠” 165 kcal  [category:minute_maid·估算]
     · 可口可乐中杯       147 kcal  [guarded]
     ℹ️ 「泰式炭烤风味猪猪堡」为推导估算值 450(430~495) kcal(medium 置信度,非营养表数据)
        推导:【锚点自检】双层猪柳蛋麦满分 513 = 马芬137 + 猪柳饼126×2 + 蛋79 + 芝士46 = 514 ✓
              【方法A】面包113 + 猪柳饼126×2 + 酱85 = 450
              【方法B】513 − 24(马芬→面包) − 79(去蛋) − 46(去芝士) + 85(加酱) = 449
        来源:...①本堡是否含芝士——若含,实际值约 496(故区间上界给到 495)...

🚀 快速开始

三步:安装 → 配置 Token → 运行。需要 Python ≥ 3.9,零第三方依赖。

安装方法

有两种,任选:

# 方式一:安装为命令行工具(推荐)
git clone <this-repo>
cd mcd-order-calories
python3 -m pip install -e .
mcd-calories                     # 安装后可直接用

# 方式二:不安装,直接以模块方式运行
PYTHONPATH=src python3 -m mcd_order_calories.cli

配置 Token

在 M-China/mcd-mcp-server 申请后设置环境变量:

export MCD_MCP_TOKEN="你的 Token"

使用示例

mcd-calories                  # 订单卡路里一览(默认命令,不写子命令也行)
mcd-calories --detail         # 展开每单的商品明细与估算推导
mcd-calories --json           # 结构化输出,便于程序消费
mcd-calories gaps             # 列出未匹配商品,区分「已知缺口」与「新出现」
mcd-calories explain 双层脆鸡堡  # 解析单个商品名,展示判定理由与全部候选
mcd-calories doctor           # 检查连通性与所需工具

未安装时把上面的 mcd-calories 换成 PYTHONPATH=src python3 -m mcd_order_calories.cli。


🎯 真正难的不是算术,是「商品归一」

算法只有一行:Σ(kcal × 数量)。难点在于订单和营养表只能按商品名对齐,而两边命名习惯不同。

订单里写的 营养表里写的 差异类型
人气经典随心配 双层吉士汉堡 + 小杯玉米杯 套餐 vs 成分
可口可乐中杯 可乐中杯 少一个前缀
纯牛奶(盒装) 纯牛奶(盒装) 半角 / 全角
麦辣鸡翅2块 麦辣鸡翅-2块 连字符
那么大鸡排(椒盐风味) 那么大鸡排 尾部口味后缀
【美汁源】“多汁柠柠” (营养表没有) 覆盖缺口

本项目用五级级联处理,每一级都有明确的采纳条件:

层级 名称 采纳条件 信任度
1 alias 人工复核过的别名表 最高
2 exact 归一化后完全相等 高
3 supplement 人工推导的估算值(附推导+区间+来源) 估算
4 category 命中品类规则(蘸酱、【美汁源】、厚松饼堡) 估算
5 stripped 剥掉尾部括号后与营养表完全相等 高
6 guarded 候选经硬约束过滤后只剩一个 中
7 ambiguous / unmatched 多候选或没候选 → 不猜,如实上报 —

用 mcd-calories explain <商品名> 可以看到任意一个商品名的判定理由与全部候选(含被淘汰的原因):

$ mcd-calories explain 那么大鸡排(椒盐风味)
判定:stripped
结果:那么大鸡排  385.0 kcal
理由:剥掉尾部括号说明「(椒盐风味)」后与营养表条目一致

$ mcd-calories explain 双层脆鸡堡
判定:supplement
结果:双层脆鸡堡  520.0 kcal
理由:命中补充热量表(本项目推导),520(490~560) kcal(medium 置信度,非营养表数据)

🔒 四条不可动摇的规则

1. 硬约束优先于相似度——因为错答案的相似度更高

实测数据:

similarity(可口可乐中杯, 无糖可口可乐中杯) = 0.732   ← 错,且更高
similarity(可口可乐中杯, 可乐中杯)        = 0.633

含糖与无糖只差两字,字符串上反而更「像」。如果按相似度排序取第一,这一项会算错约 150 kcal。

所以本项目先做硬约束检查(含糖标记 / 杯型 / 规格 / 数量 / 冷热),冲突的候选一律淘汰,且单方未标注不算冲突(否则「可乐中杯」永远匹配不上「可口可乐中杯」)。

实际输出——注意 ✗ 那一行的相似度比正确答案还高:

$ mcd-calories explain 可口可乐中杯
查询:可口可乐中杯
判定:guarded
结果:可乐中杯  147.0 kcal
理由:唯一候选,相似度 0.633;另有 2 个候选被硬约束淘汰

候选(相似度仅供人工参考,不作为自动决策依据):
  ✓ 可乐中杯                  0.633
  ✗ 无糖可口可乐中杯            0.732  ← 含糖标记不一致:无糖 ≠ 含糖

这就是为什么本项目从不按相似度排序取第一。

2. 唯一性才自动采纳

过滤后多于一个候选,一律判 ambiguous 交人工,不排序取第一。

自动采纳门槛 0.60 是用真实数据标定的,不是拍的:门槛 0.30 时实测出两个假阳性——

【美汁源】“多汁柠柠” → 【美汁源】“黄金橙橙”(相似度 0.314)  不同口味
双层脆鸡堡          → 双层吉士汉堡   (相似度 0.312)  不同商品

安全区间实测为 (0.312, 0.633]。宁可漏,不可错——热量工具里的假阳性会静默污染数值,比承认一个缺口糟糕得多。

3. 未支付订单不计入「已摄入」

order-list 会返回未支付订单。实测账号里就有 1 条「待支付」——如果直接计入合计,
就会把还没吃到的东西算成已经吃进去的热量。

所以本项目把订单状态分类(规则见 data/order_status.json):只有「订单已完成」计入合计,
待支付 / 进行中 / 已取消 / 状态未识别一律单独展示并披露:

【已消费】计入合计
2026-09-18 12:29      482 kcal  2/2       上海康建商务广场得来速餐厅
...
==============================================================================
【待支付】**不计入合计**  —— 未支付,还没吃到
2026-10-09 17:01     ≥436 kcal  2/4    ⚠️ 麦当劳上海古浪路桃浦星品荟餐厅
==============================================================================
已消费 9 单 · 完整覆盖 9/9 单 · 覆盖商品 41/41 项 · 已覆盖部分合计 11211 kcal
未计入合计:待支付 1 单(≥436 kcal)

待支付订单的卡路里仍然会算出来——它本身是有用信息("你有个待支付订单,约 436 kcal"),
只是不能混进「已摄入」。

⚠️ 另外注意:order-list 没有分页参数,只返回最近 10 条。新订单会把最老一条挤出窗口,
所以本项目给出的是近期窗口内的统计,不是全部历史。

4. 覆盖率是硬性输出,绝不隐藏

实测某单只覆盖 1/4 项时算出 87 kcal,而真实热量在 800 kcal 以上。只给数字不给出覆盖率会严重误导。

所以输出永远同时给数值和覆盖率,覆盖率不足 100% 一律标注为下界:

🔥 ≥234 kcal  ⚠️ 仅覆盖 2/4 项(50%)
   ⚠️ 缺少:枫糖风味厚松饼猪柳堡、世界波脆鸡排蛋堡
   ⚠️ 实际热量显著高于此值,请勿据此做热量控制

🧩 估算值也是可审计的

营养表只收录 158 条,而实际商品远多于此。本项目用四张人工维护表分层处理,每层职责明确:

文件 用途 要求
aliases.json 订单名 → 营养表已有条目 需说明判断依据
categories.json 整个品类共用默认值 需说明来源
supplements.json 单个商品的推导估算值 必须附推导过程 + 误差区间 + 非官方来源(缺一即加载报错)
known_gaps.json 确认算不出,如实披露 需说明为什么

supplements.json 里的每个值都附带完整推导,例如双层脆鸡堡:

【基准值校准】芝士 = 吉士汉堡包294 − 汉堡包248 = 46
              牛肉饼 = 双层吉士汉堡429 − 吉士汉堡包294 = 135
              面包   = 汉堡包248 − 135 = 113       (自检 294 + 135 = 429 ✓)
【反推鸡排】  麦香鸡369 = 面包113 + 鸡排 + 生菜4 + 酱100 → 鸡排 ≈ 152
【方法A】     113 + 2×152 + 100 = 517
【方法B】     双层吉士汉堡/吉士汉堡包 = 1.459;369 × 1.459 = 538
【结论】      两法收敛于 517~538,取中值 520(区间 490~560)

并且所有补充值共用同一套基准值(面包113 / 马芬137 / 蛋79 / 芝士46 / 猪柳饼126 / 脆鸡扒178 / 麦香鸡鸡排152 / 酱85~100),有三组独立自检闭合:

火腿扒麦满分 261 = 马芬137 + 火腿扒124             ✓
猪柳麦满分   308 = 马芬137 + 猪柳126 + 芝士46       ✓
猪柳蛋麦满分 387 = 137 + 126 + 79 + 46             ✓

每条估算都在输出里带区间、置信度和推导过程,谁都能复核这个数是怎么来的。


👥 目标用户

用户 场景 本项目解决什么
在做饮食管理的人 减脂 / 增肌 / 控糖期间仍会吃麦当劳,需要知道实际摄入 App 只显示订单不显示热量;本项目把历史订单折算成卡路里
想复盘自己饮食习惯的人 「我这个月到底吃了多少麦当劳、热量多高」 一次列出全部历史订单的热量,并可按门店/时间观察
麦当劳高频用户 常点固定几样,想知道这些到底多少卡 常点商品逐项列出热量与匹配来源
营养 / 健康类内容创作者 需要可引用的数据,且必须说明数据来源与不确定性 每个数都标注来源层级;估算值附区间、置信度与推导过程
Agent 开发者 需要一个「两个数据源只能按名称对齐」的工程参考 五级级联匹配 + 假阳性防御 + 覆盖率披露,可作为 MCP 工程范式参考

⚠️ 重要边界:本项目给出的是估算参考,不是医学或营养诊断依据。营养表未收录的商品按文中标注的方式估算并显式披露区间;覆盖率不足时数值是下界。请勿据此做严格的医学决策。

🧪 测试

零依赖运行——用标准库 unittest,clone 下来不用装任何东西就能验证:

PYTHONPATH=src python3 -m unittest discover -s tests -t tests    # 107 passed

测试锁死的都是实测踩过的坑,不是设想的边界:

  • 两个假阳性回归(双层脆鸡堡、多汁柠柠 必须永远被拒)
  • 相似度错答案更高(0.732 > 0.633)这个反直觉事实
  • 含糖 / 无糖不可互配,大杯 / 中杯不可互配
  • 蘸酱鸡球(甜酸酱风味) 不是酱,不能被酱规则吞掉 200+ kcal
  • 剥掉括号后虽相等、但含糖冲突必须拒绝
  • 覆盖率不足时必须标注下界
  • 补充表三条硬规矩(推导 / 区间 / 来源)缺一即报错
  • 品类正则写错在加载期报错而非静默失效
  • 出厂的四份 JSON 真实加载(最容易坏的不是代码而是数据)

验证状态(如实说明)

项 状态
协议实现(握手 / MCP 调用 / 响应解析) ✅ 107 项测试全绿
归一化与五级匹配 ✅ 端到端跑通
真实端点 https://mcp.mcd.cn 真实账号数据 ✅ 10/10 单完整覆盖,45/45 项命中
估算值(5 项) ⚠️ 见上方区间与推导,非官方数据

与本作品另一个方向不同:这个项目的核心逻辑已经用真实账号数据验证过,不是只跑通 mock。


🗂 项目结构

.
├── README.md                  # 本文件
├── CONTEST_DECLARATION.md     # 参赛声明(官方原文,逐字节一致,请勿修改)
├── MCP_INTEGRATION.md         # MCP Server / Tool / 调用流程 / 业务价值
├── mcp-config.example.json    # 脱敏 MCP 配置示例(仅环境变量占位符)
├── SKILL.md                   # 通用 Agent Skill 入口
├── pyproject.toml             # 打包配置(零第三方依赖)
├── LICENSE
├── src/mcd_order_calories/
│   ├── mcp_client.py          # 零依赖 Streamable HTTP MCP 客户端
│   ├── payload.py             # 解析服务端「字段说明 + JSON」混排返回
│   ├── nutrition.py           # 营养表自定义分隔格式解析
│   ├── normalize.py           # 归一化 + 硬约束(guard)提取
│   ├── categories.py          # 品类规则(蘸酱 / 【美汁源】/ 厚松饼堡)
│   ├── supplements.py         # 推导估算值与三条硬规矩校验
│   ├── matching.py            # 五级级联匹配器(本项目核心)
│   ├── orders.py              # 订单展开、覆盖率、缺口台账
│   ├── cli.py                 # 命令行入口
│   └── data/                  # 四张人工维护表
├── tests/                     # 107 项测试(零依赖 unittest)
└── docs/
    └── data-maintenance.md    # 数据表的维护流程

📌 合规说明

  • CONTEST_DECLARATION.md 为官方原文,文件名与内容均未改动(SHA-256 cd28101bcb29cc8fb24b372fe91ce6b42c4a1487a54e01f7ade0c8c6a84241b7)。

  • 仓库内不含任何真实 Token、密钥、账号凭证或他人个人信息;配置文件仅使用环境变量占位符。

  • 项目为参赛者原创,仅通过麦当劳官方 MCP 接口访问数据,未抓取或内嵌任何非公开数据。

  • 本项目只读:仅调用 order-list 与 list-nutrition-foods 两个查询接口,不涉及任何下单、领券、抽奖等写操作。

  • 所有热量数值仅供估算参考,不构成医疗、营养或其他专业建议;餐品信息以麦当劳官方渠道实时结果为准。营养表未收录的商品按文中标注的估算方式处理,会显式披露区间与推导。

本项目为「麦当劳程序员节创意开发大赛」参赛作品,由参赛者独立开发,非麦当劳官方产品。

📄 License

MIT

About

基于麦当劳mcp的订单卡路里统计

Resources

Stars

102 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages