Skip to content

fix(mcp): surface query() failures as data, not exceptions#88

Merged
zzstoatzz merged 1 commit into
mainfrom
fix/query-error-handling
Jun 2, 2026
Merged

fix(mcp): surface query() failures as data, not exceptions#88
zzstoatzz merged 1 commit into
mainfrom
fix/query-error-handling

Conversation

@zzstoatzz

Copy link
Copy Markdown
Owner

A failing query (404, DNS miss, binary response) was raising up through fastmcp into pydantic-ai's tool-call layer, where max_retries=1 would abort the entire agent run after a single bad host guess. Exploration calls — picking the wrong PDS, hitting an endpoint that returns blob bytes — should be cheap to fail.

The MCP query tool now catches httpx.HTTPStatusError, httpx.RequestError, and JSON/Unicode decode errors and returns a structured {\"error\": ..., \"message\": ...} dict instead. The CLI/library path (_internal.operations.query) is unchanged — it still raises, preserving existing error flow for non-MCP callers.

Motivating failures (live phi batch)

  • query(\"com.atproto.sync.listRepos\", host=\"grain.social\") → 404 → retry with pds.grain.social → DNS miss → whole agent run aborted with `UnexpectedModelBehavior: Tool 'query' exceeded max retries count of 1`
  • query(\"com.atproto.sync.getBlob\", ...) → tried to JSON-decode PNG header → `UnicodeDecodeError: 'utf-8' codec can't decode byte 0x89`

Both now come back as data the model can read and adapt to.

Test plan

  • new tests for http_status / transport / non_json_response paths
  • existing 21 query tests still pass
  • ruff clean

A failing query (404, DNS miss, binary response) was raising up through
fastmcp into pydantic-ai's tool-call layer, where the default
max_retries=1 would abort the entire agent run after a single bad host
guess. Exploration calls — picking the wrong PDS, hitting an endpoint
that returns blob bytes — should be cheap to fail.

The MCP query tool now catches httpx.HTTPStatusError, httpx.RequestError,
and JSON/Unicode decode errors and returns a structured
`{"error": ..., "message": ...}` dict instead. The CLI/library path
(_internal.operations.query) is unchanged — it still raises, preserving
existing error flow for non-MCP callers.

Covers two motivating failure modes from a live phi batch:
- 404 on a wrong-host listRepos guess took down the whole run
- query("com.atproto.sync.getBlob", ...) blew up on the PNG header
  trying to JSON-decode binary bytes
@zzstoatzz
zzstoatzz merged commit a3d234f into main Jun 2, 2026
2 of 3 checks passed
@zzstoatzz
zzstoatzz deleted the fix/query-error-handling branch June 2, 2026 06:17
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