http: surface the server's error body on failed calls and discovery (lands #26)

Merged
H3
1mo ago
+309-4

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.callTool normalizes a failed call into an Error whose message carries the status and the server's body (a string error / message / detail field when the body is JSON, otherwise the JSON itself), with enumerable status and data fields so the reason survives JSON.stringify out 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: Forbidden in errors[].

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 build in 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.

  • callTool errors include the status and body in the message and carry enumerable status and data fields, so the reason survives JSON.stringify out 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 structured error field takes precedence over a generic message field.
  • A blank body falls back to the status text; tool-call messages cap detail at 2000 characters while data keeps 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.