@@ -12,6 +12,8 @@ import (
1212 "log/slog"
1313 "net"
1414 "net/url"
15+ "os"
16+ "os/exec"
1517 "strings"
1618 "sync"
1719 "time"
@@ -118,9 +120,9 @@ func NewRemoteToolset(name, urlString, transport string, headers map[string]stri
118120}
119121
120122// errServerUnavailable is returned by doStart when the MCP server could not be
121- // reached but the error is non-fatal (e.g. EOF). The toolset is considered
122- // "started" so the agent can proceed, but watchConnection must not be spawned
123- // because there is no live connection to monitor .
123+ // reached but the error is non-fatal (e.g. EOF, binary not found).
124+ // Start() propagates this so started remains false, and the agent runtime
125+ // retries via ensureToolSetsAreStarted on the next conversation turn .
124126var errServerUnavailable = errors .New ("MCP server unavailable" )
125127
126128// Describe returns a short, user-visible description of this toolset instance.
@@ -155,16 +157,11 @@ func (ts *Toolset) Start(ctx context.Context) error {
155157 return nil
156158 }
157159
158- ts .restarted = make (chan struct {})
160+ if ts .restarted == nil {
161+ ts .restarted = make (chan struct {})
162+ }
159163
160164 if err := ts .doStart (ctx ); err != nil {
161- if errors .Is (err , errServerUnavailable ) {
162- // The server is unreachable but the error is non-fatal.
163- // Mark as started so the agent can proceed; tools will simply
164- // be empty. Don't spawn a watcher — there's nothing to watch.
165- ts .started = true
166- return nil
167- }
168165 return err
169166 }
170167
@@ -240,10 +237,11 @@ func (ts *Toolset) doStart(ctx context.Context) error {
240237 //
241238 // Only retry when initialization fails due to sending the initialized notification.
242239 if ! isInitNotificationSendError (err ) {
243- if errors . Is (err , io . EOF ) {
240+ if isServerUnavailableError (err ) {
244241 slog .Debug (
245- "MCP client unavailable (EOF), skipping MCP toolset " ,
242+ "MCP client unavailable, will retry on next conversation turn " ,
246243 "server" , ts .logID ,
244+ "error" , err ,
247245 )
248246 return errServerUnavailable
249247 }
@@ -548,6 +546,15 @@ func isInitNotificationSendError(err error) bool {
548546 return false
549547}
550548
549+ // isServerUnavailableError returns true if err indicates the MCP server process
550+ // could not be reached — binary missing/not-found, or process exited immediately
551+ // before completing the MCP handshake (io.EOF). These are retryable conditions.
552+ func isServerUnavailableError (err error ) bool {
553+ return errors .Is (err , io .EOF ) ||
554+ errors .Is (err , exec .ErrNotFound ) ||
555+ errors .Is (err , os .ErrNotExist )
556+ }
557+
551558func processMCPContent (toolResult * mcp.CallToolResult ) * tools.ToolCallResult {
552559 var text strings.Builder
553560 var images , audios []tools.MediaContent
0 commit comments