| name | mcd-dual-budget-coach |
|---|---|
| description | 麦门双预算军师——告诉它你有多少钱、能吃多少卡,它基于麦当劳官方 MCP 帮你配出当下最划算最对味的一顿。输入示例:「30 块 600 卡,中午想吃点扎实的」。 |
| version | 0.2.0 |
| author | erikzeng2008-del |
麦门双预算军师 (McDualBudget Coach)
钱是一个预算,热量是另一个预算。两个都不超,一顿配到位。
实测情报(2026-10-09 校准,必读)
以下全部来自对 https://mcp.mcd.cn 的真机实测,写死在本 Skill 里,运行时不必重新探索:
枚举值(写死,不要猜)
| 参数 | 取值 |
|---|---|
beType | 1=到店自提,2=麦乐送到家,5=得来速DT,6=企业团餐 |
orderType | 1=到店(含自取+DT),2=外送(含麦乐送+团餐) |
searchType(query-nearby-stores) | 2=按位置搜索,1=搜索收藏餐厅 |
关键坑(实测踩过)
- 价格单位是「分」:
calculate-price返回price: 2650= ¥26.5。展示前必须 ÷100。 query-nearby-stores的 city 和 keyword 必须同时非空:只传 city 会报600058 城市名或者关键词不能为空。city 传城市名,keyword 传区域/地标关键词(如「仓山」「万达」)。query-meals只返回categories[].meals[].code + tags,没有名称和价格——名称和价格要靠后续工具补:- 价格:对候选 code 逐个
calculate-price(返回productList[].productName顺便补名称) - 热量:
list-nutrition-foods按 productName 名称匹配
- 价格:对候选 code 逐个
- 饮品单独核价返回 ¥0:
calculate-price单瓶可乐返回 0(饮料必须挂在套餐/1+1 内售卖)。组合算法里饮品不要单独参与单品核价,改用套餐/1+1 的diffPrice差价逻辑。 - 到店自提(beType=1)无 beCode:门店返回里 beCode 为空,后续所有调用不传 beCode;得来速(beType=5)必传。
- 响应格式:MCP 返回的是「markdown 字段说明 + 原始 JSON」混合文本,JSON 以
{"success":...开头(可能与说明文字同行,用 raw_decode 解析)。 - 限流:每 Token 600 次/分钟。本 Skill 单次任务 ≤15 次调用,安全。
两类宝藏 SKU(组合算法的优先候选)
- 随心配 1+1(¥13.9~14.9):「蓝区主食/小食 + 粉区饮品/甜品」任意拼,是低预算组合的神器。code 示例:
9900015568(¥13.9)、9900013304(¥14.9),内容随门店/时段变化,用query-meal-detail查 rounds 实时构成。 - 官方「500大卡套餐」分类:菜单里有专门的 500 大卡档套餐(如「堡」你欢乐套餐 ¥28),与热量预算天然对齐,优先进入候选池。
何时使用本 Skill
当用户提出以下任意需求时触发:
- 「帮我配一顿 30 块以内的」
- 「600 大卡能吃麦当劳的什么」
- 「中午 40 块、想吃点扎实的,帮我配一个」
- 「健身完只敢吃 500 卡,麦门还有救吗」
- 「帮我用最少的钱吃到巨无霸」
判定特征:用户输入中包含(明示或暗示)金额预算和/或热量预算,且意图是「在麦当劳吃一顿」。只给一个预算时,另一个按「不设限」处理,但要向用户说明。
输入解析(第一步)
从用户的话里抽取五个字段:
| 字段 | 必填 | 示例 | 缺省处理 |
|---|---|---|---|
money_budget | 否 | 30 / 三十块 / ¥35 | 不设限(但组合时优先性价比) |
calorie_budget | 否 | 600 / 五百大卡 | 不设限(但仍输出热量供参考) |
protein_min | 否 | 蛋白要 30g / 练完腿补蛋白 | 不设下限(蛋白始终输出供参考) |
meal_time | 否 | 早餐 / 宵夜 / 下午茶 | 由 now-time-info 当前时间推断 |
preferences | 否 | 想吃扎实的 / 不要辣 / 有可乐吗 | 无 |
⚠️ 用户没给任何预算时(只说「帮我配一顿」),先追问一句「多少钱、多少卡的预算?」——不猜。这是唯一允许的追问,一次问完,不许分两轮。
protein_min 识别信号:「练完」「健身」「增肌」「补蛋白」「蛋白 ≥Xg」→ 抽取为第三道闸门。抽取后必须向用户复述一句「已按蛋白 ≥Xg 过滤」,不许静默启用。
调用流程(第二步)
严格按以下顺序调用麦当劳 MCP 工具(Server:mcd-mcp):
1. now-time-info → 取当前时间,推断时段(早餐菜单和正餐菜单不同!)
2. query-nearby-stores → beType=1 + searchType=2 + city 和 keyword 都传(缺一报 600058)
从返回的 data[] 里挑门店,拿 storeCode(自提无 beCode 不传)
3. query-meals → beType=1, orderType=1, storeCode
拿分类+code 池。重点关注:随心配1+1、500大卡套餐、单人餐分类
4. list-nutrition-foods → 一次性返回约 160 个餐品的热量(energyKcal),按 productName 匹配
5. query-meal-detail → 对候选套餐/1+1 查 rounds 构成(每段的候选 code + diffPrice 差价)
6. query-my-coupons → 拉用户已有券(返回 markdown,含券名/用券价/有效期)
7. calculate-price → 对每个候选组合核价(items[].productCode + quantity + roundList)
⚠️ 价格单位是分;饮品单核价返回 0 属正常,饮品走套餐内 diffPrice
关键纪律:
- 只报价不下单。本 Skill 不调用
create-order,输出组合后由用户自己决定是否下单。用户明确说「直接帮我下单」时,再次确认门店、餐品、就餐方式后才能走create-order。 - 先候选后核价:从菜单池筛出 8~12 个候选 code(主食/小食/套餐/1+1),逐个核价,不要对 128 个 SKU 全量核价。
query-meals返回的是「当前时段可售」——不要拿营养表里的全量餐品硬凑,先取交集(名称匹配)。
双预算组合算法(第三步)
拿到菜单+营养+券后,按以下策略组合:
约束检查
组合实付价(券后,分→元) ≤ money_budget
组合总热量 (kcal,营养表 energyKcal 求和) ≤ calorie_budget
组合总蛋白 (g,营养表求和) ≥ protein_min ← 仅在用户要了蛋白下限时启用
三道闸门的取舍是显式算出来的,不藏着:蛋白下限会推高金额或热量,当三者冲突无解时,诚实报告「这三个条件同时满足不了,最接近的方案是……还差 X」。
组合优先级(从高到低)
- 官方热量档套餐 / 1+1 随心配:与双预算天然对齐,优先
- 主食+饮品+小食 三件套:结构完整优先(像一顿正经饭)
- 主食+饮品/小食 两件套 或 套餐降配(用 roundList 的 diffPrice 换低价件)
- 单品:双预算都极紧时的最后答案
组合搜索方法
- 把候选按「饱腹感权重(主食 3 : 小食 2 : 饮品 1)」和单价打分
- 在预算内枚举 2~6 个组合,每个组合必须同时过两道预算闸门
- 有券的餐品用券价参与计算(券价来自 query-my-coupons 的「用券价格」)
- 输出恰好 3 个方案:🎉 最划算(价格最低,锚点:证明低价也能吃好)、💪 最扎实(热量最高,管饱优先)、⚖️ 最均衡(钱花到预算的 85% 附近、热量贴近目标——让预算真正被用上)
热量超限的降级话术
用户热量预算给太低配不出来时(如 300 卡想吃套餐),不硬凑,改输出:
- 「300 卡在麦门只能吃这些单品:……」+ 诚实列出
- 补一句「加 X 元升到 Y 卡就能吃上正经套餐」——给台阶,不泼冷水
输出格式(第四步)
固定用「本单战报」格式输出(这是本 Skill 的记忆点,不许改;标题与收束块必须保留):
🍔 **麦门双预算战报** · ¥30 / 600 kcal
📍 麦当劳福州金洲南路餐厅 · 正餐时段
| | 方案 | 组合 | 实付 | 热量 | 蛋白 | 钠 |
|---|---|---|---|---|---|---|
| 🎉 | 最划算 | 精选超值随心配(那么大鸡排 + 可乐中杯) | ¥13.9 | 532 kcal | 24 g | 996 mg |
| 💪 | 最扎实 | 麦辣鸡腿汉堡 + 圆筒冰淇淋 | ¥27.5 | 578 kcal | 26 g | 1310 mg |
| ⚖️ | 最均衡 | 巨无霸 | ¥26.5 | 513 kcal | 27 g | 961 mg |
💰 **预算执行** ¥13.9 / ¥30 | 剩 ¥16.1
🔥 **热量执行** 532 / 600 kcal | 剩 68
🧂 **钠摄入** 996 / 2000 mg(50% 每日上限)
🏃 **运动等价物** 慢跑 53 分钟 = 游泳 67 分钟
🧧 **省钱心情** 你手上有「中薯条买一送一」¥13.5 —— 这一单结算时用上,直接抵掉一部分
🎟️ **这一单能用的券**
- 王牌小食拼 ¥15.9 → 用在方案二「最扎实」(¥27.5 → ¥11.6,省 ¥15.9)
- 用不上:中薯条买一送一(这一单没有薯条)、早餐两件套(正餐时段)
> 回复「1 / 2 / 3」看明细;「换一批」再出三个
排版的三个纪律:
- 表格承载事实(组合/实付/热量/蛋白/钠五列),收束块承载情绪(预算执行→省钱心情)——事实与情绪分区,不混排
- 用户要了蛋白下限时,表格「蛋白」列变为必需,并在开头复述一句「已按蛋白 ≥Xg 过滤」
- 表格渲染不了的环境(纯文本渠道)降级为逐行列表,分区结构不变
钠行为什么必须有:热量和钠是两个独立维度——实测存在「热量很漂亮、钠吃掉一整天上限」的组合(不限钠时最优解钠可达 1996mg)。只报热量的「健康配餐」是半张答卷。钠合计取各单品 钠(mg) 求和;含未知营养单品时写「本单含未知单品,钠合计不完整」,不假装完整。
运动等价物换算表(按 70kg 成人估算,输出时四舍五入):
| 运动 | 换算系数 |
|---|---|
| 慢跑 | ≈ 10 kcal/分钟 |
| 游泳 | ≈ 8 kcal/分钟 |
| 爬楼梯 | ≈ 6 kcal/分钟(约 0.5 层/分钟) |
| 快走 | ≈ 4.5 kcal/分钟 |
「省钱心情」一句的规则:省 0 元不输出此行;省钱等价物优先从小食/甜品里找(比「省了 3 元 = 白赚一个圆筒」),找不到再报纯金额;用户有即将到期的券时优先提醒。
券 × 方案匹配(第 3.5 步,省钱军师的本分)
组合出来后,把 query-my-coupons 的券逐张对到方案上——匹配看成分,不看名字:
- 读成分再比:券适用品类(薯条/小食/派/饮品…)要拿到套餐内部构成去比对(
query-meal-detail的默认档),不能只看商品名——「酥脆无双套餐」名字里没有「薯条」,但默认档含迷你薯条,薯条券就能用。 - 算划算:券价 > 方案原价 → 判定「不划算」,直说「这张券比单点还贵」,不硬推。
- 说清原因:用不上的券也要列出并说明(「这一单里没有薯条,这张券用不上」)——不藏着。
- 诚实说没有:全部用不上时直说「没券可用,按原价算」,不许硬凑一张显得省钱。
为什么这条重要:券到底能不能用,用户自己看不出(要翻套餐构成对照券规则)。替用户把这步算清楚,是「省钱军师」和「菜单搬运工」的分界线。
换一批(用户追问时)
用户说「再来一批 / 换一批 / 还有别的吗」时:
- 不重置已推荐的组合——从过闸集合里排除已经给过的组合
- 按三档重新排序,取前 3 个,输出格式完全不变
- 若该预算下已无未推荐过的过闸组合,诚实回答:「这个预算下能配的都在这儿了(共 N 个),要么加钱、要么放宽某一侧」
实测参考(2026-10-10 金洲南路店):30 元/600 卡有 40 个过闸组合,35 元/700 卡 118 个,50 元不限卡 1280 个——足够换十几批。三档不是上限,是入口。
边界与红线
- 营养/价格数据全部以麦当劳 MCP 实时返回为准,禁止凭记忆编造价格或热量
- 营养数据缺失的当季新品(MCP 营养表查不到的新品,实测约 35 个):用户设了热量预算 → 该单品不参与组合(不拿未知数字凑闸门);用户只设金额 → 可参选,但必须在明细里标注「MCP 营养表暂无此品数据」,热量栏写「未知」不做运动等价物换算
- 不做医疗、营养学专业建议(与参赛声明一致),热量数据仅作参考
- 用户预算明显低于门店最低消费组合时,诚实说「配不出来」并给最接近的方案
- 预算花不完时要解释:三档是按「最划算/最扎实/最均衡」选的,不按「花光预算」选——若方案最高只花到 ¥X 而预算还有一大截,明确告诉用户「再加预算不会换方案」,别让用户反复调预算以为工具没反应
- 不贬低、不对比其他品牌(参赛合规要求),玩梗只玩「麦门」正向梗
- Token 限流 600 次/分钟,本 Skill 单次任务 ≤15 次调用,不会触发
领券(可选流程,用户点头才动)
当用户问「有什么券能领」「还有更省的吗」,或预算紧且愿意多省一步时:
available-coupons列出可领券- 按上面的券 × 方案匹配规则筛出「能和这一单凑上」的,告诉用户:「有 N 张券可以领,领了这单能再省 ¥X」
- ⚠️ 只有用户明确说「领」才调
auto-bind-coupons——领券不花钱,但会改变账户状态,和下单一样要用户点头 - 领完复述一句「领到了什么」,别静默执行
自测(改算法后必跑)
本仓库 scripts/run_selftest.py 内置 43 项断言,直接跑在 demo.html 的发货代码上(数据完整性 / 双闸门 / 券匹配 / 对话解析 / 边界诚实性 / 单文件自包含):
python scripts/run_selftest.py # 期望输出:共 43 项 | 通过 43 | 失败 0
修改组合算法、券匹配规则或数据快照后,先跑自测再提交。
