Skip to content

fix: MCP safe reads do not retry transport failures or preserve diagnostics #4

Description

@viowai

What Happened

@everme/agent-sdk@0.3.4 documents one retry for safe transport failures, but the retry branch is unreachable for native fetch failures:

  1. execOnce() catches the native failure and wraps it in EvermeError.
  2. execWithRetry() immediately rethrows every EvermeError.
  3. A synthetic local socket-reset probe therefore observes one request, not two.

There is a second semantic gap: /mem/search and /mem/context are safe reads implemented as POST, so method-only retry gating cannot cover them. When the error reaches @everme/memory-mcp, the adapter returns only error: <message> and drops correlation/classification data that an MCP host could use for health decisions.

This is separate from EverMind-AI/EverOS#338, which tracks ingestion task metadata and exact visibility verification.

Steps To Reproduce

  1. Use @everme/agent-sdk@0.3.4 or current main at 0e6fd0ec90a2cf8a90946f434504b6813826eb1a.
  2. Point createClient() at a local HTTP server that resets the first GET connection and succeeds on the second.
  3. Call client.request("GET", "/healthz").
  4. Observe that the server receives one request and the caller receives EvermeError immediately.

The same dead path applies to DNS/connection errors. POST /mem/search and POST /mem/context are never eligible under the current method-only check.

Expected Behavior

  • Explicit semantic reads (GET/HEAD, plus POST /mem/search and /mem/context) should make at most one bounded retry for transient transport failures, HTTP 429, and HTTP 5xx.
  • All attempts and Retry-After delay should share the original timeout budget.
  • Non-idempotent memory writes must remain single-attempt, even if a caller mislabels one as a read.
  • Exhausted errors should expose redacted fields such as classification, cause code, HTTP status, request id, attempts, retryability, and elapsed time.
  • MCP tool failures should preserve those fields in structuredContent.error while retaining the existing isError text response.
  • Diagnostics must not include queries, request bodies, URLs, tokens, or native fetch cause text.

Environment

  • Package: @everme/agent-sdk@0.3.4, @everme/memory-mcp@0.3.4
  • Runtime: Node.js 18+
  • Reproduction: synthetic localhost transport failure; no production data involved

Proposed Fix

  • Add explicit safe_read / non_idempotent_write request semantics.
  • Allow POST safe-read opt-in only for a hard allowlist of /mem/search and /mem/context.
  • Retry safe reads once within a shared deadline; do not start a second full timeout.
  • Preserve structured, sanitized EvermeError metadata through the MCP adapter.
  • Add regression coverage for socket reset, DNS failure, timeout budget, 429, 5xx, 401/auth classification, and write single-attempt behavior.

Logs

No production logs are attached. The reproduction uses placeholder credentials and a local synthetic endpoint only.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions