Skip to content

Commit 830a017

Browse files
committed
Improve server shutdown speed for remote MCP servers
1 parent 7a79fe1 commit 830a017

3 files changed

Lines changed: 49 additions & 31 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
# Changelog
22

33
## Unreleased
4-
- Show the selected `variant` in `spawn_agent` subagent details and harden restrictions regarding the use of the optional model in sub-agent. #369
54

5+
- Show the selected `variant` in `spawn_agent` subagent details and harden restrictions regarding the use of the optional model in sub-agent. #369
66
- Fix MCP OAuth browser not opening on Windows by using `cmd /c start` instead of `java.awt.Desktop`, which is unavailable in the native image.
7+
- Improve server shutdown speed for remote MCP servers.
78

89
## 0.117.1
910

src/eca/features/tools/mcp.clj

Lines changed: 43 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -70,19 +70,22 @@
7070
ssl-ctx network/*ssl-context*
7171
rm (fn [request]
7272
(-> request
73+
(assoc :timeout-millis 30000)
7374
(update :headers merge
7475
(into {} (map (fn [[k v]]
7576
[(name k) (replace-env-vars (str v))]))
7677
config-headers))
7778
(update :headers merge
7879
(when-let [access-token (get-in @db* [:mcp-auth server-name :access-token])]
7980
{"Authorization" (str "Bearer " access-token)}))))
80-
hc (phc/make-http-client url (cond-> {:request-middleware rm}
81+
hc (phc/make-http-client url (cond-> {:request-middleware rm
82+
:timeout-millis 10000}
8183
ssl-ctx (assoc :ssl-context ssl-ctx)))]
8284
(when (string/includes? url "/sse")
8385
(logger/warn logger-tag (format "SSE transport is no longer supported for server '%s'. Using Streamable HTTP instead. Consider updating the URL." server-name)))
8486
(logger/info logger-tag (format "Creating HTTP transport for server '%s' at %s" server-name url))
8587
{:transport (phct/make-streamable-http-transport hc)
88+
:http-client hc
8689
:needs-reinit?* needs-reinit?*})
8790

8891
;; STDIO transport
@@ -336,15 +339,16 @@
336339
(swap! db* assoc-in [:mcp-clients name :tools] tools)
337340
(on-server-updated (->server name server-config :running @db*))))]
338341
(loop [attempt 1]
339-
(let [{:keys [transport needs-reinit?*]} (->transport name server-config workspaces db*)
342+
(let [{:keys [transport http-client needs-reinit?*]} (->transport name server-config workspaces db*)
340343
result (try
341344
(let [client (->client name transport init-timeout workspaces
342345
{:on-tools-change on-tools-change})
343346
init-result (pmc/get-initialize-result client)
344347
version (get-in init-result [:serverInfo :version])]
345-
(swap! db* assoc-in [:mcp-clients name] {:client client
346-
:status :starting
347-
:needs-reinit?* needs-reinit?*})
348+
(swap! db* assoc-in [:mcp-clients name] (cond-> {:client client
349+
:status :starting
350+
:needs-reinit?* needs-reinit?*}
351+
http-client (assoc :http-client http-client)))
348352
(swap! db* assoc-in [:mcp-clients name :version] version)
349353
(swap! db* assoc-in [:mcp-clients name :instructions] (:instructions init-result))
350354
(swap! db* assoc-in [:mcp-clients name :tools] (list-server-tools client))
@@ -422,14 +426,16 @@
422426
(def ^:private disconnect-timeout-ms 5000)
423427

424428
(defn stop-server! [name db* config {:keys [on-server-updated]}]
425-
(when-let [{:keys [client]} (get-in @db* [:mcp-clients name])]
429+
(when-let [{:keys [client http-client]} (get-in @db* [:mcp-clients name])]
426430
(let [server-config (get-in config [:mcpServers name])]
427431
(swap! db* assoc-in [:mcp-clients name :status] :stopping)
428432
(on-server-updated (->server name server-config :stopping @db*))
429433
(let [f (future (try (pmc/disconnect! client) (catch Exception _ nil)))]
430434
(when-not (deref f disconnect-timeout-ms nil)
431435
(logger/warn logger-tag (format "Timeout disconnecting MCP server %s, forcing transport stop" name))
432-
(try (pp/stop-client-transport! client false) (catch Exception _))))
436+
(if http-client
437+
(try (pp/stop! http-client) (catch Exception _))
438+
(try (pp/stop-client-transport! (pcs/?transport client) false) (catch Exception _)))))
433439
(swap! db* assoc-in [:mcp-clients name :status] :stopped)
434440
(on-server-updated (->server name server-config :stopped @db*))
435441
(swap! db* update :mcp-clients dissoc name)
@@ -571,7 +577,7 @@
571577
gone server-side), then runs a fresh initialize-server! cycle."
572578
[server-name old-client db* config metrics]
573579
(logger/info logger-tag (format "Re-initializing MCP server '%s'" server-name))
574-
(try (pp/stop-client-transport! old-client false) (catch Exception _))
580+
(try (pp/stop-client-transport! (pcs/?transport old-client) false) (catch Exception _))
575581
(swap! db* update :mcp-clients dissoc server-name)
576582
(initialize-server! server-name db* config metrics (constantly nil)))
577583

@@ -703,29 +709,39 @@
703709

704710
(defn shutdown!
705711
"Shutdown MCP servers: interrupts in-flight init threads and disconnects
706-
running clients in parallel with a total 5s timeout."
712+
running clients in parallel with a total 5s timeout.
713+
HTTP clients are force-stopped immediately (skipping the slow DELETE handshake),
714+
while stdio clients go through graceful disconnect with a timeout fallback."
707715
[db*]
708716
;; 1. Interrupt any servers still initializing so they unblock promptly
709717
(interrupt-init-threads!)
710-
;; 2. Disconnect running clients in parallel via daemon threads
718+
;; 2. Disconnect running clients
711719
(try
712720
(let [clients (vals (:mcp-clients @db*))
713-
latch (java.util.concurrent.CountDownLatch. (count clients))
714-
threads (doall
715-
(map (fn [{:keys [client]}]
716-
(doto (Thread.
717-
(fn []
718-
(try
719-
(pmc/disconnect! client)
720-
(catch Exception _)
721-
(finally
722-
(.countDown latch)))))
723-
(.setDaemon true)
724-
(.start)))
725-
clients))]
726-
(when-not (.await latch disconnect-timeout-ms java.util.concurrent.TimeUnit/MILLISECONDS)
727-
(logger/warn logger-tag "Some MCP servers did not disconnect within timeout, forcing stop")
728-
(doseq [^Thread t threads]
729-
(.interrupt t))))
721+
;; HTTP clients: force-stop immediately (the DELETE in disconnect! always
722+
;; times out because the server is slow to respond, and we're shutting down anyway)
723+
http-clients (filter :http-client clients)
724+
;; stdio clients: graceful disconnect with timeout
725+
stdio-clients (remove :http-client clients)]
726+
(doseq [{:keys [http-client]} http-clients]
727+
(try (pp/stop! http-client) (catch Exception _)))
728+
(when (seq stdio-clients)
729+
(let [latch (java.util.concurrent.CountDownLatch. (count stdio-clients))
730+
threads (doall
731+
(map (fn [{:keys [client]}]
732+
(doto (Thread.
733+
(fn []
734+
(try
735+
(pmc/disconnect! client)
736+
(catch Exception _)
737+
(finally
738+
(.countDown latch)))))
739+
(.setDaemon true)
740+
(.start)))
741+
stdio-clients))]
742+
(when-not (.await latch disconnect-timeout-ms java.util.concurrent.TimeUnit/MILLISECONDS)
743+
(logger/warn logger-tag "Some MCP servers did not disconnect within timeout, forcing stop")
744+
(doseq [{:keys [client]} stdio-clients]
745+
(try (pp/stop-client-transport! (pcs/?transport client) false) (catch Exception _)))))))
730746
(catch Exception _ nil))
731747
(swap! db* assoc :mcp-clients {}))

src/eca/server.clj

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,10 @@
2727
(defn ^:private exit [server]
2828
(metrics/task
2929
:eca/exit
30-
(when-let [rs @remote-server*]
31-
(remote.server/stop! rs))
32-
(jsonrpc.server/shutdown server) ;; blocks, waiting up to 10s for previously received messages to be processed
30+
(let [remote-stop-f (when-let [rs @remote-server*]
31+
(future (remote.server/stop! rs)))]
32+
(jsonrpc.server/shutdown server) ;; blocks, waiting up to 10s for previously received messages to be processed
33+
(some-> remote-stop-f deref))
3334
(shutdown-agents)
3435
(System/exit 0)))
3536

0 commit comments

Comments
 (0)