Skip to content

fix(server): protocol-polish follow-ups from PR #341#342

Merged
bokelley merged 2 commits intomainfrom
bokelley/emma-followup-protocol-polish
May 1, 2026
Merged

fix(server): protocol-polish follow-ups from PR #341#342
bokelley merged 2 commits intomainfrom
bokelley/emma-followup-protocol-polish

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 1, 2026

Summary

ad-tech-protocol-expert called out three follow-ups from PR #341's review before the wire shape becomes de-facto convention. This PR ships the two with concrete fixes; the third (MCP structuredContent migration) needs a lowlevel Server.call_tool refactor and is documented as known-bridge.

#3 — Truncation marker buyer-parseability

Pre-fix the MCP ToolError text-suffix path emitted bare ... as the truncation marker, which made buyer json.loads throw JSONDecodeError with no signal of why. Now emits a sentinel object: {"_truncated": true, "_reason": "size" | "non_serializable", "_partial": "..."}always valid JSON. Buyers split on \nDetails: prefix, json.loads the tail, and branch on the marker. Iterative fit replaces naive head-room subtract so quote/backslash escaping in _partial doesn't blow past the cap.

#2caused_by.type cross-language documentation

Documented in _internal_error_details that this field is a debug hint for the seller dev reading their own server logs, NOT a cross-language contract. JS sellers won't emit Python-typed exception names. Buyers wanting structured retry/fix/abandon semantics should read recovery (terminal/correctable/transient), which IS the spec contract.

#1 — MCP structuredContent migration (deferred + documented)

_to_mcp's docstring now cites FastMCP's lossy _make_error_result (mcp/server/lowlevel/server.py:467) as the reason text-suffix is the working bridge. The protocol-correct fix needs lowlevel Server.call_tool registration so we can return CallToolResult(structuredContent={"adcp_error": ...}, isError=True) directly. Deferred to a separate effort — touches the FastMCP→lowlevel boundary throughout serve.py.

Test plan

  • CI green on Python 3.10–3.13
  • 1 test updated: truncation now asserts parseable sentinel (not bare ... suffix)
  • 1 test new: unserializable details (circular ref) emits sentinel, not malformed JSON
  • 2883 tests pass full sweep (was 2882)

🤖 Generated with Claude Code

bokelley and others added 2 commits May 1, 2026 14:10
ad-tech-protocol-expert called out three follow-ups from PR #341.
This PR ships #2 + #3 with concrete fixes; #1 (MCP structuredContent
migration) is documented as known-bridge — needs lowlevel
Server.call_tool refactor.

#3 — Truncation marker buyer-parseability. Pre-fix the MCP
ToolError text-suffix path emitted bare '...' as the truncation
marker, making buyer json.loads throw JSONDecodeError with no
signal of why. Now emits a sentinel object: {_truncated: true,
_reason: 'size' | 'non_serializable', _partial: '...'} — ALWAYS
valid JSON. Buyers split on '\nDetails: ' prefix, json.loads the
tail, and branch on the marker. Iterative fit replaces naive
head-room subtract so quote/backslash escaping in _partial doesn't
blow past the cap.

#2 — caused_by.type Python-only debug breadcrumb. Documented in
_internal_error_details that this field is a debug hint for the
seller dev reading their own server logs, NOT a cross-language
contract. JS sellers won't emit Python-typed exception names. Buyers
wanting structured retry/fix/abandon semantics should read
'recovery' (terminal/correctable/transient), which IS the spec
contract.

#1 — MCP structuredContent migration. Documented as bridge in
_to_mcp's docstring with the file:line citation of FastMCP's
lossy _make_error_result. The protocol-correct fix needs lowlevel
Server.call_tool registration so we can return CallToolResult
directly with structuredContent=adcp_error + isError=True;
deferred to a separate effort because it touches the FastMCP→
lowlevel boundary throughout serve.py.

Tests: 1 updated (truncation now asserts parseable sentinel),
1 new (unserializable details emits sentinel, not malformed JSON).
Test count: 2883 (was 2882).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Latest Emma matrix run (post-#341/#342) caught three small DX items
all four backends agreed on. Fold into PR #342 since it's the same
scope (protocol-polish follow-ups).

1. examples/hello_seller.py:222 — wire-field bug. Stub returned
   {deliveries: [...]} but the schema at
   schemas/cache/media-buy/get-media-buy-delivery-response.json
   requires media_buy_deliveries. A buyer copying this example
   would get INVALID_RESPONSE on call. Sales 9/10 P1.

2. examples/hello_seller.py docstring overclaims. Module docstring
   said Three platform methods (get_products, create_media_buy,
   get_media_buy_delivery) but the class actually implements all
   five required sales-non-guaranteed methods (per
   REQUIRED_METHODS_PER_SPECIALISM, validated at boot). Trim the
   claim to match reality.

3. serve() boot log line. Two of four matrix backends flagged
   silent-boot — adopters had to curl to confirm the listener was
   up. Now logs one INFO line at the bind boundary:
   MCP listening on http://host:port/mcp (transport=streamable-http)
   for MCP, A2A listening on http://0.0.0.0:port/ for A2A.
   Framework-controlled (uvicorn's startup log can be filtered/
   quieted; this line always lands).

Test count: 2883 (existing test_get_media_buy_delivery_returns_zeros
updated to assert the spec field name; pins drift).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bokelley bokelley merged commit 5a37adc into main May 1, 2026
12 checks passed
@bokelley bokelley deleted the bokelley/emma-followup-protocol-polish branch May 1, 2026 21:05
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.

1 participant