Skip to content

fix(server): populate structuredContent.adcp_error on MCP error results#534

Closed
bokelley wants to merge 2 commits intomainfrom
claude/issue-509-mcp-structured-error
Closed

fix(server): populate structuredContent.adcp_error on MCP error results#534
bokelley wants to merge 2 commits intomainfrom
claude/issue-509-mcp-structured-error

Conversation

@bokelley
Copy link
Copy Markdown
Contributor

@bokelley bokelley commented May 4, 2026

Summary

  • When an ADCPError is raised from a handler, return a CallToolResult directly instead of raising ToolError. Returning bypasses FastMCP's _make_error_result path (which drops structuredContent), so isError=true and structuredContent.adcp_error.code are now both populated on the wire.
  • Adds _extract_adcp_error_fields() to adcp.server.translate — extracts {code, message, recovery, field?, details?, suggestion?} from all ADCPError variants (ADCPError, ADCPTaskError, Error Pydantic model, optional adcp.decisioning.types.AdcpError).
  • Text fallback in content[] is preserved for backward compatibility (older clients that parse the text form still work unchanged).

Closes #509

Why this approach

FastMCP's _make_error_result (called when a ToolError is raised) drops structuredContent. The MCP lowlevel server has a bypass: isinstance(results, types.CallToolResult) at line 540 short-circuits to return types.ServerResult(results) and preserves the result unchanged. Returning a CallToolResult from fn takes this path; raising ToolError does not.

The convert_result gate between fn return and the lowlevel server is safe: FastMCP creates RootModel[dict[str, Any]] for dict[str, Any]-typed tools, which calls model_validate(result.structuredContent) on the returned CallToolResult — any dict passes, so the structured error envelope is never rejected.

Test plan

  • TestExtractAdcpErrorFields (11 tests) — all exception type branches, field/details/suggestion presence/absence
  • test_adcp_error_returns_structured_call_tool_resultADCPTaskError from handler → CallToolResult with structuredContent["adcp_error"]["code"] and text fallback
  • test_adcp_error_text_fallback_preservedADCPAuthenticationError text fallback coexists with structured content
  • ruff check src/ — clean
  • mypy src/adcp/ — clean
  • pytest tests/ --ignore=tests/integration --ignore=tests/conformance — 1987 passed, 15 skipped, 1 xfailed

Pre-PR reviews

Code review: LGTM with two fixes applied — narrowed except Exceptionexcept ImportError on optional decisioning import; added suggestion field to DecisioningAdcpError branch to prevent silent drop on proxy round-trips. Both addressed in the refine commit.

Protocol review: Confirms structuredContent.adcp_error nested shape is correct per the AdCP spec's JSON-pointer convention (/adcp_error/code). recovery enum values (terminal/transient/correctable) confirmed correct. Non-breaking addition under MCP spec.


Triage-managed PR — opened automatically for issue #509.

https://claude.ai/code/session_01V4QPiByXeHu2p18TsxjWDF


Generated by Claude Code

claude added 2 commits May 4, 2026 01:18
When an ADCPError is raised from a handler, return a CallToolResult directly
instead of raising ToolError. Returning bypasses FastMCP's _make_error_result
path, which drops structuredContent, so both isError=true and
structuredContent.adcp_error.code are now propagated to the wire.

Adds _extract_adcp_error_fields() to translate.py to extract the structured
{code, message, recovery, field?, details?} dict from all ADCPError variants
(ADCPError, ADCPTaskError, Error Pydantic model, DecisioningAdcpError).

Text fallback in content[] is preserved for backward compatibility.

Closes #509

https://claude.ai/code/session_01V4QPiByXeHu2p18TsxjWDF
…r, fix mypy

- Add suggestion field to _extract_adcp_error_fields() for all branches
  (prevents silent drop on MCP→A2A proxy round-trips)
- Narrow except Exception → except ImportError for optional decisioning import
- Add pyproject.toml mypy override for adcp.decisioning (optional component)

https://claude.ai/code/session_01V4QPiByXeHu2p18TsxjWDF
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 4, 2026

Superseded by #525 (feat(server): MCP error responses populate structuredContent.adcp_error), already merged to main.

@bokelley bokelley closed this May 4, 2026
@bokelley bokelley deleted the claude/issue-509-mcp-structured-error branch May 4, 2026 08:51
@bokelley
Copy link
Copy Markdown
Contributor Author

bokelley commented May 4, 2026

Acknowledged — closing the loop here. #525 is already merged, so this draft is superseded.


Generated by Claude Code

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.

feat(server): MCP error responses must populate structuredContent.adcp_error.code (not just text)

2 participants