基于麦当劳中国 MCP 打造的点餐成本助手,做两件事:
短期 —— 这一单怎么点最省
- 拉取当前门店的实时菜单与价格
- 拉取你账户里当前门店可用的优惠券
- 枚举「单品 / 套餐 / 券组合 / 加购凑单」的候选方案
- 用 MCP 的
calculate-price逐方案真实计价(而不是猜) - 输出最省钱的方案,以及为什么不推荐更便宜但吃不饱的选项
长期 —— 你白扔了多少钱
- 券的沉默成本:你手里有几张券即将过期?那就是正在流失的钱
- 消费习惯画像:近 N 个月花了多少、客单价是否偏高、哪些单品该改成套餐
- 数据来自
order-list历史订单 +query-my-coupons券存量
| 🍟 一次点餐平均省 | 💸 沉默成本发现 | 🧮 价格来源 | 🔑 使用门槛 |
|---|---|---|---|
| 3 ~ 13 元 | 一次几十元 | MCP 真实计价 | 无需 Token 即可跑测试 |
快速体验(30 秒)
git clone https://github.com/Ayzzz123/mcd-coupon-strategist.git
cd mcd-coupon-strategist
python demo.py零第三方依赖(只用标准库)、不需要 MCP Token、不联网。
demo.py 读取 fixtures/ 里录制的真实 MCP 响应,跑通两条链路:
链路 A · 单次省钱
门店:麦当劳南京东城汇餐厅(1990622)(到店自取)
需求:想吃「汉堡 + 薯条 + 可乐」,预算 40 元
推荐方案
组合:套餐:麦辣鸡腿汉堡三件套
实付 33.5 元
超预算(仅供对照)
· 单点:麦辣鸡腿汉堡 + 薯条 + 可乐 —— 实付 46.5 元,比推荐方案贵 13.0 元
→ 这个价差来自套餐结构本身,不需要任何优惠券 —— 这是最稳的省法。
链路 B · 消费审计
券:当前门店可用 2 张,卡包里持有 9 张 —— 沉默成本按后者算
【一】券的沉默成本
你的券有风险:9 张将在 7 天内陆续过期(面值 21.1 元);
另有 7 张的券面价值无法从菜单推算,未计入面值。
优先处理这几张:
· 大脆鸡扒堡早餐两件套 价值未知(菜单中无对应商品,未估算) 还剩 1 天
仅 05:00-10:29 可用 · 到店专用
· 中薯条买一送一 省 14.5 元 还剩 2 天
· 王牌小食拼 省 6.6 元 还剩 2 天
…… 另有 6 张(为控制篇幅未展开)
【二】消费习惯
订单数:8 单(其中 2 笔 0 元)
总支出:103.5 元
客单价:17.25 元(按 6 笔有支出的订单计算)
时段分布:
早餐 · 4 单(2 笔有支出) · 支出 58.9 元 · 样本不足 3 笔,未估算客单价
下午 · 1 单 · 支出 1.0 元 · 样本不足 3 笔,未估算客单价
晚市 · 3 单 · 支出 43.6 元 · 客单价 14.53 元
本月最该做的事:先把 21.1 元的券用掉,
这笔钱不用就是纯损失。
另外 7 张券同样要过期,但券面价值还没算出来 —— 看清它们的时间和限制,别让它们悄悄走掉。
券包体检
名义总额 ¥21.1(9 张) → 可实现 ¥21.1
【顺手可用】2 张 · 净省 ¥21.1
· 中薯条买一送一 净省 14.5 元 还剩 2 天
· 王牌小食拼 净省 6.6 元 还剩 2 天
【无法判断】7 张
· 大脆鸡扒堡早餐两件套 菜单中无对应商品,未估算 还剩 1 天
……
用券排程
窗口 2 天(券全部过期前) · 点单频率 0.089 单/天
→ 这期间你大概只会点 1 单,最多用掉 1 张券
第 1 单 → 中薯条买一送一 净赚 14.5 元
排不上(1 张 · 可实现 ¥6.6)
· 王牌小食拼 净价值 6.6 元 还剩 2 天
券包可实现 ¥21.1,但机会只够用掉 ¥14.5 —— 剩下 ¥6.6 注定随券过期浪费。
麦麦省共 7 条券记录:已领取 4 条、可领取 3 条(同名去重后)
可领取的券
你还有 3 张券没领(领取免费)
【无法判断】3 张
· 免费脆薯饼 菜单中无对应商品,未估算
· 人气麦旋风买一送一 菜单中无对应商品,未估算
· 9.9元中杯冰美式 菜单中无对应商品,未估算
3 张的价值无法从券面推算 —— 我们不猜;领取后可以用「带券计价」实测出精确价值。
另外别忘了机会有限:上面排程算出,窗口内的点单机会已被你手里的券占满 ——
券不是越多越好,用得上才算数。
活动节奏
10 月日历里有 14 天有活动:已过 2 天 · 今天 1 天 · 之后 11 天
最近一次:10月15日(明天)
· 麦当劳 X PEACEMINUSONE
说明:「有活动」不等于「更便宜」。
活动内容里的价格是营销文案,无法核实,本工具不拿它做计算。
所以这里只给时间线,值不值得等要你结合活动内容自己判断。
你手里的券撑得到 10月15日 吗(纯日期比较):
· 中薯条买一送一 10月16日到期(还剩 2 天) → 等得到
· 王牌小食拼 10月16日到期(还剩 2 天) → 等得到
积分体检
可用 376.3 积分 · 累计 581.3 · 已用 188
⚠️ 已过期 17 积分 —— 已发生的损失
积分商城 24 个商品:9 个仍在兑换期内(其中 6 个换得起),15 个已过兑换期
【现在就能换】
· 6.9元可乐麦炫酷 需 50 积分 还剩 77 天
· 3.9元圆筒冰淇淋 需 100 积分 还剩 77 天
· 16.9元2份麦辣鸡翅 需 200 积分 还剩 77 天
【同款不同价 · 换便宜的那个】
· 10.9元指定小食任选 —— 一个要 200 积分,一个只要 100 积分,差 100
【已过兑换期】15 个
这些商品的接口状态仍标着「上架」,但兑换截止日已经过去 ——
只看状态会以为还能换。
上面那 3 张全是「价值未知」不是解析失败,是数据事实:这份菜单快照采集于 21:59,而脆薯饼、冰美式并不在该时段的菜单里。我们没有为了让 demo 好看 而往菜单里塞一个能匹配上的商品名 —— 原因与验证方式见
fixtures/README.md。上面这段输出为便于阅读做了删减(省略了分隔线、以及「积分还不够」等重复项), 完整输出请直接跑
python demo.py。
真实运行时,这些响应由 MCP 实时返回,业务逻辑一字未改 ——
所以这个 demo 同时也是整套数据流水线的端到端回归测试。
数据来源与采集时间见 fixtures/README.md。
一个真实例子
同一个账号,同一个门店,两种问法:
问「今天怎么点最省」 →
推荐方案
组合:套餐:麦辣鸡腿汉堡三件套
实付 33.5 元
超预算(仅供对照)
· 单点:麦辣鸡腿汉堡 + 薯条 + 可乐 —— 实付 46.5 元,比推荐方案贵 13.0 元
同样吃到「汉堡 + 薯条 + 可乐」:套餐 33.5 元,单点 46.5 元,差 13 元 —— 而单点那条已经超出 40 元预算,所以它是对照,不是备选。
问「我最近在麦当劳花了多少」 →
【一】券的沉默成本
你的券有风险:9 张将在 7 天内陆续过期(面值 21.1 元);
另有 7 张的券面价值无法从菜单推算,未计入面值。
优先处理这几张:
· 大脆鸡扒堡早餐两件套 价值未知 还剩 1 天
仅 05:00-10:29 可用 · 到店专用
· 中薯条买一送一 省 14.5 元 还剩 2 天
· 王牌小食拼 省 6.6 元 还剩 2 天
【二】消费习惯
订单数:8 单(其中 2 笔 0 元)
总支出:103.5 元
客单价:17.25 元(按 6 笔有支出的订单计算)
洞察:
· 近 3 个月你在麦当劳共消费 103.5 元,其中 6 笔有实际支出,平均每单 17.25 元。
· 另有 2 笔 0 元订单(多为券核销或积分兑换)—— 不计入客单价,但算作一次点单机会。
· 还有 2 笔「部分退款」订单 —— 接口不返回退款金额,无法确认 realTotalAmount 是退款前还是退款后的口径,上面的支出可能略偏高。
本月最该做的事:先把 21.1 元的券用掉,这笔钱不用就是纯损失。
第二个问题的价值不在这一次的数字大小,而在于它是持续性的: 第一个问题每单只影响一次;第二个问题每次都会告诉你又有什么在悄悄过期 —— 而这是一次性工具永远给不出的答案。
注意「王牌小食拼」那个 6.6 元:这张券的标题里没有数字,菜单里也没有对应商品,
靠反查原价这条路根本走不通。它的价值是把券的商品码传给 calculate-price
实测出来的 —— 而不是估一个「差不多」的数。
这两段输出与
python demo.py的结果逐字一致 —— 你可以自己跑一遍验证。
我们做对了什么别人没做的
省钱类工具已经很多了,多到这次比赛报名里就有好几支在做同一件事。所以先说清楚我们的不同点到底在哪:
| 常见做法 | 我们的做法 | 差别 |
|---|---|---|
| 让模型估算价格 | 所有金额由 MCP calculate-price 回填 | 模型给不出真实价格,只能猜 |
| 只说「推荐买这个」 | 说清省在哪:哪张券、哪个套餐、加了什么 | 用户才能学会,而不是依赖工具 |
| 为用券鼓励凑单 | 主动检测凑单陷阱并劝退 | 凑单常常反而更贵 |
| 只看这一单 | 同时看历史订单 + 券存量 | 一次性工具看不到长期结构 |
| 券是「可用资源」 | 券是有保质期的钱 | 过期即损失,这是本项目独有的视角 |
| 积分是「攒够就换」 | 积分也是有保质期的钱,且**status 不等于可兑换** | 实测 24 个兑换品里 15 个已过兑换期,接口状态却仍标「上架」 |
| 「有券就建议用」 | 算净价值,该劝退就劝退 | 名义 ¥150 的券包,可实现的可能只有 ¥60 |
| 教你「怎么用券」 | 还提醒你**「还有券没领」** | 未领取的券是唯一一种零成本的省钱来源 |
| 用「平均客单价」一刀切 | 分清消费口径 / 点单口径、全时段 / 分时段 | 口径错,后面所有券的判定都会跟着偏 |
| 券 = 「门店里能用的」 | 券分两种口径:门店可用的 vs 卡包里持有的 | 实测差 2 张 vs 9 张 —— 拿门店券算沉默成本,会把 7 张即将过期的早餐券整个漏掉 |
| 拿活动文案里的价格当依据 | 只取活动的日期与标题,文案价格一概不用 | 营销文案无法核实,拿它算钱等于编 |
| —— | 券的沉默成本 | 别处见不到这个指标 |
差异化内核:别处都在帮用户花钱,我们在帮用户止损。 下单前省的那几块钱是「收益」,快过期的券是「损失」—— 人对损失的敏感度是对收益的两倍,而这个数字从来没人给他算过。
关键实现:怎么知道一张券真值多少钱
「券的沉默成本」要成立,前提是得知道每张券真实值多少钱。而 MCP 返回的券通常只有名字,没有面值:
「中薯条买一送一」 → 值多少钱?
「9.9元中杯冰美式」 → 值多少钱?
「王牌小食拼」 → 值多少钱?
首选:直接问收银台。 把券自带的商品码连同 couponId 传给 calculate-price,
返回的 discount 就是这张券的精确价值,price 就是用券实付:
| 券 | 实测结果 |
|---|---|
| 中薯条买一送一 | 14.5 元 |
| 王牌小食拼 | 6.6 元 |
兜底:从菜单反查原价(拿不到券的商品码时用):
| 券形态 | 价值推算 | 本例结果 |
|---|---|---|
满减券 满50减12 | 直接取减免额 | 12.0 元 |
买一送一 中薯条买一送一 | 取该商品菜单单价 | 14.5 元 |
免费券 免费脆薯饼 | 取该商品菜单单价(券面即免费) | 脆薯饼单价 |
固定价券 9.9元中杯冰美式 | 菜单原价 − 固定价 | 23.0 − 9.9 = 13.1 元 |
为什么首选实测:「王牌小食拼」标题里没有数字、菜单里也没有对应商品 —— 反查只能返回 0(价值未知),而它实际值 6.6 元。 反查不只是可能低估,还会把真实存在的价值报成 0。
两条路都走不通时,一律取 0 并在报告中标注「价值未知」, 绝不估一个「看起来合理」的数。宁可少报,不可编造。
前提:把「客单价」的口径搞对
后面的券包体检和用券排程都依赖同一个数:你的「自然消费基准」 —— 不看券的话,你本来会花多少钱。它取自历史订单的客单价。
而这个数很容易算错。真实订单里有两类订单不该按「一单消费」来算:
| 订单类型 | 怎么处理 |
|---|---|
| 整单退款 | 直接过滤,不计入任何统计 |
| 部分退款 | 退款金额接口不提供 → 保持计入,但如实提示「支出可能略偏高」,不猜退款额 |
| 0 元订单 | 多为券核销 / 积分兑换 → 不计入客单价,但仍算一次点单机会 |
最后一条是关键:0 元订单只在有券时才会出现,拿它当「不看券时你会花多少」是 循环论证;而且它会把基准往下拉,让所有券都显得更难用上。
真实数据里,仅这一处口径修正就让客单价从 12.94 元变成 17.25 元, 结论随之从「两张券都得凑单」变成「两张都顺手可用」, 可实现金额从 ¥17.58 变成 ¥21.1。
于是本项目区分两个口径 —— 混用会让结论系统性失真:
- 消费口径(算钱、当基准):只算有实际支出的订单 →
avg_per_order - 点单口径(算机会):全部订单都算 → 推算「券过期前你还会点几单」
更进一步:券包体检
知道「你正在白扔多少钱」之后,下一个问题自然是:这笔钱该怎么不白扔?
答案取决于一个反直觉的事实 —— 券的名义面值,从来不是它对你的真实价值:
净价值 = 券面价值 − 为用这张券、你本来不会花的那部分钱
一张「满50减12」的券,如果客单价是 13 元 —— 为了用它得点到 50 元, 名义省 12,实际多花 37,净亏 25。
按这个公式,券包会被切成四类:
| 类别 | 判定条件 | 建议 |
|---|---|---|
| 顺手可用 | 你本来就会花到门槛 | 直接用,净价值 = 券面价值 |
| 努一把 | 差额 < 券面价值 | 凑单后仍净赚,标出净赚多少 |
| 纸面富贵 | 差额 ≥ 券面价值 | 明确劝退,算出硬用会亏多少 |
| 无法判断 | 券面反查不到原价 | 如实标注,不估算 |
最后汇总成一句可以直接行动的话:
券包名义 ¥150 → 按你的消费习惯只拿得到 ¥60。
第三类是全篇最反直觉的地方 —— 别的工具都在说「你有券,快去用」, 我们说「这几张别用」。这与「别处帮用户花钱,我们帮用户止损」的立场一致。
⚠️ 判定用的「自然消费基准」取自历史订单的客单价,是一个保守近似、 而非精确值。这个假设写在报告里,不藏在代码里。
再进一步:用券排程
「可实现 ¥60」有个隐含前提 —— 这些券你都有机会用掉。 但券会过期,而你点单的次数有限。
于是有了第二层:先推算「券全部过期前你还会点几单」,再在这个约束下分配券。
窗口 20 天 · 点单频率 0.09 单/天
→ 这期间你大概只会点 1 单,最多用掉 1 张券
建议安排
第 1 单 → 满20减12 净赚 5.0 元
排不上(1 张 · 可实现 ¥5.0)
· 免费脆薯饼 净价值 5.0 元 还剩 20 天
券包可实现 ¥10.0,但机会只够用掉 ¥5.0 —— 剩下 ¥5.0 注定随券过期浪费。
「可实现的券,未必用得上」 —— 这是比「券面价值加总」更硬的一层。 大多数省钱工具连第一层都没做,这一层基本没人碰。
三个假设都写在代码注释里,不藏着:你在窗口内本来就会点 K 单、 每单最多用 1 张券、用券不改变你的点单次数。
最后一层:还没领的券
前面三步看的都是你已经领到手的券。但你的券源不止这些 ——
available-coupons 会同时返回「已领取」和还没领的券:
麦麦省共 7 条券记录:已领取 4 条、可领取 3 条
可领取:免费脆薯饼 / 人气麦旋风买一送一 / 9.9元中杯冰美式
别的工具都在帮你「用」券,没人提醒你「还有券没领」。 未领取的券是唯一一种零成本的省钱来源,所以我们把它也做进了体检 —— 沿用同一套净价值框架,但问的是不同的问题:
| 券包体检问 | 可领取券问 | 类别文案 |
|---|---|---|
| 这张券该不该用 | 这张券该不该领 | 领了就能用 / 领了要凑一点 / 领了也用不上 |
「领了也用不上」是这里最反直觉的一类:领券本身免费, 但你为了用它而消费不是 —— 这跟券包体检是同一套标准。
还有一处必须诚实的地方:这批券拿不到 couponId(工具不返回),
所以上一节那套「带券计价实测」走不通,只能靠菜单反查;
反查不到时如实标「价值未知」,不猜。如上面 demo 输出的 3 张全是未知 ——
原因是那份菜单快照采集于 21:59,脆薯饼与冰美式并不在该时段的菜单里。
⚠️ 不擅自替用户领券。 领取(
auto-bind-coupons)会改动账号状态, 必须先取得用户明确同意。工具负责把「有哪些没领、值不值」摆清楚, 领不领由用户决定。
另外,这批券没有有效期,所以它们不进用券排程(排程的窗口天数是靠有效期算的)。 渲染时也刻意不输出剩余天数 —— 把「未知」说成「无期限」就是编造。
目标用户
- 学生党 / 打工人:预算固定,希望同样吃饱的前提下花得更少
- 持券用户:手里攒了一堆券,但不知道哪张快过期、哪张该用
- 选择困难用户:不想在菜单里反复横跳,希望直接得到一个明确答案
- 想知道自己花了多少钱的人:从没算过在麦当劳的月度开销
核心能力
| 能力 | 说明 |
|---|---|
| 门店菜单感知 | 价格与供应状态随门店实时变化,不写死菜单 |
| 券面价值解析 | 从菜单反查原价,算出这张券真实值多少钱(买一送一 = 商品单价;9.9元券 = 原价 − 9.9) |
| 方案枚举计价 | 所有候选方案都经 MCP 真实计价,不复用历史价格 |
| 省钱归因 | 明确告诉用户「省在哪」:用了哪张券、换成了哪个套餐、加了什么凑单品 |
| 反凑单陷阱 | 当「为用券而加购」反而更贵时,主动指出并给出更优解 |
| 券的沉默成本 | 量化即将过期的券面值 —— 那是正在流失的钱 |
| 消费习惯画像 | 从历史订单还原月度支出、客单价、高频单品,给出结构性省钱建议 |
| 时段口径 | 菜单分时段供应,所以「几点点的」也是消费结构。样本不足 3 笔的时段只报单数,不给客单价 |
| 券包体检 | 算出每张券的净价值,明确告诉你哪几张别用:名义 ¥150,可实现的可能只有 ¥60 |
| 用券排程 | 可实现的券未必用得上 —— 在「券过期前你还会点几单」的约束下分配券,点明注定浪费多少 |
| 可领取券发现 | 麦麦省里还没领的券。别的工具帮你用券,我们提醒你还有券没领,并判断哪张值得领 |
| 积分体检 | 积分是另一种有保质期的钱:余额、已过期、能换什么、够不够,以及同一件商品哪个 SPU 更省积分 |
| 活动节奏 | 活动时间线 × 券的有效期:回答「等活动再买」等不等得起。只做纯日期比较,不拿营销文案里的价格说话 |
快速开始
1. 申请麦当劳 MCP Token
前往麦当劳 MCP 开放平台 https://open.mcd.cn/mcp ,使用手机号登录后在控制台激活并复制 MCP Token。
2. 配置 MCP 连接
本项目提供脱敏示例配置 mcp-config.example.json:
{
"mcpServers": {
"mcd-mcp": {
"type": "streamablehttp",
"url": "https://mcp.mcd.cn",
"headers": {
"Authorization": "Bearer ${MCD_MCP_TOKEN}"
}
}
}
}将 ${MCD_MCP_TOKEN} 替换为你自己的 Token 后,写入 MCP Client 的配置。请勿将真实 Token 提交到任何公开仓库。
3. 在 WorkBuddy 中启用
- 打开 WorkBuddy,左侧边栏进入【专家 · 技能 · 连接器】→【连接器】页签
- 点击右上角【自定义连接器】→【配置 MCP】
- 粘贴上面的 JSON(替换 Token),点击【保存】
- 回到【自定义连接器】,将
mcd-mcp设为【启用】
4. 开始使用
直接对话即可,例如:
我在麦当劳点外卖,门店选好之后帮我算一下:
我手里有券,想吃一个汉堡 + 一份薯条 + 一杯饮料,预算 40 元,
怎么点最省钱?给我两个方案对比。
使用示例
示例一:预算内吃饱
你:预算 35 元,想吃鸡类主食 + 一个配餐,帮我选。
助手:已查询当前门店菜单与你名下可用券,为你计价了 3 个候选方案:
方案 组合 实付 预算 35 元 ✅ 推荐 麦辣鸡腿汉堡三件套(汉堡 + 薯条 + 可乐) 33.5 预算内 超预算 单点:麦辣鸡腿汉堡 + 薯条 37.0 超 2 元 超预算 单点:麦辣鸡腿汉堡 + 薯条 + 可乐 46.5 超 11.5 元 只推荐三件套:33.5 元拿到三样,比只买两样的单点组合(37 元,已超预算)还便宜 3.5 元, 比同内容的单点组合省 13 元。这是套餐结构本身带来的价差,不必再叠加优惠券。
示例二:凑单陷阱提醒
你:我有一张「满 50 减 12」的券,是不是该凑单?
助手:不建议。你当前想买的商品合计 46.5 元,为触发门槛需要再加 3.5 元商品, 实付变为 46.5 + 3.5 - 12 = 38 元;而直接买三件套只要 33.5 元。 也就是说,凑单用券后反而比直接买套餐贵 4.5 元。 除非你本来就想吃那个凑单品,否则不划算。
这是一个演示场景(该账户当前没有满减券),用于说明凑单陷阱的判定逻辑。 逻辑本身由
src/test_saver.py覆盖。
以上金额均来自 MCP
calculate-price的真实返回,非估算值。
架构
两条链路共用同一套 MCP 数据底座,但回答的是完全不同的问题:
- 链路 A(短期):
saver.py—— 枚举候选组合,每个组合都交给calculate-price真实计价,输出这一单的最省方案 - 链路 B(长期):
auditor.py—— 读order-list历史订单与query-my-coupons券存量,输出结构性浪费报告
衔接两者的是 mcd_adapter.py:它负责唯一一次的「分 → 元」换算、字段映射、退款过滤,以及券面价值反查。算法层不接触原始响应,因此不会出现单位错乱的幻觉。
项目结构
mcd/
├── README.md # 本文件
├── CONTEST_DECLARATION.md # 参赛声明(官方文本,未做改动)
├── MCP_INTEGRATION.md # MCP 集成说明:Server / Tool / 调用流程 / 业务价值
├── SKILL.md # Skill 定义与工作流说明
├── mcp-config.example.json # 脱敏 MCP 配置示例(仅环境变量占位符)
├── .env.example # 环境变量占位符
├── .gitignore
├── LICENSE # MIT
├── demo.py # 零配置演示:跑通两条链路(不需要 Token)
├── assets/ # README 展示图
│ ├── banner.svg # 头图
│ ├── demo-dual-view.svg # 两种问法对比
│ ├── architecture.svg # 双链路架构
│ └── social-preview.png # GitHub 社交预览图(1280×640)
├── fixtures/ # 录制的真实 MCP 响应(供 demo 复现)
│ ├── README.md # 数据来源、采集时间与补充方法
│ ├── menu.json # query-meals
│ ├── coupons.json # query-store-coupons
│ ├── orders.json # order-list
│ ├── prices.json # calculate-price(按组合签名索引)
│ ├── coupon_probe.json # calculate-price 带券计价(实测券的精确价值)
│ ├── available_coupons.json # available-coupons(⚠️ 该工具返回 Markdown 文本)
│ ├── my_coupons.md # query-my-coupons(账户券,⚠️ Markdown 文本)
│ ├── meal_detail.json # query-meal-detail(套餐内含单品)
│ ├── points_account.json # query-my-account(积分账户)
│ ├── points_products.json # mall-points-products(积分商城)
│ └── campaign_calendar.md # campaign-calendar(⚠️ Markdown 文本,活动时间线)
├── src/
│ ├── saver.py # 单次省钱:方案枚举、需求约束与优化核心逻辑
│ ├── auditor.py # 长期画像:券的沉默成本 + 消费习惯审计
│ ├── planner.py # 券包体检 + 用券排程 + 可领取券预判
│ ├── points.py # 积分体检:余额、有效期、可兑换性(不折算金额)
│ ├── campaign.py # 活动节奏:活动时间线 × 券的有效期(纯日期比较)
│ ├── mcd_adapter.py # MCP 响应适配层(单位换算 + 字段映射)
│ ├── test_saver.py # 算法逻辑自测
│ ├── test_adapter.py # 适配层自测
│ ├── test_auditor.py # 审计模块自测(含日期临界值、时段口径)
│ ├── test_planner.py # 券包体检与排程自测(四类判定 + 机会约束)
│ ├── test_order_adapter.py # 订单/券解析自测(用真实响应片段)
│ ├── test_claimable.py # 可领取券自测(文本解析 + 不得编造有效期)
│ ├── test_points.py # 积分自测(按截止日过滤 + 不得折算成金额)
│ └── test_campaign.py # 活动日历自测(只取结构化字段 + 标签不当事实)
├── scripts/
│ └── style_check.py # 零依赖代码规范检查(CI 会跑)
├── .github/workflows/
│ └── tests.yml # CI:规范检查 + Python 3.8/3.10/3.12/3.13 跑测试与 demo
├── workbuddy.md # WorkBuddy 开发对话上下文(WorkBuddy 专项奖励用)
└── docs/
├── TOKEN_GUIDE.md # MCP Token 申请指引
└── SUBMIT_CHECKLIST.md # 报名提交检查清单
测试
python demo.py # 端到端:两条链路的完整输出
python src/test_saver.py # 单次省钱:枚举、排序、凑单陷阱
python src/test_auditor.py # 长期画像:沉默成本、消费习惯
python src/test_planner.py # 券包体检与排程:四类判定、机会约束
python src/test_adapter.py # 适配层:单位换算、响应解析
python src/test_order_adapter.py # 订单/券:退款过滤、券价值推算
python src/test_claimable.py # 可领取券:文本解析、免费券券型、不编造有效期
python src/test_points.py # 积分:按截止日过滤、同款不同积分、不折算金额
python src/test_campaign.py # 活动日历:只取结构化字段、标签不当事实全部为纯本地运行,不需要 MCP Token,也不需要安装任何第三方库。
代码规范
python scripts/style_check.py # 零依赖,只用标准库检查项:行宽 ≤ 88 列、行尾空白与制表符、文件结尾换行、所有函数的返回类型标注、
所有非双下划线函数的 docstring、未使用的导入、__all__ 与公开定义的一致性。
之所以自己写而不用 ruff / flake8:项目定位是零第三方依赖,
连开发期的 linter 依赖也不引入。CI 上会跑这个脚本,规范不达标直接失败。
CI 会在 Python 3.8 / 3.10 / 3.12 / 3.13 上自动跑这一整套(见 tests.yml)。
这个项目可能对你有用,如果……
- 🌱 你在做 MCP 相关的项目 ——
MCP_INTEGRATION.md里记录了官方文档没写、但跑通链路必需的 12 条实测调用契约(门店查询的必传参数、calculate-price的字段名、「分 vs 元」两个接口方向相反的坑),可以帮你少踩不少坑。 - 🍔 你想知道自己在外卖上花了多少 —— 链路 B 给出的是可以直接执行的结论:「你有 2 张券将在 4 天内陆续过期,面值 14.5 元」。
- 🧩 你在写 Agent / Skill 的评测 —— 这个项目是一个完整的、双链路的 MCP 编排示例,
saver.py+auditor.py+mcd_adapter.py分层清晰,可直接作为参考实现。 - 💡 你也在做麦当劳 MCP 的省钱工具 —— 欢迎对比,也欢迎来 issue 里讨论「券的沉默成本」这个视角。
如果它对你有帮助,一个 star 就是最好的反馈。 ⭐
本项目为麦当劳程序员创意开发大赛参赛作品,非麦当劳官方产品。
设计原则
- 绝不猜测价格 —— 所有金额均来自 MCP
calculate-price的返回结果。 - 宁可少报,不可编造 —— 券的价值若无法从菜单反查到原价,取 0 并在报告中标注,而不是估一个看起来合理的数。
- 省在哪要说清 —— 每个推荐都附带省钱归因,而不是只给一个数字。
- 不诱导消费 —— 为用券而凑单如果导致实付上升,必须明确提示。
- 退款不算消费,0 元订单也不算 —— 整单退款直接过滤;「部分退款」的退款金额接口不提供,所以如实提示「支出可能略偏高」而不是猜一个数;0 元订单(券核销 / 积分兑换)不计入客单价,但仍算一次点单机会。
- 信息以官方为准 —— 价格、供应状态、券的可用范围均以麦当劳官方渠道实时结果为准。
- 不擅自改动账号状态 —— 领券(
auto-bind-coupons)属写操作,必须先取得用户明确同意; 工具负责把选项摆清楚,决定权留给用户。 - 不知道就说不知道 —— 未领取的券拿不到有效期,就不输出剩余天数; 数据缺失时如实说缺失,不用「无期限」这类说法掩盖。
- 不把积分折算成金额 —— 积分没有固定汇率,它值多少取决于你换的那张券能省多少; 要换算就得先拿带券计价实测。所以积分体检只给事实(余额、有效期、够不够), 不给「约等于 ¥X」。
- 不拿营销文案做计算 —— 活动日历里的价格(「26.9 元起」)是营销文案,无法核实;
积分商品的
note接口更是明确写着「严禁从中提取任何数值用于价格计算」。 这两处都只取结构化字段,文案一律丢弃。 - 样本不足就不给数字 —— 分时段客单价只在样本达到 3 笔时才输出, 达不到就只报单数并说明。样本量支撑不起一个基准时,报出来只是噪音。
免责声明
本项目为麦当劳程序员创意开发大赛参赛作品,由参赛者独立开发,非麦当劳官方产品。 项目输出仅供参考,不构成医疗、营养或其他专业建议;餐品信息、价格及供应状态以麦当劳官方渠道的实时结果为准。
License
MIT