Skip to content

🐛 fix(mcp): 日程 MCP 工具按职责拆分并改为结构化返回 - #387

Merged
HuXiaohui424 merged 3 commits into
mainfrom
fix/mcp
Aug 28, 2026
Merged

🐛 fix(mcp): 日程 MCP 工具按职责拆分并改为结构化返回 #387
HuXiaohui424 merged 3 commits into
mainfrom
fix/mcp

Conversation

@HuXiaohui424

@HuXiaohui424 HuXiaohui424 commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

结论

  • 将原先 4 个"多用途"日程 MCP 工具(create/query/update/delete,通过 repeat / status / 定位参数组合区分行为)拆分为 9 个职责单一的工具,消除一个工具根据入参隐式分支到多条业务路径的问题。
  • schedule.query 从"自然语言语音摘要 + 结构化结果"改为只返回分类的结构化 JSON,IM 上报失败不再改变返回给模型的数据,只反映在 im_delivery 字段。
  • 周期规则字段从嵌套 repeat 对象改为顶层扁平字段,并补充严格的格式/范围校验与中文错误提示。
  • 生产输出音量 70 → 100(全量),同步 ES8311 初始音量,统一为"生产全音量、串口语音验证半音量"。
  • 希望 Reviewer 重点判断:工具拆分后的命名/入参约束是否清晰无歧义、删除/取消类工具的确认字段是否仍能防止误取消、query 去掉文本摘要后模型侧消费是否符合预期。

Refs #388

变更

  • schedule.create:仅创建一次性日程,移除 repeat 分支。
  • 新增 schedule.create_rule / schedule.update_rule / schedule.delete_rule / schedule.update_occurrence / schedule.skip_occurrence,把原先 create/update/delete 里的周期规则、未来 occurrence、跳过单次路径拆成独立工具。
  • schedule.update / schedule.delete 只按 schedule_id 操作已物化实例(含一次性与已物化周期实例),删除必须回传 expected_event / expected_start_time 确认。
  • schedule.query:新增 schedule_id / rule_id(互斥)定位查询;返回 one_time_schedules / recurring_rules / recurring_schedules / future_occurrences / exceptions 分类数组,移除 text_output 语音摘要,message 改为结构化统计。
  • 周期字段:repeat 嵌套对象改为顶层扁平参数,ParseFlat 增加 freq_type/时间/日期格式、interval_val、weekdays_mask、day_of_month、month_of_year、monthly_mode、occurrence_count 的范围校验和中文报错。
  • 音量:Runtime 生产音量 70→100,ES8311 初始音量 70→100,注释同步。
  • 文档:docs/architecture/mcp-tool-contract.md 同步工具契约(query 结构化返回、字段语义调整)。
  • 测试:同步 7 个 host 测试的工具名、入参形状与结构化返回断言。

明确未包含:

  • 未改动 schedule 领域 service/命令/仓储层(QueryScheduleCommand 等的 schedule_id/rule_id 字段沿用现有实现)。
  • 未新增/删除领域能力,仅调整 MCP 工具层编排与输入校验。
  • 未处理模型侧对结构化返回的 prompt/消费逻辑。

架构与兼容

  • 依赖方向不变(MCP 工具层 → schedule service / rule service / reminder service)。
  • MCP 工具契约变更(工具数量 4→9、query 返回结构、repeat 对象移除),属于对外工具接口的破坏性变更,需要模型侧 prompt 与调用方同步。
  • 不涉及 Port / Profile / 数据模型 / 协议变化;无持久化 schema 迁移。
  • 音量从 70 到 100 属于运行时行为变化,仅影响生产音频输出大小。

验证

  • ./scripts/run_pre_submit_checks.sh
  • 远端 CI 的工作流、格式、IM Gateway、主机测试、架构、ESP-IDF 和 CodeQL 均通过;依赖图已启用时依赖审查也通过,未启用时已记录跳过原因
  • ESP-IDF 对应 Profile 构建
  • 真机或外部服务验证(如适用)

证据:
(待补充本地/CI 输出链接)

TDD 记录

  • RED:调整前新增/改写断言(如 查询应返回结构化 JSON 而非文本摘要、启用周期能力时应注册九个日程工具、IM 上报成功应返回 submitted 状态和结构化 JSON 而非文本摘要),在旧实现返回 text_output / 仅注册 4 工具时失败。
  • GREEN:实现工具拆分、query 结构化返回与 flat 周期字段后,上述断言转绿。
  • REFACTOR:移除 SummaryOutput / FullVoiceScheduleText / VoiceScheduleEntry 等语音摘要辅助函数与不再使用的 ObjectField / StringField。

风险与回退

  • 工具契约破坏性变更:依赖 schedule MCP 工具的上层(模型 prompt、e2e、IM 消费方)需同步,未同步会出现工具名找不到或入参被拒。回退办法:回滚本分支即可恢复旧 4 工具契约,无数据迁移。
  • 删除/取消确认:schedule.delete 强制回传 expected_event/expected_start_time,模型未回传会被拒,属于更保守的防误删设计。
  • 音量 70→100:真机全音量可能偏响,需真机验证;如需回退将 Runtime 与 ES8311 常量改回 70。
  • 未覆盖场景:未验证无开始时间记录、异常时间边界(如夏令时)等边缘输入。

Review 清单

  • Issue 的验收标准已经逐项回应
  • 没有提交凭据、设备备份、用户隐私或构建产物
  • 硬件日志保留了必要的明文诊断上下文;仅替换秘密和隐私字段
  • 新增/迁移的第三方代码记录了上游 commit 和许可
  • 文档和注释与实际行为一致
  • AI 生成内容已经由作者理解、测试并承担责任

@codecov

codecov Bot commented Aug 28, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.19783% with 62 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ents/voicelife_mcp/src/tools/schedule_mcp_tools.cc 75.96% 4 Missing and 46 partials ⚠️
...oicelife_mcp/src/tools/schedule_mcp_tools_input.cc 92.54% 1 Missing and 11 partials ⚠️
@@            Coverage Diff             @@
##             main     #387      +/-   ##
==========================================
+ Coverage   90.42%   90.48%   +0.06%     
==========================================
  Files         213      216       +3     
  Lines       25707    26124     +417     
  Branches     6800     6847      +47     
==========================================
+ Hits        23246    23639     +393     
- Misses       1128     1148      +20     
- Partials     1333     1337       +4     
Flag Coverage Δ
cpp 90.48% <83.19%> (+0.06%) ⬆️
typescript 92.12% <90.74%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...voicelife_mcp/src/tools/schedule_mcp_tools_input.h 100.00% <ø> (ø)
...oicelife_mcp/src/tools/schedule_mcp_tools_input.cc 86.37% <92.54%> (+2.44%) ⬆️
...ents/voicelife_mcp/src/tools/schedule_mcp_tools.cc 82.25% <75.96%> (+2.94%) ⬆️

... and 17 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@JunLang-7 JunLang-7 changed the title fix(mcp): 日程 MCP 工具按职责拆分并改为结构化返回 🐛 fix(mcp): 日程 MCP 工具按职责拆分并改为结构化返回 Aug 28, 2026

@fennoai fennoai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本次审查覆盖了日程 MCP 工具拆分、周期规则参数解析、查询结构化输出、提醒同步以及音频运行时音量配置。当前实现有几处会造成状态或下游数据不一致的问题,详见行内评论。

已验证:与日程 MCP 相关的 8 个 host 测试目标全部通过(包括 schedule_mcp_tools_test、提醒、失败恢复、操作记录和输入解析覆盖)。完整 host 构建还会在未改动的 display/audio 头文件中因 [[maybe_unused]]-Werror=attributes 失败。

Additional findings

  • components/voicelife_mcp/src/tools/schedule_mcp_tools.cc:?: [P1] 校验跳过 occurrence 的确认事件: 工具描述和 SkipOccurrenceProperties 都要求调用方回传 expected_event 以确认目标,但这里既没有读取也没有比较该字段,缺少它或传入其他事件名称都会直接写入 skip exception。模型在使用过期的 rule_id + original_start_time 定位数据时,因缺少这道确认保护可能跳过错误的 occurrence;请像 schedule.delete 一样要求并校验查询结果中的 event
  • components/voicelife_mcp/src/tools/schedule_mcp_tools.cc:?: [P1] 提醒撤销失败时不要先取消规则: cancel_schedule_rule 在这里先执行并成功修改数据库,随后 SuspendRuleReminders 才可能失败;此时工具返回“旧提醒撤销失败”,但周期规则及其已物化实例已经被取消,且重试无法恢复这次被报告为失败的操作。旧实现是在取消规则前先暂停提醒,失败时不会改变规则状态。请恢复先暂停/确保可恢复的顺序,或在提醒失败时补偿恢复规则状态并明确报告结果。

intent.endDate = properties.value<std::string>("end_date");
intent.resultCount = result_count;
intent.schedules = *schedules_json;
intent.schedules = *one_time_json;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] 保留周期实例到 IM 查询上报

这里把 intent.schedules 改为只序列化 one_time_json,但 service.query_schedule 返回的已物化周期实例已经被放入 recurring_schedules。因此一次同时包含一次性日程和已物化周期实例的查询,模型响应仍有周期实例,而 ScheduleQueryResultIntent 上报会静默丢失这些实例,导致下游 IM 查询结果与工具结果不一致。请让上报的 schedules 包含两类已物化日程,或为上报契约增加并填充周期实例字段。

@HuXiaohui424
HuXiaohui424 merged commit 7adddfd into main Aug 28, 2026
15 checks passed
@JunLang-7
JunLang-7 deleted the fix/mcp branch August 28, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants