Skip to content

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

@bokelley

Description

@bokelley

Motivation

When a platform method raises `AdcpError(code="MEDIA_BUY_NOT_FOUND", ...)`, the framework today projects it onto the MCP wire as a TEXT payload like `MEDIA_BUY_NOT_FOUND[media_buy_id]: ...`. The structured `CallToolResult.structuredContent.adcp_error.code` field that the AdCP wire spec defines is NOT populated.

Documented as a known limitation in `src/adcp/server/translate.py:246-250`:

Carries `isError=True` AND a structured `adcp_error` object on the same envelope — but `_make_error_result` (`mcp/server/lowlevel/server.py:467`) drops `structuredContent` for error results, so we can't reach...

Why this is biting us now

The AdCP storyboard runner's compliance checks include JSON-pointer assertions like `/adcp_error/code` to verify spec-conformant error responses. With the current text-only projection:

  • A seller correctly raising `MEDIA_BUY_NOT_FOUND` for an unknown media_buy_id passes the behavioral check (rejected the request)
  • But fails the wire-shape check (`/adcp_error/code` returns `actual: mcp_error` instead of `MEDIA_BUY_NOT_FOUND`)

This was just surfaced by the multi_platform_seller storyboard fix-pack (PR #508): residual storyboard failures aren't mock bugs — they're SDK wire-projection gaps. Every adopter using `adcp.server.serve` has the same gap.

Proposed fix

Migrate the error-result construction to use FastMCP's path that preserves `structuredContent` on `isError=True` results. The framework should populate:

```json
{
"isError": true,
"structuredContent": {
"adcp_error": {
"code": "MEDIA_BUY_NOT_FOUND",
"message": "...",
"recovery": "correctable",
"field": "media_buy_id",
"details": { ... }
}
},
"content": [{"type": "text", "text": ""}]
}
```

Where the structured fields are derived from the raised `AdcpError` instance.

Acceptance criteria

  • `AdcpError` raised from any platform method projects to `structuredContent.adcp_error` with all spec-defined fields (`code`, `message`, `recovery`, optional `field`/`details`)
  • Storyboard runner's `/adcp_error/code` JSON-pointer assertions pass for correctly-raised errors
  • Text fallback in `content[]` preserved for human-readable display
  • Existing tests stay green
  • Reference: storyboard runner artifacts from multi_platform_seller showing `actual: mcp_error` failures

Refs

  • Documented limitation: `src/adcp/server/translate.py:246-250`
  • Surfacing context: PR fix(examples): multi_platform_seller passes storyboards (drops continue-on-error) #508 (multi_platform_seller storyboard fix-pack) — agent ran into this when verifying wire-conformance of correctly-raised errors
  • Upstream issue: adcp-client#1527 (storyboard runner UX) — once this lands, the storyboard runner's structured error checks become useful as a regression gate

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions