Repository navigation
feat: add create_order MCP tool, mirroring POST /api/v1/orders (AI-11) - #101
Conversation
atc964
left a comment
There was a problem hiding this comment.
Clean mirror, and delegating to order_service.create_order rather than reimplementing means this inherits #71's 404 fix for free. Docs regeneration is correct.
parsed_metadata = json.loads(metadata) returns whatever the JSON says, but REST types it metadata: Optional[dict]. So {"metadata": "spring"} is a 422 over HTTP and a stored string over MCP. That string later reaches order_meta.update(proposed) in apply_change_request, an uncaught AttributeError on a str, so an approved change request against that order 500s with no API path to repair it.
Required before merge:
- Reject non-dict
metadataafter parsing.if parsed_metadata is not None and not isinstance(parsed_metadata, dict), return the error shape REST's 422 produces. Full schema validation is not necessary. - A test passing
metadata='"spring"'asserting it is rejected rather than stored.
Follow-up, not blocking: the PR body cites create_package's product_ids as precedent. That parameter does not exist. The convention claim is right, the actual precedents are update_rate_card(entries) and bulk_deal_operations(operations).
|
@atc964 Confirmed — a bare Pushed Also noted the Full suite: 1565 passed, 28 skipped. Ready for another look. |
REST types metadata as Optional[dict] (Pydantic-enforced); this MCP tool accepted a raw JSON string, so a bare '"spring"' or '[1, 2]' parsed fine but wasn't a dict. Left unchecked, that non-dict value was stored as-is and only surfaced later as an uncaught AttributeError on order_meta.update(proposed) inside apply_change_request -- a 500 with no way to repair the order, caught in a maintainer review. Now rejected up front with an invalid_metadata error, matching REST's error shape (full schema validation isn't needed, just the type gate).
8629b84 to
9be1d42
Compare
atc964
left a comment
There was a problem hiding this comment.
The isinstance gate is right and sits before order_service.create_order, the string and array rejection tests assert set_order is never awaited, and malformed JSON behavior is unchanged from the first commit. Approving.
One correction for the record, no code change needed: this does not actually match REST's 422 shape. There is no custom RequestValidationError handler, so REST rejects a non-dict metadata with FastAPI's default Pydantic error list (detail as a list with type dict_type), not a detail object. What your check does match is this codebase's service-error envelope, the same {"detail": {"error", "message"}} that _service_json emits when order_service raises HTTPException, which is the more useful consistency for MCP callers anyway. Worth fixing the comment above the check so the next reader is not misled.
Optional polish: wrap the json.loads in a try/except returning an invalid_metadata error too, so '{bad' gets the same friendly envelope instead of a generic tool error.
The defect
REST has
POST /api/v1/ordersto turn a booked deal into an order, but there was no MCP equivalent. A buyer or operator working purely over MCP could discover, quote, book, and distribute a deal, but had no tool to create the order that follows — the flow dead-ended at "distributed."Already filed as AI-11 (High priority — "major incoherence"), reported independently of this fix.
The fix
Added
create_ordertomcp_server.py, calling the existingorder_service.create_order(deal_id, quote_id, metadata)— no new service logic was needed, since the REST router itself is a thin wrapper around the same function.metadatais accepted as a JSON object string (matching the established convention for dict-typed MCP tool parameters elsewhere in this file, e.g.create_package'sproduct_ids). Not operator-gated, matching REST's own_get_optional_api_key_record(optional, not required) and the siblinglist_orderstool, unliketransition_orderwhich is a genuinely admin action.Also regenerated
docs/reference/mcp-tools.md(auto-generated, drift-guarded bytest_docs_inventory_drift.py— a real CI check this PR'screate_orderaddition would otherwise fail).Verification
New tests in
tests/unit/test_mcp_server.py(TestCreateOrder, 3 tests): creates and persists an order, parses JSONmetadata, and works correctly with no arguments at all (bothdeal_id/quote_idare optional). Negative control: broke thedeal_idpassthrough — exactly the one test asserting it failed, the other two (unrelated todeal_id) correctly still passed.Full unit suite: 1524 passed (1521 baseline + 3 new), 0 failures. Integration suite: 39 passed, 28 skipped, 0 failures.
ruff check/ruff format --check: clean.Live, end-to-end: called
create_orderover a real MCP session withdeal_id/metadata, then confirmed the exact same order viaGET /api/v1/orders(REST) — real cross-surface persistence, not a tool-local mock.A note on scope, not a defect
The tool's own docstring initially read "...persist its state machine (AI-11)" — caught and removed before this draft, since that's a public-facing tool description (shown to any MCP client, and now baked into the committed
docs/reference/mcp-tools.md), not an internal comment. Different treatment than the CHANGELOG entry, which keeps the reference — a changelog is expected to carry tracking references, a tool description read by an end user or LLM client isn't.