# TH Hotel MCP Submit Payload Mapping ## 1. 文档信息 | 项目 | 内容 | | --- | --- | | 文档版本 | 0.1 | | 日期 | 2026-07-12 | | 状态 | 已落地第一版 | | 适用范围 | `th_hotel_submit_task_results` 的 V3/P0.1 payload adapter、schema validator 和一次提交规则 | ## 2. 核心原则 `th_hotel_submit_task_results` 接收的是 SuperAgent 已经冻结的业务结果。MCP 层只负责把业务结果转换为本系统可接收的提交 payload,不重新解释业务含义,不调用 Skill,不根据字段名猜测任务类型。 目标链路: ```text SuperAgent 业务结果 -> MCP Submit Payload Adapter -> MCP Submit Schema Validator -> ReservationAiTaskIntakeService -> MCP tool result ``` 实现位置: ```text server/src/main/java/cn/nianxx/thhotel/integrations/mcp/superagent/service/SuperAgentMcpSubmitPayloadAdapter.java server/src/main/java/cn/nianxx/thhotel/integrations/mcp/superagent/service/impl/SuperAgentMcpSubmitPayloadAdapterImpl.java ``` ## 3. 支持的输入形态 | 形态 | 判断方式 | 处理方式 | | --- | --- | --- | | V3 业务根 | 同时包含 `source_message` 和 `message_events` | 先校验根结构、source message、event 必填字段和关系索引,再按 `message_events[]` 顺序映射 `source_event_index` | | V3 S10/S99 入口通知 | 同时包含 `source_message` 和 `route_code` | 校验 S10/S99 根字段和 source message 后透传给业务入站服务 | | V2 兼容任务结果 | 包含 `ai_task_results` | 保留旧 `source_message_id + ai_task_results[]` 兼容路径 | 未知根字段会在 MCP 层被拒绝,错误码为 `MCP_SUBMIT_PAYLOAD_INVALID`。 `source_message_id` 缺失属于既有基础设施输入错误,MCP adapter 不改写该错误通道;业务入站服务会返回 `MISSING_SOURCE_MESSAGE_ID`。 ## 4. source_message_id 定义 | 字段 | 规则 | | --- | --- | | `source_message.source_message_id` | V3 必填;必须来自 AgentBus 原始 payload 的外部消息 ID,对应 `platform_source_message_inbox.external_message_id` | | `source_message_id` | V2 兼容必填;同样对应 `platform_source_message_inbox.external_message_id` | | `hotel_id` | SuperAgent 正式提交不需要传;本系统按平台唯一 ACTIVE 酒店解析 | `source_message_id` 不是本系统数据库主键,也不是 `platform_source_message_inbox.id`。 ## 5. source_event_index 映射 SuperAgent 业务结果中的 `E1`、`E_CHILD_1`、`E_PARENT` 等值只是 Agent 内部事件 ID,不能直接当成本系统的 `source_event_index`。 MCP adapter 按 `message_events[]` 数组顺序生成一基数字索引: | 业务事件 ID | 数组位置 | 本系统 `source_event_index` | | --- | --- | --- | | `E_CHILD_1` | 第 1 个 event | `1` | | `E_CHILD_2` | 第 2 个 event | `2` | | `E_PARENT` | 第 3 个 event | `3` | | `E_TRACE` | 第 4 个 event | `4` | 映射规则: - `message_events[].source_event_index` 输出为数字。 - `related_source_event_index` 输出为字符串数字,例如 `"1"`。 - `parent_source_event_index` 输出为数字。 - `related_source_event_indices[]` 输出为字符串数字数组,保持原顺序。 - 悬空引用、重复引用、缺失事件 ID 都在 MCP 层拒绝。 ## 6. ai_task_results[] 兼容 item schema V2 兼容路径仍保留,`ai_task_results[]` item 以 REST 总契约为准,第一版 MCP schema 只暴露 object,但业务入站会校验核心字段。 建议 item 结构: ```json { "source_event_index": 1, "catalog_code": "S01", "skill_id": "S01_new_booking_skill", "result_type": "normal_task", "task_type": "New Booking", "task_subtype": "new_fit_reservation", "current_or_history": "current", "case_keys": { "group_code": null, "confirmation_number": null, "reservation_number": null, "block_code": null }, "relevant_message_excerpt": "Please create a new booking.", "attachments": [], "file_references": [], "context_used": {}, "extracted_fields": {}, "manual_review": null, "additional_operations": [], "idempotency_key": null } ``` 新数据优先使用 V3 业务根或 V3 S10/S99,不建议继续新增 V2 `ai_task_results[]`。 ## 7. extraction_warnings 规则 | 形态 | 规则 | | --- | --- | | V3 业务根 | `extraction_warnings` 必须存在且为数组,无警告传 `[]` | | V2 兼容 | `extraction_warnings` 可存在且为数组;缺省由业务层按空数组处理 | MCP 层不解释 warning 语义,不因为 warning 自动创建任务。 ## 8. 成功示例:Parent Split 提交给 MCP tool 的业务结果可以使用 Agent 内部事件 ID: ```json { "source_message": { "source_message_id": "mail-20260712-parent-split-001", "subject": "Parent split booking request", "from": "agent@example.test", "cc": [], "received_at": "2026-07-12T04:00:00Z", "source_channel": "Email" }, "message_events": [ { "event_type": "New Booking", "event_role": "travel_agent_request", "source_event_index": "E_CHILD_1", "current_or_history": "current", "case_keys": { "group_code": "CHILD-A", "confirmation_number": null, "reservation_number": null, "block_code": null }, "relevant_message_excerpt": "Create child group A.", "attachments": [], "file_references": [], "context_used": {}, "extracted_fields": { "booking_object_type": "Group Block" }, "manual_review": null }, { "event_type": "New Booking", "event_role": "travel_agent_request", "source_event_index": "E_CHILD_2", "current_or_history": "current", "case_keys": { "group_code": "CHILD-B", "confirmation_number": null, "reservation_number": null, "block_code": null }, "relevant_message_excerpt": "Create child group B.", "attachments": [], "file_references": [], "context_used": {}, "extracted_fields": { "booking_object_type": "Group Block" }, "manual_review": null }, { "event_type": "Cancel Allotment", "event_role": "travel_agent_request", "source_event_index": "E_PARENT", "current_or_history": "current", "case_keys": { "group_code": "PARENT", "confirmation_number": null, "reservation_number": null, "block_code": "PARENT" }, "relevant_message_excerpt": "Release parent after child split.", "attachments": [], "file_references": [], "context_used": {}, "related_event_type": "New Booking", "requires_downstream_hard_validation": true, "related_source_event_indices": ["E_CHILD_1", "E_CHILD_2"], "extracted_fields": { "relationship_type": "linked_parent_release_after_child_split", "parent_group_code": "PARENT", "cancel_scope": "entire_allotment_control_block", "parent_release_or_cancel_candidate": true, "release_reason": "parent_to_child_allocation_split", "allocation_split_from_parent": true, "child_group_codes": ["CHILD-A", "CHILD-B"] }, "manual_review": null } ], "case_candidates": [], "extraction_warnings": [], "unhandled_current_intents": [] } ``` MCP adapter 进入业务层前会把父事件关系映射为: ```json { "source_event_index": 3, "related_source_event_indices": ["1", "2"] } ``` ## 9. 成功示例:跨 Child Trace 当 Trace 覆盖完整 Parent split 的全部 Child 时,不能只绑定第一个 Child。业务结果应保留全部 Child 引用: ```json { "event_type": "Trace", "source_event_index": "E_TRACE", "related_source_event_indices": ["E_CHILD_1", "E_CHILD_2"], "extracted_fields": { "trace_subtype": "general_request" } } ``` MCP adapter 会把它映射为: ```json { "source_event_index": 4, "related_source_event_indices": ["1", "2"] } ``` ## 10. 拒绝示例 未知字段: ```json { "source_message": {}, "message_events": [], "case_candidates": [], "extraction_warnings": [], "unhandled_current_intents": [], "unexpected_root": true } ``` 返回: ```json { "isError": true, "structuredContent": { "success": false, "error": { "code": "MCP_SUBMIT_PAYLOAD_INVALID", "details": { "field": "unexpected_root" } } } } ``` 悬空关系: ```json { "related_source_event_indices": ["E_CHILD_1", "E_UNKNOWN_CHILD"] } ``` 同样返回 `MCP_SUBMIT_PAYLOAD_INVALID`,`details.field=related_source_event_indices`,不会调用业务写入 Service。 ## 11. 一次提交和失败不重试 当前 MCP endpoint 与后端业务 Service 同进程: - adapter 校验失败时,不调用 `ReservationAiTaskIntakeService`。 - adapter 校验通过后,只调用一次 `ReservationAiTaskIntakeService.accept`。 - MCP endpoint 不做自动重试。 - 业务结果不会因为 MCP 响应被回写或污染。 当前回归测试: ```text ./mvnw -Dtest=SuperAgentMcpControllerTest,SuperAgentMcpSubmitEnabledControllerTest test ``` 覆盖内容: - `tools/list` 暴露 V3 submit schema。 - 缺失 `source_message_id` 保持既有 `MISSING_SOURCE_MESSAGE_ID` 错误。 - 未知根字段在业务层前被拒绝。 - 悬空 `related_source_event_indices` 在业务层前被拒绝。 - 重复 `related_source_event_indices` 在业务层前被拒绝。 - `E_CHILD_1/E_CHILD_2/E_PARENT` 按数组顺序映射为 `1/2/3`。 - 合法 Parent split 只创建一个 AI batch,并返回 `accepted_count=3`。