# api-doc MCP：接口定义核对流程（阶段 2 必读）

适配页面前，**所有用到的接口都要在 api-doc（AgentFox）里核对一遍入参/出参**，
不要靠平板代码或猜测推断参数名——出库交接就因为猜了 `hoState`（实际是 `handoverState`）
白白折腾了一轮实机排查。

## 连接方式

项目 `.mcp.json` 已配置 `api-doc` 远程 HTTP MCP。若会话内 MCP 工具可直接调用
（`mcp__api-doc__*`），直接用；**若工具没有注入会话**（ToolSearch 搜不到），用 curl 走
MCP 协议手工调用，三步：

```bash
URL=$(python3 -c "import json;print(json.load(open('.mcp.json'))['mcpServers']['api-doc']['url'])")
AUTH="Authorization: $(python3 -c "import json;print(json.load(open('.mcp.json'))['mcpServers']['api-doc']['headers']['Authorization'])")"

# 1) initialize：从响应头 mcp-session-id 取会话 id
curl -s -D /tmp/mcp_headers.txt -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"claude-code","version":"1.0"}}}' > /dev/null
SID=$(grep -i 'mcp-session-id' /tmp/mcp_headers.txt | tr -d '\r' | awk '{print $2}')

# 2) initialized 通知（必须发，否则 tools/* 报 session 未初始化）
curl -s -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' > /dev/null

# 3) tools/call（响应是 SSE，`sed 's/^data: //'` 后按 JSON 解析）
curl -s -X POST "$URL" -H "$AUTH" -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" -H "mcp-session-id: $SID" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_endpoints","arguments":{"keyword":"IntExpMove"}}}'
```

## 可用工具（5 个）

| 工具 | 用途 |
|------|------|
| `get_project_overview` | 项目概览 + 模块清单（首次调用） |
| `list_modules` | 全部模块含描述 |
| `list_endpoints` | 单模块内接口列表（分页，`moduleId` + `cursor`） |
| `search_endpoints` | 按关键字搜接口（`keyword` 用控制器前缀最准，如 `IntExpOutHandover`） |
| `get_endpoint_detail` | **核心**：单接口完整契约，schema 已拍平成字段表（`endpointId`） |

## 核对清单（每个页面做一遍）

对 ViewModel 里每个 `NetApply.api.xxx` 调用：

1. 从 `module_base/.../http/net/Api.kt` 拿到 `@POST` 路径 → `search_endpoints` 找到接口 → `get_endpoint_detail`
2. **入参**：前端传的每个 key 必须能在文档字段表里找到**同名字段**；找不到 = 传了也白传（后端静默忽略，不报错）
3. **类型**：状态类字段（`moveState`/`handoverState`/`outState`）文档均为 integer，前端传 `"0"` 字符串靠
   Jackson 隐式转换（实测可用）；新代码尽量传数字
4. **出参**：设计稿卡片/详情页的每个展示字段，都要能对上出参字段；对不上就是后端缺口
5. **结论落两处**：
   - 后端缺口（缺入参/缺出参/数据不回填）→ 记入 `documents/后端接口对接问题清单.xlsx`
     （按「模块 → 页面 → 接口定义 → 接口地址」格式，含前端现状与期望，状态标 ⏳/🔍）
   - 前端可自行解决的（参数名写错、字段绑错）→ 直接改代码，别记成后端问题

## 已核对过的语义（别再猜）

- `IntExpOutHandover`：交接状态入参 = `handoverState`（不是 `hoState`），**2026-07-29 内网实测过滤生效**
  （传 0/1 分别返回纯净的待/已交接集合，字符串 `"0"`/`"1"` 同样被接受）；`ldId` 是**组装人**不是交接人；
  交接人姓名出参 `hoUserName`，编码 `hoId`；查询入参**没有**交接人筛选（后端缺口 #4）
- `IntExpOutHandover/pageQueryTotal`：**出参恒 0**（任何条件下 totalPc/totalWeight/wbNumber 均为 0，
  与同条件 pageQuery 不一致，后端缺口 #6）——依赖它的 Tab 角标/底部统计显示 0 不是前端问题
- `IntExpMove`：出参无航班字段（缺口 #1）、无操作人姓名（缺口 #2）；`opId`/`opdate`/`businessType`
  文档有定义但实测恒 null（缺口 #3）；`pageQueryTotal` 正常（与 pageQuery 口径一致，`moveState` 过滤生效）
- `typeCode/getSpecialCodeByParam`：`flag=1` 国际，`ieFlag` 必须传空串（传 `"E"` 返回空数组）
- `eqm/uld`（ULD信息，6 个端点）：查询入参仅 `uld`/`status`/`uldSuffix`（uldSuffix 名义是
  "uld后两位"，实际就是**按所属航司筛选**，配 `DictUtils.getIntCarrierList`）；`status` 仅两态
  0 正常/1 故障（三态需求=缺口 #7）；无「来源」字段（#8）、无 `checkInDate`/`ifNo` 筛选入参（#9）、
  无批量删除（#10，`deleteUld` 是 `@Query` 单条）；出参 `ifNo`/`efNo`/`checkInDate`/`checkOutDate`
  文档无描述，按命名推断进/出港航班号与出入库时间（#11 待确认）
- `IntExpArrive`（出港运抵，2026-07-30 内网实测）：`declareStatus`（申报状态）只在出参，
  `pageQuery`/`pageQueryTotal` 入参均无此字段（缺口 #20，待运抵/已运抵 Tab 靠客户端拆分）；
  `declareStatus`/`GjcHaWb.arrivalStatus` 三色编码：`01`=正常(绿)、`W`=靛蓝态、其余非空=琥珀异常态；
  `haWbList[].response`/`arrivalOpDate` 是回执文本与对应时间，实测确认真实存在非空数据；
  分单补充收发货人信息**后端完全没有对应接口**（`search_endpoints` 对 `IntExpArrive`/`HaWb`
  关键字均搜不到分单更新接口，只有国际进港的 `IntImpAirManifest/complete`），
  实机调用拟定路径 `IntExpArrive/haWb/supplement` 确认 404（缺口 #19）
- `IntExpSearch`（出港查询，2026-07-31 内网实测）：查询入参 `outState`/`fno`/`dest`/`spCode`/`awbType`/
  `businessType`/`goodsCn`/`beginDate`/`endDate`/`agentCode`/`wbNo` 全部真实生效（平板既有口径）；
  文档定义的 `isFclose`（筛选航班是否关闭）**pageQuery 与 pageQueryTotal 均静默忽略**（A/B 对照
  全量 72 票与单日两组，加参前后结果完全一致，缺口 #21）；`pageQueryTotal` 出参无已入库/已离港
  分项计数（#21）；`detail` 出参 Map 无 `lockStatus`（锁定状态）与计费重量字段（#22）；
  详情「入库件数/入库重量」= `warehouseList` 各批次 pc/weight 求和（mock 与实测均吻合）；
  卡片状态推断口径：`fclose` 非空=已离港、否则 `opDate` 非空=已入库；
  特码字典沿用 `DictUtils.getSpecialCodeList(flag=1, ieFlag="")`（ieFlag 必须空串）
- `IntExpStorageUse`（出港仓库，2026-07-31 文档核对，内网 A/B 未测——当日开发机不在内网）：
  `pageQuery`/`pageQueryTotal` 同一套入参 schema：`fdate`/`fno`/`wbNo`/`likeNo`/`spCode`/`dest`/
  `agentCode`/`location`/`clearNormal`/`checkIn`/`reviewStatus`/`dep`/`hno`/`prefix`；设计稿筛选的
  **运单类型/业务类型/品名(中) 均无入参**（`awbType`/`businessType`/`goodsCn`，缺口 #23）；
  手机三格统计按 `clearNormal=0/1` 各调一次 `pageQueryTotal` 取 `wbNumber`，该过滤**是否真实生效
  待内网 A/B**（缺口 #24，参照 isFclose 有定义但被忽略的先例）；出参 `clearNormal` 0=未清仓/1=已清仓
  （卡片标签与「清仓」按钮置灰按 ==\"1\" 判定）；详情复用 `IntExpSearch/detail`（maWbId Query 参数）
- `IntImpStorage`（进港仓库，2026-07-31 文档核对；**注意前端 Api.kt 路径前缀是 `IntImpStorage`
  不是 `IntImpStorageUse`**，api-doc 按后者搜不到）：`pageQuery`/`pageQueryTotal` 同一套入参：
  `fdate`/`fno`/`wbNo`/`hno`/`fdep`（始发站）/`location`/`clearNormal`/`fid`；比出港仓库还少
  `agentCode`/`spCode`/`dest`——设计稿筛选的代理/特码/运单类型/业务类型/品名(中) **5 项均无入参**
  （缺口 #25）；分项统计与 clearNormal 过滤待实测同 #24（记为 #26）；详情复用
  `IntImpSearch/detail`（Body 传 `prefix`+`no`，出参 `data.maWb` 无 schema，字段 key 以平板绑定为准：
  `awbPc`/`awbWeight`/`cashWeight`/`inPc`/`inWeight`/`lockState`(0/1 需转中文)/`mftStatus` 原始舱单/
  `tallyStatus` 理货报告/`command` 海关放行/`by1` 承运人/`dlvTime` 出库时间；`dep`/`dest1`/`dest`/
  `unNumber`/`opDate` 为按出港推断，待实测 #27）
- `IntImpPickUpDlv`（提取出库，2026-07-31 文档核对）：`pageQuery` 入参含 **`outState`
  （0 未出库/1 已出库）**——待出库/已出库 Tab 直接真实过滤（抓包证实携带，效果待内网 A/B #29）；
  另有 `fno`/`dest`/`origin`/`carrier`/`awbType`/`businessType`/`goods`/`likeNo`/`pkId`/
  `beginDate`/`endDate`+`dateType`(0 入库/1 离港)/`chargeFlag`(0 未提取/1 已提取)；
  **无 `fdate`（航班日期）入参**（缺口 #28）；出参无车牌/目的港/业务类型名称（#28）；
  运单号筛选参数名是 **`no`**（平板既有口径）；`chargeName`/`chargeTime` = 柜台办理人/缴费(提取)时间，
  `dlvTime` = 出库时间，`dlvName` 按提货人语义推断作「出库人」展示（待确认 #29）；
  确认出库接口 `pickUpOut` 文档注明**需将全字段传至后端**（平板 `selectedItems.toRequestBody()` 已满足）
- `IntImpSearch`（进港查询，2026-07-31 文档核对）：`pageQuery`/`pageQueryTotal` 分页参数名是
  **`page`/`limit`**（不是 pageNum/pageSize）；手机筛选 9 项入参全部真实存在：`beginDate`/`endDate`/
  `fno`/`agentCode`/`spCode`/`origin`（始发站）/`awbType`/`businessType`/`goods`（品名，**不是 goodsCn**）；
  `outState`（0 未出库/1 已出库）文档为 integer，前端传字符串靠 Jackson 隐式转换；`pageQueryTotal`
  出参无已入库/已出库分项计数（缺口 #30，手机三格统计为客户端计数口径：dlvTime 非空=已出库、
  inDate 非空=已入库）；出参含 `agentName`（中文名）+ `agentCode`，agentName 可为 null
- 「进港移库」（设计稿 11）**归属更正（2026-08-03 用户确认）：属国内进港移库 `DomImpMove`**，
  不是国际进港移库——「后端模块缺失」的旧结论（原缺口 #31）作废，原按 IntExpMove 约定预接的
  IntImpMove 前端实现已整体删除。溯源：api-doc 53 模块中移库仅 `DomImpMove`（国内进港，8 接口）与
  `IntExpMove`（国际出港，4 接口），移库按转关业务前缀划分（DomImpMove 处理 `CI**`、IntExpMove
  处理 `IO**`），设计稿详情卡的「国际进港」红标签实为 `awbType` 值（如「国际进港（国内中转）」），
  不代表页面归属
- `DomImpMove`（国内进港移库，2026-08-03 内网实测）：手机 Tab 双端点——待移库 `search` /
  已移库 `searchMoved`（同一套入参 schema，分页参数名 **`page`/`limit`**）；入参 A/B 实测：
  **`wbNo`、`awbType`（值为 CICO/CIII/CIIO 转关码）过滤生效；`fdate`/`fno`/`spCode` 文档有定义但
  被静默忽略**（缺口 #32）；`searchMoved`/`detail` 出参 **`opId`/`opDate` 恒 null** 且无操作人姓名
  字段（缺口 #33，移库人/移库时间显示"-"），`moveId` 形如 `CICO240617155016` 疑似内嵌移库时间戳但无
  文档语义；`searchMoved` 出参**无 pic 三字段**（search 有）——拍照上传页统一先调 `GET detail`
  回填已有照片（#34 前端已规避）；照片提交走 `modify` 最小体
  `{mawbId, remark, picNumber, pic, originalPic}`（平板移交编辑页同链路，removeEmptyOrNull）；
  批量移库 `move` 入参 `{fid:"0", ids:[mawbId]}`；国内进港特码字典
  `DictUtils.getSpecialCodeList(flag=0, ieFlag="I")` 实测取数正常
- `IntExpCheckIn`（出港计重，2026-07-29 内网实测）：`likeNo` **并非任意模糊**——按 8 位 `no`
  匹配（11 位 wbNo 或部分号均 0 条），11 位全号必须走 `wbNo`；`checkIn`/`checkInList`
  文档有定义但 `pageQuery`/`pageQueryTotal` **均静默忽略**（缺口 #12，待计重/计重中计数依赖）；
  查询入参无通道号/承运人/运单类型（#13）、`checked/*` 无收运时间范围（#14）；`checked/pageQuery`
  出参无通道号（#15）但有 `userName`（计重人中文名）；`listRecordByWh` 出参计重人仅 `opId`（#16）；
  `preArrive` 成功后 `pageQuery` 的 `arriveFlag` 不回填（#17）；`splitCheckIn`/`completeCheckIn`
  无 `palletNumber`/`carWeight` 入参（#18，手机版托盘数量/自重按设计稿传预留参数）；
  运单类型字典：`DictUtils.getWaybillTypeList2(ieFlag="E", type="IO")`

## 内网直调验证技巧（比驱动 UI 快得多）

登录态 token 存在 SharedPreference 里，取出后可以直接 curl 后端做行为验证
（**注意：不要把 token 打印到输出里**，落临时文件引用）：

```bash
adb -s <serial> shell "run-as com.lukouguoji.aerologic cat \
  /data/data/com.lukouguoji.aerologic/shared_prefs/data.xml" \
  | grep -o '<string name="token">[^<]*' | sed 's/.*>//' > /tmp/air_token

curl -s -X POST "http://192.168.1.250:8093/IntExpOutHandover/pageQuery" \
  -H "Authorization: $(cat /tmp/air_token)" -H "Content-Type: application/json" \
  -d '{"pageNum":1,"pageSize":50,"handoverState":0}'
```

用它做「参数是否生效」的 A/B 对比（同条件只改一个参数看 total 变化），
再回 App UI 走一遍确认端到端链路。UI 里的滚轮日期选择器可用
`input swipe`（慢速 500ms、每次 3 格 ≈ 396px）驱动，滑完截图校准。
