http: surface the server's error body on failed calls and discovery (lands #26)
Merged
H3
1mo ago+309+309+012345678901234567890123456789-4-4-0123456789
Summary
Lands #26 by @edujuan, cherry-picked with authorship preserved, on top of #41 so it applies to the rewritten SSE and Streamable HTTP protocols. Stacked on #41: until #41 merges this diff shows its commits too; after that only the three commits from #26 remain.
What #26 does
HttpCommunicationProtocol.callToolnormalizes a failed call into anErrorwhose message carries the status and the server's body (a stringerror/message/detailfield when the body is JSON, otherwise the JSON itself), with enumerablestatusanddatafields so the reason survivesJSON.stringifyout of a sandboxed tool runner. Network and timeout errors pass through unchanged.- SSE and Streamable HTTP discovery read the response body before throwing on a non-2xx, so a refused discovery no longer surfaces as just
HTTP 403: Forbiddeninerrors[].
Conflict resolution: only the Streamable HTTP discovery block conflicted with #41's rewrite; the body read was re-applied inside the new structure. The SSE hunk merged cleanly.
Test plan
- #26's four tests (body in message, object-valued error field, streamable and SSE discovery) pass on top of #41
-
bun test packages/http: 128 pass -
bun run buildin packages/http succeeds including DTS
Supersedes #26. Companion: universal-tool-calling-protocol/python-utcp#102.
š¤ Generated with Claude Code
Summary by cubic
Failed HTTP tool calls and discovery now surface the server's error body instead of a generic status code, so the real reason for a refusal shows up in messages and logs.
callToolerrors include the status and body in the message and carry enumerablestatusanddatafields, so the reason survivesJSON.stringifyout of sandboxed tool runners.- SSE and Streamable HTTP discovery errors include the response body, truncated to 200 characters, instead of
HTTP 403: Forbidden. - Object-valued error fields are JSON-stringified rather than shown as
[object Object], and a structurederrorfield takes precedence over a genericmessagefield. - A blank body falls back to the status text; tool-call messages cap detail at 2000 characters while
datakeeps the full body. - Truncation is an O(cap) code-point walk, so huge bodies are not fully materialized and multi-byte characters on the boundary are never split.
- Network and timeout errors pass through unchanged.