bug(mcp): madt_api_call returns non-2xx responses as ordinary data - HTTP status discarded on the MCP surface #153
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sixfold-space/madtea#153
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observed (2026-07-16)
Codeberg's
/repos/issues/searchendpoint was hard-failing platform-wide (HTTP 500, Forgejo's empty-message ISE body{"message":"","url":"https://codeberg.org/api/swagger"}- reproduced with anonymous curl over both IPv4 and IPv6, and authenticated via madtea). The typed path surfaced it correctly:madt_issues action=searchreturnedAPI error 500: ....madt_api_callon the SAME failing endpoint returned a successful-looking result:madt_api_call GET /repos/issues/search?q=whoami&limit=5 jq=length->{"result": 2}- which is the key count of the error object, masquerading as a plausible search-result count.isErrorflag and no status code anywhere in the result.This actively derailed the diagnosis of the search outage: the passthrough appeared to prove the endpoint healthy while every other path 500'd, sending the investigation toward phantom madtea-side differences (auth, headers, param order, token scopes) for the better part of an hour.
Mechanism
internal/service/api/call.goCall()capturesHTTPStatusCodeonCallResultbut never treats >=400 as an error.internal/mcp/tools_api.gohandler readsresult.Bodyand ignoresresult.HTTPStatusCodeentirely: noisError, no status in the content, and whenjq/templateis set the error body is transformed before anyone could notice what it was.requestWithMeta, internal/gitea/client_http.go) converts >=400 intoAPI error <code>: <body>.Ask
madt_api_callsurfaces non-2xx as a tool error (isError, message shaped like the typed path:API error <code>: <body>), evaluated BEFORE jq/template application so a filter can never transmute an error body into plausible data.{status, body}structurally instead of erroring); default stays fail-loud. gh (the naming yardstick) exits non-zero on non-2xx fromgh api.madtea apifor the same gap and align both surfaces.Acceptance
madt_api_call-> tool error naming the HTTP status; with jq set, the error is returned instead of filtered output.