Skip to content

About

把一单麦当劳寄存给未来的自己 | 麦当劳 MCP 创意开发大赛参赛作品

Resources

Stars

55 stars

Watchers

0 watching

Forks

Repository files navigation

麦麦时间信使 · McTime Capsule

把一单麦当劳寄存给未来某个时刻的自己。

你写下的一句话被封进一颗胶囊,支付完成才算封缄。等到那个时刻,餐送到,卡片打开 —— 上面是几小时(或一天)前的你,留给现在的你的那句话。

麦当劳 × 腾讯 WorkBuddy 1024 程序员节创意开发大赛参赛作品 底座:麦当劳中国 MCP(https://mcp.mcd.cn,35 个工具,本作品真实调用其中 8 个)

寄存卡(封缄后) 开封卡(到点后)
寄存卡 开封卡
附言遮罩,只留首字与字数 附言全文逐字归还 + 一句回应

未支付时只有一张挽留卡,没有寄存卡,更没有开封卡:

挽留卡


一、它解决的不是"点餐"

点餐已经被解决得很好了。没被解决的是**"此刻的我"和"过一会儿的我"之间的断层**:

  • 现在很饿,但 20 分钟后有个会;
  • 现在很想吃点好的,但今天已经不想做任何决定了;
  • 今天撑得很难,但没有人会知道,包括明天早上醒来的自己。

所以这里的动作不是"预约下单",而是寄存:把此刻的一点情绪写下来,和一单餐一起交出去,让未来某个时刻的自己签收。

交付物是一张卡,不是一个订单号。

它给谁用

  • 被日程切碎的上班族:想好好吃一顿,但 20 分钟后有个会 —— 把这一单寄到会议结束之后。
  • 一个人扛事的人:今天很难,但没人会知道 —— 写一句话寄存出去,让几小时后的自己签收。
  • 只接受确定时刻的人:不接受"大概半小时到",只接受"18:30 我下楼取"。
  • 每周有固定节奏的人:周五下班必吃一顿 —— 用周期承诺配好一次,之后每周自动起单。

不适合:想要即时送达的、想匿名给陌生人送礼的、想用积分抵扣的(本项目显式不做)。

二、安装与 30 秒跑通

export MCD_MCP_TOKEN=你的_token          # 在 open.mcd.cn 手机号登录后激活获取

# 1. 拿门店与真实档期(深圳 · 科技园,服务器时间 2026-10-22 20:50)
node scripts/mcp-client.mjs call query-nearby-stores examples/args-stores.json > stores.json
node scripts/slots.mjs --file examples/stores-shenzhen.json --now "2026-10-22 20:50"

真实输出(不是示意):

这几个时刻可以寄存,你选一个:

A. 周四 今天 22:30 · 宵夜(10/22)
B. 周五 明天 07:30 · 早餐(10/23)
C. 周五 明天 08:00 · 早餐(10/23)
D. 周五 明天 11:00 · 午餐(10/23)
E. 周五 明天 11:30 · 午餐(10/23)
F. 周五 明天 15:00 · 下午茶(10/23)

每个时刻都是这家店真实放出的可预约档期,不在列表里的时间门店接不了单。
# 2. 选附言(三天坐标自动出三条候选)
node scripts/pick-epilogue.mjs --dow 周五 --slot 晚 --mood PRAISE
#   FRI-EVE-PRAISE-01  这周你撑得比你以为的好。  (12 字 → 这●●●●●●●●●●●)
#   FRI-EVE-PRAISE-02  这周最好的那个你,你自己可能没看见。

# 3. 看二次确认清单 + create-order 载荷(不会下单,只打印)
node scripts/capsule.mjs plan --capsule examples/capsule-pending.json --now "2026-10-23 17:35"

# 4. 支付完成后,用 query-order 的状态文本推进状态机
node scripts/capsule.mjs seal --capsule examples/capsule-pending.json --status "已支付" --now "2026-10-23 17:40"
#   → SEALED  已封缄(命中「已支付」) / 卡片:sealed → docs/cards/....svg

# 5. 到点且送达才开封
node scripts/capsule.mjs open --capsule examples/capsule-pending.json --status "已完成" --now "2026-10-23 18:30"

跑测试(21 条,全部针对硬约束):

node tests/capsule.test.mjs     # 不要用 node --test(它会 spawn 子进程)

三、五个不可让步的设计决定

1. 用户不能自由填时间

reservationTimeOptions 是档期的唯一来源。门店给什么,就只能选什么:

{ "date": "2026-10-23", "today": false,
  "reservationOptionText": "早餐(07:14至10:15),午餐(10:44至14:15),下午茶(14:44至16:45),夜市(17:14至21:45),宵夜(22:14至22:46)" }

scripts/slots.mjs 把每个时段切成 30 分钟网格的可选时刻([开始+15, 结束-15] 内,距现在 ≥ 45 分钟),输出 A/B/C 标号。措辞固定是**「这几个时刻可以寄存,你选一个」** —— 不是「你想寄给几点」。

用户自己报的时间有极大概率落在门店不营业的时段,那个报错发生在 create-order,对用户来说就是"付不了款"。把选择器做在前面,是唯一不浪费用户耐心的做法。

关于"7 天":这是产品的叙事上限,不是接口承诺。实测服务端只回今天和明天两天。返回两天就只给两天,不虚构第三天 —— 一个能编出档期的预约系统,用户试两次就会发现。

2. 封缄绑支付,不绑下单

create-order 成功 ≠ 已支付 ≠ 会送达。官方的返回文案自己就说了:

支付完成后请回复'支付完成'或'支付失败',会帮您查询最新的订单状态

状态机只认 query-order 的状态文本:

观测到 状态 出什么卡
待支付 / 未支付 UNPAID 挽留卡("胶囊未封缄,请完成支付")
已支付 / 已接单 / 制作中 SEALED 寄存卡(附言遮罩)
已完成 / 已送达 / 已取餐 OPENED 开封卡(附言全文)
已取消 / 已退款 CANCELLED 失联卡
其它一切 UNKNOWN 什么都不出

最后一行是刻意的。状态枚举没有穷举过,任何"看起来像已支付"的猜测,都会让用户收到一张写着"已封缄"的假卡 —— 而假卡比没有卡严重得多,它会让用户以为自己已经付过钱。

同理,开封要两个条件同时成立:orderStatus 已送达 且 已到 open_at。门店提前做好了不算开封 —— 提前开封等于提前剧透。

3. 备注只放门店能执行的指令

{ "remark": "请随餐附一张卡片,谢谢" }

11 个字,在 50 字上限里。备注是给门店看的操作指令,不是文案位。

附言全长文案只活在卡片里。两个理由:一是 remark 上限 50 字,一张卡的情绪装不进去;二是备注会被门店打印在流水单上,一句"这周你撑得比你以为的好"出现在后厨小票上,这件事的荒谬程度超出产品边界。

另外坦白一点:到店自取场景,官方只推荐在外送(orderType=2)时引导填 remark。所以纸质卡片按最佳努力处理 —— 能附上最好,附不上不影响交付。主交付物永远是数字卡片,我们不把"门店真的贴了一张纸"写进用户预期。

4. 护城河是文案库和卡片,不是预约机制

任何人接上 MCP 都能做出"帮你预约一单"。做不出来的是在正确的时刻说出正确的那句话。

所以附言不从零写。references/epilogue-library.md 给了 7 × 3 × 3 = 63 格 的索引:星期几 × 时段 × 情绪。

硬撑 HOLD 讨赏 PRAISE 松绑 RELEASE
周一 晚 最硬的一天结束了,你还站着。 周一没把你按倒,值得一份薯条。 今天到此为止,剩下的明天再说。
周三 午 最没劲的那个下午,你在里面。 你把无聊的活干完了,这最难。 今天不需要有意义。
周五 晚 这周结束了,你从头撑到尾。 这周你撑得比你以为的好。 周五晚上不属于任何人,包括老板。
周日 晚 周日的晚上最难,我知道。 你休息好了,明天就有本钱。 明天的事,明天的人去扛。

(全 63 格 + 10 条完整双卡示例见库文件。)

空白输入框有道德压力 —— 让用户自己写,多数人会卡住,然后放弃。给三条候选、允许改几个字、再留一个"都不像"的出口,转化率完全不是一回事。

5. 主叙事只服务"寄给未来的自己"

"寄给别人"是降级副线:代点 + 附言,走同一套链路。不承诺匿名、惊喜、匿名投递、指定时刻送达 —— 我们手上只有门店的取餐码,没有对方的门口。承诺了就是骗人,而被骗过一次的用户不会来第二次。


四、对门店排产的价值

这是这个项目对品牌方最实在的地方。预约单不是"延迟的即时单",它是需求从"猜测"变成"排布"。

1. 把脉冲需求摊成可规划的曲线

即时单的形状是脉冲:11:50–12:20 三十分钟内涌进一天里最大的一波,后厨按峰值配人、配料、预热设备,峰值过后产能空转。

预约单在下单那一刻就把时间戳锁死了。门店在上午就能看到"今天 18:30 有 12 单、19:00 有 8 单",按 15–30 分钟粒度排产、分批解冻、错峰预热。峰值不会被削掉(该来的还是会来),但峰值变得可预测,而可预测的峰值才谈得上调度。

2. 寄存行为天然填谷,不抢峰

这一条是结构性的:能预约的时段,往往就是门店的非峰值时段。

实测档期原文里,夜市从 17:14 才开始放,下午茶是 14:44–16:45 —— 正是门店客流最低、产能最富余的两个窗口。信使只在这两个窗口里让用户选时刻,等于把冲动型消费导流到产能空档,而不是往 12:00 的队列里再塞十单。

对品牌这是纯增量:用户本来会在 20:00 随口点一单,现在提前两小时锁在 18:30,时段没变、客单没降,但后厨多了一次提前量。

3. 支付前置降低了弃单

create-order 之后必须完成支付才封缄,未支付只有挽留卡。这比"先占位后付款"的预约模型少了一整类空单 —— 排产最怕的不是单多,是排了却没人来。封缄绑支付,等于排产表里的每一格都是已收钱的确定性需求。

4. 不向门店额外索取运力

信使不新建任何履约能力:不要求门店为它延长营业、不要求额外人手、不做"专属时段"。它只在 reservationTimeOptions 给出的既有产能里选点,本质是把已经空着的产能标价出售。

5. 顺带产出一份"时段偏好"数据

每颗胶囊都记着 slot_label(早餐/午餐/下午茶/夜市/宵夜)、dow_of_open(星期几)、open_at(具体时刻)。攒够样本后可以直接回答门店最关心的那个问题:哪个人群愿意为"确定的时刻"提前多久下单。这是即时单永远拿不到的信息。


五、架构

                     ┌──────────────────────────────┐
  now-time-info ────►│                              │
  query-nearby-      │   S1 选档期(A/B/C 选择器)   │
    stores ─────────►│   S2 选信物(套餐 + roundList)│
  query-meals ──────►│   S3 写附言(7×3×3 模板)     │
  query-meal-detail ►│   S4 二次确认(全量清单)      │
  calculate-price ──►│   S5 create-order(未封缄)    │
                     │        │                      │
                     │        ├─ 未支付 ─► 挽留卡     │
                     │        └─ 已支付 ─► 封缄       │
  query-order ──────►│   S6 寄存卡(附言遮罩)        │
                     │   S7 到点 + 送达 → 开封卡      │
                     └──────────────────────────────┘
文件 作用
MCP_INTEGRATION.md 实际使用的 MCP Server / Tool / 调用流程 / 业务价值(参赛必交)
CONTEST_DECLARATION.md 参赛声明(官方原文,未做任何改动)
SKILL.md Skill 主文件:五条硬规则、八个状态、工具编排、异常分支
references/mcp-contract.md 麦当劳 MCP 实测契约(字段级坑位,含真实 traceId)
references/state-machine.md 状态转移表、封缄判定表、幂等与重入
references/epilogue-library.md 附言库:63 格索引 + 10 条双卡示例 + 生成规则 + 红线词表
references/card-spec.md 两张卡的字段定义、遮罩算法、呼应规则 R1–R7、落盘 schema
scripts/slots.mjs 档期 → 可寄存时刻(确定性、可测)
scripts/pick-epilogue.mjs 三维坐标 → 候选文案
scripts/seal.mjs 状态文本 → 状态机状态 → 卡片类型
scripts/cards.mjs SVG 卡片渲染(零依赖)
scripts/capsule.mjs 编排 CLI + 二次确认 + 订单载荷
scripts/mcp-client.mjs 最小 Streamable-HTTP MCP 客户端(零依赖)
tests/capsule.test.mjs 21 条测试,直接编码验收标准

六、两张卡与呼应规则

寄存卡和开封卡共用同一个 capsule_id —— 它们不是两张图,是同一个胶囊的两个时刻。

寄存卡 sealed 开封卡 opened
时机 支付完成 到点 + 已送达
附言 遮罩:首字 + ●(这周你撑得比你以为的好。 → 这●●●●●●●●●●●) 全文,逐字原样
时态 将来时("18:30 开封") 现在时("现在 18:30,隔了 50 分钟")
作用 留下一个未闭合的问题 只确认,不打分
落盘 sealed_at 只写一次 opened_at 只写一次,幂等

七条呼应规则(R1–R7)在 references/card-spec.md,tests/ 里有断言兜底,其中最硬的三条:

  • R1 开封卡的附言必须与原文全等,一个标点都不改;
  • R3 寄存卡不得出现附言全文,也不得出现能反推原文的提示;
  • R6 禁词:免费 中奖 惊喜 神秘 匿名 保证送达 加油 你做到了。

七、周期承诺(加分项)

"以后每周五 17 点都这么来一单。"

落成 commitments/<id>.json,每周用 now-time-info 判定是否命中:

node scripts/capsule.mjs due --commitments commitments.json --now "2026-10-30 18:30"
# - 周五的固定寄存:周五 18:30 · 精选单人餐三件套
#   → 生成待确认预约单,仍然要二次确认,不静默下单。

周期承诺省的是"重新想一遍",不是"替你花钱"。二次确认这一步在任何模式下都不删。

八、实测记录

2026-10-22 20:50(GMT+8),深圳 · 科技园:

调用 结果
now-time-info 2026-10-22 20:50:37 / THURSDAY / GMT+08:00
query-nearby-stores(searchType=1) 600050 收藏餐厅为空 → 转关键词搜索
query-nearby-stores(只传 city) 600058 城市名或者关键词不能为空
query-nearby-stores(city + keyword) 5 家店,全部 reservation=true,档期只到 10/23
calculate-price(巨无霸×1,无预约) price=2550(分)= ¥25.5,takeWayList=[eat-in, locker-out]
calculate-price(reservationDate=2026-10-23 03:30) 60003 门店暂停营业 → 该时刻不成立
calculate-price(三件套 + 3 个 roundList + 18:00 预约) price=3450 = ¥34.5,档期成立
query-order / order-status 枚举 未验证(测试账号无历史订单),故按 UNKNOWN 兜底

原始响应存在 examples/,字段级坑位见 references/mcp-contract.md。

九、这个项目不做什么

  • 不做冷静期、不做劝退消费。 用户想要就下单,我们只负责把它寄到未来。跟品牌利益对着干的产品活不到第二次迭代。
  • 不做营养评分、不做热量说教。 那个时刻用户需要的是被承认,不是被考核。
  • 不承诺匿名惊喜投递。 没有那个能力。
  • 不编档期、不编状态、不编时长。 卡片上每个数字都必须能追到一次工具返回。
  • 不在未支付时出卡。 一次都不行。

十、许可与报名

MIT。参赛信息见 docs/registration-issue.md。

本项目所有餐品、价格、档期与门店数据均来自麦当劳 MCP 实时返回;餐品供应与价格请以麦当劳官方渠道为准。 下单是真实消费动作,本项目不会在未经用户明确确认的情况下调用 create-order。

About

把一单麦当劳寄存给未来的自己 | 麦当劳 MCP 创意开发大赛参赛作品

Resources

Stars

55 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages