You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The blockscout-analysis skill's SKILL.md § "Transient errors" (currently lines 29–36 on main) gives one unified retry rule for both access methods:
Retry on 5xx (especially 500 Internal Server Error): retry the same call up to 3 times before giving up …
Applies to both access methods. The same rule governs native MCP tool calls and scripts hitting the MCP REST API …
This was discovered to be inaccurate during review of blockscout/mcp-server#361, which fixes an integration-test retry cascade (3 internal × 3 test-wrapper = 9 attempts). The same cascade exists in production via REST clients that follow this rule literally.
Empirical findings
Verified by running an in-process experiment that forces httpx.ConnectError / httpx.ConnectTimeout / honest upstream 502 against make_blockscout_request (production default bs_request_max_retries = 3):
Failure mode
Internal attempts
REST surface
MCP surface
Transport error (ConnectError) — retries exhausted
3
500 + JSON {\"error\":...}
200 + isError:true, bare exception text
Transport timeout (ConnectTimeout) — retries exhausted
3
504 + JSON
200 + isError:true, bare exception text
Honest upstream 502 (HTTPStatusError, not retried)
1
502 pass-through
200 + isError:true, \"502 Bad Gateway - Details: ...\"
Two key consequences for the skill:
Native MCP clients never see a 5xx HTTP status. Tool errors are always wrapped as {\"isError\": true, \"content\": [{\"type\": \"text\", \"text\": \"Error executing tool <name>: <str(exception)>\"}]} at HTTP 200. The current rule "retry on 5xx" is structurally inapplicable in MCP mode — an agent has to reason about retryability from the error text, not from a status code.
REST scripts following the rule literally produce a 3×3=9 cascade for transport failures. A ConnectError exhausts the server's 3 internal retries → surfaces as 500; a ConnectTimeout surfaces as 504. Both are 5xx → the script's "retry 3 times" rule fires on top → 9 total attempts for a single transport-level event. The script has no reliable way to distinguish "already-retried transport failure" from "honest upstream 5xx" via status code alone.
Proposed change
Split § "Transient errors" into two access-method-specific subsections rather than one unified rule.
Native MCP (agents)
Tool errors arrive as isError: true with text content; no HTTP status indicator.
Retryability is decided from the error text (natural-language understanding) — agents should retry when the text suggests a transient/network issue (e.g., "connection reset", "timed out", "All connection attempts failed", "upstream 5xx") and not retry on validation/4xx-style messages.
Add a soft note: the server already retries transport-level failures internally up to 3 times before surfacing them, so additional aggressive retries from the agent rarely help — 1 follow-up attempt is usually sufficient.
REST API (scripts)
500 and 504 responses from the MCP server itself typically indicate the server already exhausted its 3 internal retries on a transport-level failure (incomplete reads, connection errors, downstream timeouts). For these, 1 additional client-side retry is sufficient — do not retry up to 3 times.
For honest 5xx passed through from upstream Blockscout (e.g., a genuine 502 from the indexer), 3 retries are still reasonable. Scripts cannot reliably distinguish these two cases by status code alone, so the conservative default should be 1 retry.
4xx semantics unchanged (no retry, including 413 for direct_api_call oversize).
Context
The
blockscout-analysisskill'sSKILL.md§ "Transient errors" (currently lines 29–36 onmain) gives one unified retry rule for both access methods:This was discovered to be inaccurate during review of blockscout/mcp-server#361, which fixes an integration-test retry cascade (3 internal × 3 test-wrapper = 9 attempts). The same cascade exists in production via REST clients that follow this rule literally.
Empirical findings
Verified by running an in-process experiment that forces
httpx.ConnectError/httpx.ConnectTimeout/ honest upstream 502 againstmake_blockscout_request(production defaultbs_request_max_retries = 3):ConnectError) — retries exhausted{\"error\":...}isError:true, bare exception textConnectTimeout) — retries exhaustedisError:true, bare exception textisError:true,\"502 Bad Gateway - Details: ...\"Two key consequences for the skill:
Native MCP clients never see a 5xx HTTP status. Tool errors are always wrapped as
{\"isError\": true, \"content\": [{\"type\": \"text\", \"text\": \"Error executing tool <name>: <str(exception)>\"}]}at HTTP 200. The current rule "retry on 5xx" is structurally inapplicable in MCP mode — an agent has to reason about retryability from the error text, not from a status code.REST scripts following the rule literally produce a 3×3=9 cascade for transport failures. A
ConnectErrorexhausts the server's 3 internal retries → surfaces as 500; aConnectTimeoutsurfaces as 504. Both are 5xx → the script's "retry 3 times" rule fires on top → 9 total attempts for a single transport-level event. The script has no reliable way to distinguish "already-retried transport failure" from "honest upstream 5xx" via status code alone.Proposed change
Split § "Transient errors" into two access-method-specific subsections rather than one unified rule.
Native MCP (agents)
isError: truewith text content; no HTTP status indicator.REST API (scripts)
413fordirect_api_calloversize).Related work
SPEC.md§ 7 "HTTP Request Robustness" documents the internal retry policy on the server side; a parallel update there is being planned.mcp-server/API.md§ "Error Handling".Versioning
This is a skill content change; will need a
bump-skill-versiononce content is finalized.