Skip to content

proxy: in-flight reconnect retry can double-execute a side-effecting tools/call (at-least-once) #17

Description

@saurabhjain1592

Follow-up from #16 (proxy stdio reconnect)

The reconnect fix (#16) adds a transparent in-flight retry: when a tools/call sees a dropped backend session mid-call, the proxy reconnects and re-issues the same request once (backend.go:~449-465).

Gap

conn.call returns the dropped-session error on the <-c.closed branch without distinguishing:

  • request never reached the backend (safe to retry), vs
  • request executed on the backend, but the process died before the response was read (NOT safe to retry).

So for a side-effecting tool (payment, send, delete, write SQL), the retry can cause double execution. MCP has no idempotency key, so this is at-least-once by construction.

For the current BukuWarung demo this is harmless (tools are reads: getUserInfo, lookup_customer, get_sales_summary, run_sql_report). It becomes real once write/side-effecting tools are governed.

Fix direction

  • Gate the auto-retry to pre-dispatch failures only: retry iff the request was not yet written to the backend (connect/handshake failure, or write that provably didn't complete). If the request was already written, surface the retryable error to the client instead of silently re-executing — i.e. at-most-once for dispatched calls.
  • Until then: document the at-least-once caveat in the README reconnect-contract section (tracked alongside the fix(proxy): reconnect to a restarted backend stdio MCP server #16 README update).

DoD

  • In-flight retry only fires when the request provably did not reach the backend
  • Already-dispatched calls return a clean retryable error on drop (no silent re-exec)
  • Test: dispatched-then-died path does NOT double-execute
  • README at-least-once caveat (interim) — or removed once at-most-once lands

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    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