fix(mcp): truthful serial-tracked SO block message + close-out discoverability#882
fix(mcp): truthful serial-tracked SO block message + close-out discoverability#882dougborg wants to merge 2 commits into
Conversation
…erability
The serial-tracked SO fulfillment block told operators to "Pass
serial_numbers via the rows= override", but following that hits a Katana
422 ("serial numbers have already been assigned") whenever the serial was
minted on the row's linked MO — the normal close-out case. Add the caveat
and name the working fallback (Katana UI "Deliver all"), which performs the
MO->SO transfer the public API cannot. Mirror the caveat in
katana://help/tools.
Also close two discoverability gaps surfaced in the 2026-05-29 close-out:
- picked_date: clarify in help that there is no separate picked_date /
delivery_date parameter — completed_at is the single backdating knob for
both order types (already shipped in #778, just hard to find).
- landed costs on receive: receive_purchase_order now points operators at
the MCP-native apportionment path (modify_purchase_order add_additional_costs
with the receipt group_id) via a next_action, its docstring, the help
resource, and the list_additional_costs description — instead of leaving
it as an undiscovered UI-only step.
Regression assertions pin the block-message caveat and the receive
next_action. The underlying transfer blocker stays open (#784 reopened,
verified in #849); the receive additional_costs field is #879.
Refs #784 #849 #804 #879
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR updates MCP operator-facing guidance for order fulfillment and purchase-order receiving, focusing on clearer serial-tracked SO messaging, landed-cost follow-up discoverability, and backdating help text.
Changes:
- Updates
fulfill_orderwarnings/help for serial-tracked sales orders andcompleted_atdiscoverability. - Adds landed-cost next-action/doc guidance after
receive_purchase_order. - Adds regression assertions for the new warning and next-action text.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
katana_mcp_server/src/katana_mcp/tools/foundation/orders.py |
Updates serial-tracked SO block warning text. |
katana_mcp_server/src/katana_mcp/resources/help.py |
Adds landed-cost, serial-transfer caveat, and completed_at help text. |
katana_mcp_server/src/katana_mcp/tools/foundation/purchase_orders.py |
Adds landed-cost follow-up guidance to receive success actions and docstring. |
katana_mcp_server/src/katana_mcp/tools/foundation/reference.py |
Expands list_additional_costs docstring with receipt-group usage. |
katana_mcp_server/tests/tools/test_orders.py |
Pins the serial-tracked SO caveat text. |
katana_mcp_server/tests/tools/test_purchase_orders.py |
Pins landed-cost next-action discoverability after receive. |
| "unassigned. Caveat: if a serial was minted on the linked " | ||
| "manufacturing order, Katana's public API cannot transfer it " | ||
| "onto this row (it returns 'serial numbers have already been " | ||
| "assigned'); fulfill that row from the Katana UI (\"Deliver " | ||
| 'all") instead.' |
There was a problem hiding this comment.
Good catch — reconciled in 4680265. You are right that the blanket reading was wrong: add_serial_numbers(resource_type="SalesOrderRow") transfer does work for SO rows that are not linked to an MO (the existing contract/test cover that path). The failure is narrower: when the target SO row's production is driven by a linked MO (the common serial-tracked close-out case), Katana hard-422s the transfer with "is linked, serial info must be updated on MO", and fulfill_order(... rows=[{serial_numbers}]) 422s with "already been assigned". This message/help text is scoped to exactly that MO-linked case. I updated the add_serial_numbers docstring (serial_numbers.py) to document the same MO-linked exception so both tools tell one consistent story and point at the same UI fallback. Verified live in the 2026-05-29 close-out; tracked end-to-end in #849 (and #784, reopened).
| **Caveat (serial minted on a linked MO):** the public-API write paths | ||
| cannot *transfer* a serial that is currently attached to the row's linked | ||
| manufacturing order — `POST /sales_order_fulfillments` returns "serial | ||
| numbers have already been assigned" and `add_serial_numbers(SalesOrderRow)` | ||
| returns "is linked, serial info must be updated on MO". That common | ||
| close-out case (finished good came off an MO with the serial already | ||
| minted) has no MCP-native path today; fulfill it from the Katana UI | ||
| ("Deliver all"), which performs the MO→SO transfer atomically. |
There was a problem hiding this comment.
Good catch — reconciled in 4680265. You are right that the blanket reading was wrong: add_serial_numbers(resource_type="SalesOrderRow") transfer does work for SO rows that are not linked to an MO (the existing contract/test cover that path). The failure is narrower: when the target SO row's production is driven by a linked MO (the common serial-tracked close-out case), Katana hard-422s the transfer with "is linked, serial info must be updated on MO", and fulfill_order(... rows=[{serial_numbers}]) 422s with "already been assigned". This message/help text is scoped to exactly that MO-linked case. I updated the add_serial_numbers docstring (serial_numbers.py) to document the same MO-linked exception so both tools tell one consistent story and point at the same UI fallback. Verified live in the 2026-05-29 close-out; tracked end-to-end in #849 (and #784, reopened).
…-linked limitation Addresses Copilot review on #882: the new fulfill_order block message and help text say an MO-minted serial can't be transferred onto a linked SO row, but add_serial_numbers' docstring described resource_type="SalesOrderRow" transfer as always working — a contradiction. The transfer path does work for SO rows NOT linked to an MO. It is rejected (422 "is linked, serial info must be updated on MO") only when the target SO row's production is driven by a linked MO. Document that exception on the transfer bullet so both tools tell the same story and point operators at the same UI fallback. Doc-only; no behavior change. Refs #784 #849 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Three operator-facing friction fixes surfaced by the 2026-05-29 Spot Bikes
end-of-day close-out (5 POs received + 2 SOs closed). All are docs/message
changes — no logic or wire-shape changes — so they're safe to ship ahead of
the heavier blockers they point at.
Truthful serial-tracked SO block message (Bug 7). The block told
operators to "Pass serial_numbers via the rows= override", but following
that hits a Katana 422 (
serial numbers have already been assigned)whenever the serial was minted on the row's linked MO — the normal
close-out case. The message now adds the caveat and names the only working
path (Katana UI "Deliver all", which performs the MO→SO transfer the public
API cannot). Mirrored in
katana://help/tools.picked_datediscoverability (Bug 4). There is no separatepicked_date/delivery_dateparameter —completed_atis the singlebackdating knob for both order types (shipped in feat(mcp): fulfill_order should accept a backdated timestamp (done_date for MO / delivery_date for SO) #778, just hard to find).
The help resource now says so explicitly.
Landed-cost discoverability on receive (Bug 3).
receive_purchase_orderdoesn't set customs/freight/duties, and the MCP-native path
(
modify_purchase_order(add_additional_costs=[…]), which can target areceipt
group_id) wasn't discoverable at receive time — so agents fellback to the Katana UI. Now pointed at via a
next_action, the receivedocstring, the help resource, and the
list_additional_costsdescription.Scope / what this is NOT
The underlying serial-transfer blocker stays open — the public API genuinely
cannot transfer an MO-minted serial onto a linked SO row; that needs a
design decision (UI-API access vs. documented fallback). Tracked in #784
(reopened) and verified end-to-end in #849. The
additional_costs-on-receive feature (vs. this discoverability pointer) is #879. Bug 6
serial input notes are on #804.
Test plan
uv run poe agent-check(ruff format/check, ty) — cleanuv run poe test— 3780 passeduv run pyrighton every edited file — 0 errorsnext_actionRefs #784 #849 #804 #879
🤖 Generated with Claude Code