把一单麦当劳寄存给未来某个时刻的自己。
你写下的一句话被封进一颗胶囊,支付完成才算封缄。等到那个时刻,餐送到,卡片打开 —— 上面是几小时(或一天)前的你,留给现在的你的那句话。
麦当劳 × 腾讯 WorkBuddy 1024 程序员节创意开发大赛参赛作品 底座:麦当劳中国 MCP(
https://mcp.mcd.cn,35 个工具,本作品真实调用其中 8 个)
| 寄存卡(封缄后) | 开封卡(到点后) |
|---|---|
| 附言遮罩,只留首字与字数 | 附言全文逐字归还 + 一句回应 |
未支付时只有一张挽留卡,没有寄存卡,更没有开封卡:
点餐已经被解决得很好了。没被解决的是**"此刻的我"和"过一会儿的我"之间的断层**:
- 现在很饿,但 20 分钟后有个会;
- 现在很想吃点好的,但今天已经不想做任何决定了;
- 今天撑得很难,但没有人会知道,包括明天早上醒来的自己。
所以这里的动作不是"预约下单",而是寄存:把此刻的一点情绪写下来,和一单餐一起交出去,让未来某个时刻的自己签收。
交付物是一张卡,不是一个订单号。
- 被日程切碎的上班族:想好好吃一顿,但 20 分钟后有个会 —— 把这一单寄到会议结束之后。
- 一个人扛事的人:今天很难,但没人会知道 —— 写一句话寄存出去,让几小时后的自己签收。
- 只接受确定时刻的人:不接受"大概半小时到",只接受"18: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 子进程)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 天":这是产品的叙事上限,不是接口承诺。实测服务端只回今天和明天两天。返回两天就只给两天,不虚构第三天 —— 一个能编出档期的预约系统,用户试两次就会发现。
create-order 成功 ≠ 已支付 ≠ 会送达。官方的返回文案自己就说了:
支付完成后请回复'支付完成'或'支付失败',会帮您查询最新的订单状态
状态机只认 query-order 的状态文本:
| 观测到 | 状态 | 出什么卡 |
|---|---|---|
| 待支付 / 未支付 | UNPAID |
挽留卡("胶囊未封缄,请完成支付") |
| 已支付 / 已接单 / 制作中 | SEALED |
寄存卡(附言遮罩) |
| 已完成 / 已送达 / 已取餐 | OPENED |
开封卡(附言全文) |
| 已取消 / 已退款 | CANCELLED |
失联卡 |
| 其它一切 | UNKNOWN |
什么都不出 |
最后一行是刻意的。状态枚举没有穷举过,任何"看起来像已支付"的猜测,都会让用户收到一张写着"已封缄"的假卡 —— 而假卡比没有卡严重得多,它会让用户以为自己已经付过钱。
同理,开封要两个条件同时成立:orderStatus 已送达 且 已到 open_at。门店提前做好了不算开封 —— 提前开封等于提前剧透。
{ "remark": "请随餐附一张卡片,谢谢" }11 个字,在 50 字上限里。备注是给门店看的操作指令,不是文案位。
附言全长文案只活在卡片里。两个理由:一是 remark 上限 50 字,一张卡的情绪装不进去;二是备注会被门店打印在流水单上,一句"这周你撑得比你以为的好"出现在后厨小票上,这件事的荒谬程度超出产品边界。
另外坦白一点:到店自取场景,官方只推荐在外送(orderType=2)时引导填 remark。所以纸质卡片按最佳努力处理 —— 能附上最好,附不上不影响交付。主交付物永远是数字卡片,我们不把"门店真的贴了一张纸"写进用户预期。
任何人接上 MCP 都能做出"帮你预约一单"。做不出来的是在正确的时刻说出正确的那句话。
所以附言不从零写。references/epilogue-library.md 给了 7 × 3 × 3 = 63 格 的索引:星期几 × 时段 × 情绪。
硬撑 HOLD |
讨赏 PRAISE |
松绑 RELEASE |
|
|---|---|---|---|
| 周一 晚 | 最硬的一天结束了,你还站着。 | 周一没把你按倒,值得一份薯条。 | 今天到此为止,剩下的明天再说。 |
| 周三 午 | 最没劲的那个下午,你在里面。 | 你把无聊的活干完了,这最难。 | 今天不需要有意义。 |
| 周五 晚 | 这周结束了,你从头撑到尾。 | 这周你撑得比你以为的好。 | 周五晚上不属于任何人,包括老板。 |
| 周日 晚 | 周日的晚上最难,我知道。 | 你休息好了,明天就有本钱。 | 明天的事,明天的人去扛。 |
(全 63 格 + 10 条完整双卡示例见库文件。)
空白输入框有道德压力 —— 让用户自己写,多数人会卡住,然后放弃。给三条候选、允许改几个字、再留一个"都不像"的出口,转化率完全不是一回事。
"寄给别人"是降级副线:代点 + 附言,走同一套链路。不承诺匿名、惊喜、匿名投递、指定时刻送达 —— 我们手上只有门店的取餐码,没有对方的门口。承诺了就是骗人,而被骗过一次的用户不会来第二次。
这是这个项目对品牌方最实在的地方。预约单不是"延迟的即时单",它是需求从"猜测"变成"排布"。
即时单的形状是脉冲:11:50–12:20 三十分钟内涌进一天里最大的一波,后厨按峰值配人、配料、预热设备,峰值过后产能空转。
预约单在下单那一刻就把时间戳锁死了。门店在上午就能看到"今天 18:30 有 12 单、19:00 有 8 单",按 15–30 分钟粒度排产、分批解冻、错峰预热。峰值不会被削掉(该来的还是会来),但峰值变得可预测,而可预测的峰值才谈得上调度。
这一条是结构性的:能预约的时段,往往就是门店的非峰值时段。
实测档期原文里,夜市从 17:14 才开始放,下午茶是 14:44–16:45 —— 正是门店客流最低、产能最富余的两个窗口。信使只在这两个窗口里让用户选时刻,等于把冲动型消费导流到产能空档,而不是往 12:00 的队列里再塞十单。
对品牌这是纯增量:用户本来会在 20:00 随口点一单,现在提前两小时锁在 18:30,时段没变、客单没降,但后厨多了一次提前量。
create-order 之后必须完成支付才封缄,未支付只有挽留卡。这比"先占位后付款"的预约模型少了一整类空单 —— 排产最怕的不是单多,是排了却没人来。封缄绑支付,等于排产表里的每一格都是已收钱的确定性需求。
信使不新建任何履约能力:不要求门店为它延长营业、不要求额外人手、不做"专属时段"。它只在 reservationTimeOptions 给出的既有产能里选点,本质是把已经空着的产能标价出售。
每颗胶囊都记着 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。