Skip to content

Commit affcf86

Browse files
committed
Add timeout to MCP tools/prompts/resources listing
Servers that advertise a capability but never answer the list request (e.g. prompts/list) parked the init thread forever on an untimed deref, leaving the server stuck in starting state. Pass mcpTimeoutSeconds to the plumcp list calls and fall back to an empty list on timeout.
1 parent aac5184 commit affcf86

2 files changed

Lines changed: 23 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Apply `mcpTimeoutSeconds` to MCP tools/prompts/resources listing, avoiding servers stuck in `starting` state when a list request never gets a response.
6+
57
## 0.153.0
68

79
- Store chats in per-chat cache files with a lazy-loaded index instead of one whole-workspace blob, fixing CPU spikes and slow startup as history grows; legacy caches migrate automatically. #557

src/eca/features/tools/mcp.clj

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -326,22 +326,33 @@
326326
(logger/warn logger-tag (format "Could not list %s: %s" kind (format-jsonrpc-error jsonrpc-error)))
327327
[]))
328328

329-
(defn ^:private list-server-tools [client]
329+
(defn ^:private on-list-timeout [kind]
330+
(fn [_id _timeout-value]
331+
(logger/warn logger-tag (format "Timeout waiting server response listing %s" kind))
332+
[]))
333+
334+
(defn ^:private list-options [kind timeout-ms]
335+
{:on-error (on-list-error kind)
336+
:timeout-millis timeout-ms
337+
:timeout-value ::list-timeout
338+
:on-timeout (on-list-timeout kind)})
339+
340+
(defn ^:private list-server-tools [client timeout-ms]
330341
(if (get-in (pmc/get-initialize-result client) [:capabilities :tools])
331-
(or (some->> (pmc/list-tools client {:on-error (on-list-error "tools")})
342+
(or (some->> (pmc/list-tools client (list-options "tools" timeout-ms))
332343
(mapv tool->internal))
333344
[])
334345
[]))
335346

336-
(defn ^:private list-server-prompts [client]
347+
(defn ^:private list-server-prompts [client timeout-ms]
337348
(if (get-in (pmc/get-initialize-result client) [:capabilities :prompts])
338-
(or (pmc/list-prompts client {:on-error (on-list-error "prompts")})
349+
(or (pmc/list-prompts client (list-options "prompts" timeout-ms))
339350
[])
340351
[]))
341352

342-
(defn ^:private list-server-resources [client]
353+
(defn ^:private list-server-resources [client timeout-ms]
343354
(if (get-in (pmc/get-initialize-result client) [:capabilities :resources])
344-
(or (pmc/list-resources client {:on-error (on-list-error "resources")})
355+
(or (pmc/list-resources client (list-options "resources" timeout-ms))
345356
[])
346357
[]))
347358

@@ -453,6 +464,7 @@
453464
server-config
454465
{:on-server-updated on-server-updated})
455466
(let [init-timeout (:mcpTimeoutSeconds config)
467+
list-timeout-ms (* 1000 init-timeout)
456468
pending-tools-refresh* (atom nil)
457469
on-tools-change (fn [tools]
458470
(let [tools (mapv tool->internal tools)]
@@ -479,9 +491,9 @@
479491
http-client (assoc :http-client http-client)))
480492
(swap! db* assoc-in [:mcp-clients name :version] version)
481493
(swap! db* assoc-in [:mcp-clients name :instructions] (:instructions init-result))
482-
(swap! db* assoc-in [:mcp-clients name :tools] (list-server-tools client))
483-
(swap! db* assoc-in [:mcp-clients name :prompts] (list-server-prompts client))
484-
(swap! db* assoc-in [:mcp-clients name :resources] (list-server-resources client))
494+
(swap! db* assoc-in [:mcp-clients name :tools] (list-server-tools client list-timeout-ms))
495+
(swap! db* assoc-in [:mcp-clients name :prompts] (list-server-prompts client list-timeout-ms))
496+
(swap! db* assoc-in [:mcp-clients name :resources] (list-server-resources client list-timeout-ms))
485497
(if (and needs-reinit?* @needs-reinit?*)
486498
(do (try (pp/stop-client-transport! transport false) (catch Exception _))
487499
(if (< attempt max-init-retries)

0 commit comments

Comments
 (0)