在「预算 × 热量 × 人数 × 忌口」多重约束下,求解麦当劳最优下单方案的 Skill。 数据全部来自麦当劳 MCP Server 的真实接口——菜单、价格、营养、优惠券一律实时拉取,不做任何硬编码。 它还会先把券领了,再带着券求解。
同一家门店、同一组约束(2 人 / 预算 ¥70 / 人均热量 ≤ 800 kcal), 差别只在于「有没有把券算进去」:
| 方案 | 实付 | 原价 | 热量 | 性价比 | |
|---|---|---|---|---|---|
| 不用券 | 那么大鸡排 ×3 | ¥42.00 | ¥42.00 | 1155 kcal | 27.5 kcal/元 |
| 用券 | 中薯条买一送一(券)×1 + 那么大鸡排 ×2 | ¥41.50 | ¥55.00 | 1348 kcal | 32.5 kcal/元 |
少花 ¥0.5,多吃 193 kcal,每元买到的热量高 18%,同时省下 ¥13.5。
数据于 2026-10-15 由麦当劳 MCP 实时接口拉取,含真实领取的优惠券。
减肥期想吃麦当劳、两个人只有 70 块、团队下午茶要凑够 8 份还不能太咸—— 这些本质上是同一个问题:带约束的组合优化。
菜单有上百个餐品、套餐还有隐藏的默认组成、卡包里躺着几张不知道能不能用的券, 靠人脑根本算不清。这个项目把它解出来,一次给出三套方案:
| 目标 | 含义 |
|---|---|
| 最省钱 | 满足热量下限前提下的最低实付,附人均花费 |
| 最扛饿 | 每元买到的千卡最高(性价比) |
| 最均衡 | 蛋白供能比接近 20%、钠更低、总热量贴近目标 |
纯 Python 标准库,零依赖,不用 pip 装任何东西。
# 1. 拿代码
git clone https://github.com/linghuchong6668/mcd-optimizer.git
cd mcd-optimizer
# 2. 准备麦当劳 MCP Token(获取方式见 M-China/mcd-mcp-server)
export MCD_MCP_TOKEN=<你的 Token>
# 3. 跑
python3 scripts/optimizer.py --city 上海 --keyword 人民广场 \
--people 2 --budget 70 --kcal 800把 MCP 接入任意支持 Streamable HTTP 的客户端,示例见 mcp-config.example.json
(仅含环境变量占位符,不含任何真实凭据)。
这是本项目和"随便挑几样"最大的区别。券链路用到三个 Tool:
auto-bind-coupons → 一键领取可领的麦麦省券
query-store-coupons → 查该门店当前可用券,以及每张券对应哪个商品
calculate-price → 带 couponId / couponCode 真实核价
之所以必须单独处理,是因为实测发现两个反直觉的坑——这两个坑不修,券就等于白领:
坑 1:券对应的商品不在菜单里。
「中薯条买一送一」是一个券专属商品(编码 9900012387),query-meals 里根本查不到。
不单独建模,它永远进不了候选池——而它恰恰是最划算的选项(原价 ¥27,凭券 ¥13.5)。
坑 2:不带券核价会静默丢件。 把券商品塞进订单但不带券,接口会悄悄把它丢掉,只返回其余商品的合计:
鸡排×2 + 中薯条买一送一(不带券) → ¥28.00 ← 薯条凭空消失
鸡排×2 + 中薯条买一送一(带券) → ¥41.50 ← 原价 ¥55,省 ¥13.5
照 ¥28 去比较,会得出「不用券更便宜」的荒谬结论——而这单根本下不了。
代码做了三层防护:price <= 0 判无效、比对 productList 条数发现丢件、
含券商品的方案只认带券那次核价。
python3 scripts/optimizer.py --city 上海 --keyword 人民广场 \
--people 2 --budget 70 --kcal 800· 领券跳过:券已领过或当前没有可领活动
门店:麦当劳上海黄浦华旭国际大厦餐厅(1450713)|2 人|预算 ¥70|人均热量上限 800 kcal|券:3 张可用
==============================================================
可行方案 753 个,实际核价 12 个(预算内)
· 已剔除 9 个核价后超出预算的组合
--------------------------------------------------------------
【最省钱】那么大鸡排(椒盐风味)×2 + 派×1
实付 ¥36.50|人均 ¥18.25
热量 991 kcal(人均 496)|蛋白 50 g|钠 2139 mg|性价比 27 kcal/元
- 那么大鸡排(椒盐风味) ×2 ¥14.00 385 kcal
- 派 ×1 ¥8.50 221 kcal
【最扛饿】中薯条买一送一(券)×1 + 那么大鸡排(椒盐风味)×2
实付 ¥41.50|人均 ¥20.75|省 ¥13.50(原价 ¥55.00,省 25%)
热量 1348 kcal(人均 674)|蛋白 56 g|钠 2322 mg|性价比 32 kcal/元
- 中薯条买一送一(券) ×1 ¥13.50(原价 ¥27.00) 578 kcal
- 那么大鸡排(椒盐风味) ×2 ¥14.00 385 kcal
ⓘ 用券:中薯条买一送一
【最均衡】麦香鸡×1 + 双层深海鳕鱼堡×1 + 巨无霸×1
实付 ¥66.50|人均 ¥33.25
热量 1367 kcal(人均 684)|蛋白 70 g|钠 2609 mg|性价比 21 kcal/元
- 麦香鸡 ×1 ¥17.00 369 kcal
- 双层深海鳕鱼堡 ×1 ¥24.00 485 kcal
- 巨无霸 ×1 ¥25.50 513 kcal
--------------------------------------------------------------
python3 scripts/optimizer.py --people 2 --budget 70 --kcal 800 --html docs/sample.html会输出一张带券信息、性价比、营养指标的方案卡片,浏览器直接打开 docs/sample.html 即可看到效果。
| 参数 | 说明 | 默认 |
|---|---|---|
--people |
用餐人数 | 1 |
--budget |
总预算上限(元),0 表示不限 | 0 |
--kcal |
每人热量上限(kcal),0 表示不限 | 0 |
--min-kcal |
每人热量下限,默认取上限的 60% | 自动 |
--avoid |
忌口关键词,逗号分隔 | 空 |
--extra |
除主食外允许的共享附加件数 | 1 |
--main |
主食件数下限 | 等于人数 |
--top |
每种目标输出的方案数 | 1 |
--no-claim |
跳过一键领券,只用现有卡包 | 关闭 |
--html |
输出可视化 HTML 卡片到指定路径 | 无 |
--json |
额外输出结构化结果,便于接自己的前端 | 关闭 |
--city / --keyword |
定位门店 | 上海 / 人民广场 |
--store-code |
直接指定门店编码,跳过搜索 | 无 |
--be-type |
1 到店自取,2 麦乐送,5 得来速,6 团餐 | 1 |
# 减脂期单人餐:热量封顶 500 kcal,必须有主食
python3 scripts/optimizer.py --people 1 --budget 30 --kcal 500 --main 1
# 4 人加班餐,允许 2 件共享小食
python3 scripts/optimizer.py --people 4 --budget 150 --kcal 900 --extra 2
# 忌口 + 输出 JSON
python3 scripts/optimizer.py --people 2 --budget 60 --avoid 辣,花生 --json
# 只想看现有卡包里的券,不想再领
python3 scripts/optimizer.py --people 2 --budget 60 --no-claimMCP 负责提供真实数据与权威核价,重计算放本地,两者通过约束求解衔接—— 而不是把大模型当计算器反复打接口。
- 真实数据:门店、菜单、现价/原价、营养成分全部实时拉取,菜单换了项目不用改。
- 套餐营养反推:营养表只有单品维度,菜单里大量是组合餐。命中失败时用
query-meal-detail拆出套餐默认组成(主食+小食+饮料)逐项累加, 营养覆盖率从 32% 提到接近全覆盖。 - 券商品单独建模:菜单里买不到的券专属商品,带券核价拿到真实到手价后强制入池; 「买一送一」类券还会把营养翻倍,避免把两份薯条的热量算成一份。
- 约束建模:主食件数、预算上限、热量上下限、忌口关键词同时生效, 避免「最省钱 = 两个冰淇淋」这种废解。
- 多样性保留:三个目标各自用自己的代理指标先排一遍、各贡献 1/3 名额再统一核价。 否则先按价格砍到 N 个,最扛饿和最均衡就只能矮子里拔将军。
- 按实付复核预算:菜单价是「起价」,套餐真实核价往往更高 (实测有组合菜单合计 ¥128、实付 ¥155),所以核价后必须再复核一次预算。
调用量控制在单次 80 次以内,远低于 600 次/分钟的限额;客户端内置 429 指数退避, 营养表落盘缓存 12 小时,领券后查卡包会强制回源避免读到旧缓存。
.
├── SKILL.md # WorkBuddy Skill 定义与使用说明
├── README.md
├── MCP_INTEGRATION.md # MCP 调用流程与业务价值
├── CONTEST_DECLARATION.md # 参赛声明(官方原文,未改动)
├── mcp-config.example.json # 脱敏 MCP 配置示例
├── workbuddy.md # WorkBuddy 开发上下文
├── docs/sample.html # 真实跑出来的方案卡片样例
└── scripts/
├── mcd_mcp.py # MCP Streamable HTTP 客户端(缓存 + 限流退避 + 安全调用)
├── nutrition.py # 营养表解析与餐品名模糊匹配
└── optimizer.py # 领券、券建模、约束求解、核价与排序、HTML 渲染
会帮我自动下单吗? 不会。项目定位是决策辅助,只用到了查询与核价类 Tool,不用下单类 Tool, 避免自动化下单带来的资金风险。方案出来后你自己确认再下单。
一键领券是真实操作吗?
是。auto-bind-coupons 会把券真实领进你的卡包。默认开启,不想要加 --no-claim。
为什么算出来的价格跟 App 里看到的不一样?
所有金额都以 calculate-price 的实时返回为准(接口单位是「分」)。
门店优惠、时段、随单购卡都会影响实付,同一方案换个时间可能不同。
改了菜单会不会失效? 不会。菜单、价格、营养、券全部实时拉取,项目里没有任何硬编码的餐品或价格。
- 想控卡又想吃麦当劳的人(减脂 / 健身 / 体检后忌口)
- 精打细算的学生、上班族(同样花钱,吃得更饱)
- 帮团队订下午茶、加班餐的组长 / 行政(按人头与预算自动配平)
本项目为麦当劳程序员节创意开发大赛参赛作品,由参赛者独立开发,非麦当劳官方产品。 输出仅供参考,不构成医疗、营养或其他专业建议;餐品信息、价格、优惠券及供应状态 以麦当劳官方渠道的实时结果为准。