Skip to content

MCP remote client has no transport-level retry on socket/connection errors #25287

Closed as not planned

Description

@flupkede

Problem

When a remote MCP server (type: "remote" with StreamableHTTPClientTransport) becomes temporarily unreachable — e.g. the server process restarts, the laptop suspends/resumes, or a TCP keep-alive goes stale — the MCP client has no recovery mechanism.

What happens today

  1. Server restarts → all in-memory sessions lost
  2. Client sends a tools/call → @modelcontextprotocol/sdk's internal reqwest pool has a dead keep-alive connection
  3. Socket-level error occurs (not even an HTTP 404) — the request never reaches the server
  4. client.callTool() throws an error
  5. In packages/opencode/src/mcp/index.ts, the convertMcpTool execute function has no catch/retry logic — the error propagates to Effect.catch which logs it and returns undefined
  6. The MCP server is marked as "failed" and never reconnects

Why server-side middleware can't fix this

  • Socket errors never reach the server. The client's HTTP library fails before sending a request.
  • Even if the request does reach the server (e.g. HTTP 404 for stale session), the server can't tell the client's internal SDK state about a new session ID — that state lives inside @modelcontextprotocol/sdk's transport layer.

Expected behavior

When a tool call fails due to a transport error, the MCP client should:

  1. Detect that the connection is dead (socket error, ECONNRESET, etc.)
  2. Close the old transport/client
  3. Create a new transport and reconnect (re-initialize)
  4. Retry the original tool call with the new session

Suggested fix

In convertMcpTool (packages/opencode/src/mcp/index.ts), wrap the execute function with transport-level retry:

execute: async (args: unknown) => {
  try {
    return await client.callTool(
      { name: mcpTool.name, arguments: (args || {}) as Record<string, unknown> },
      CallToolResultSchema,
      { resetTimeoutOnProgress: true, timeout },
    )
  } catch (e) {
    // If this is a transport-level error, try reconnecting once
    if (isTransportError(e) && clientKey && mcpConfig) {
      log.warn("MCP transport error, attempting reconnect", { clientKey, error: e.message })
      try {
        await client.close()
        const result = await createAndStore(clientKey, { ...mcpConfig, enabled: true })
        if (result.status === "connected" && state.clients[clientKey]) {
          return await state.clients[clientKey].callTool(
            { name: mcpTool.name, arguments: (args || {}) as Record<string, unknown> },
            CallToolResultSchema,
            { resetTimeoutOnProgress: true, timeout },
          )
        }
      } catch (retryError) {
        log.error("MCP reconnect failed", { clientKey, error: retryError })
      }
    }
    throw e
  }
}

A simpler alternative: use the existing connect function to re-establish the connection, leveraging the transport fallback chain (StreamableHTTP → SSE).

Environment

  • OpenCode: latest (v1.x)
  • MCP SDK: @modelcontextprotocol/sdk
  • MCP Server: codesearch serve (streamable HTTP)
  • OS: Windows (but issue applies to all platforms — any server restart triggers it)

Activity

  1. added
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 1, 2026
  2. github-actions commented on May 1, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of existing issues. Please check:

    If your issue is distinct from all of the above (e.g. specifically about socket-level errors before the request reaches the server, rather than session invalidation), please clarify the difference in your report.

  3. flupkede commented on May 1, 2026

    @flupkede
    Author

    @rekram1-node I'm happy to update it. Just let me know.

  4. flupkede commented on May 1, 2026

    @flupkede
    Author

    Relationship to existing issues

    Thank you for the pointer — this is indeed related but distinct from the existing issues:

    Key distinction: two failure modes

    Failure mode Request reaches server? Current behavior Fix location
    Stale session ID (404) ✅ Yes SDK throws, no retry Could be server-side (strip header) OR client-side (reconnect)
    Dead TCP connection (socket error) ❌ No SDK throws at transport level, no retry Must be client-side

    After investigating the @modelcontextprotocol/sdk source and OpenCode's convertMcpTool in detail, the fix belongs in OpenCode's tools() function — wrapping convertMcpTool with transport-aware retry logic that leverages the existing connect() → createAndStore() path.

    Happy to submit a PR if maintainers agree on the approach.

  5. removed
    coreAnything pertaining to core functionality of the application (opencode server stuff)
    on May 3, 2026
  6. water-in-stone commented on May 4, 2026

    @water-in-stone

    Thanks to your analysis, I have created a MR with thorough testing. @flupkede

  7. water-in-stone commented on May 4, 2026

    @water-in-stone

    @jlongster I have created a MR with thorough testing. PTAL.

  8. github-actions commented on Aug 28, 2026

    @github-actions
    Contributor

    To stay organized issues are automatically closed after 60 days of no activity. If the issue is still relevant please open a new one.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions