From 937032c7903eb537e071757f0af5453dd166bc41 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Mon, 23 Mar 2026 16:27:37 +0100 Subject: [PATCH] :sparkles: Allow for reconnections to MCP server --- frontend/src/app/main/data/workspace.cljs | 2 +- frontend/src/app/main/data/workspace/mcp.cljs | 66 +++++++++++++++---- .../src/app/main/ui/workspace/main_menu.cljs | 8 +-- mcp/packages/plugin/src/main.ts | 8 ++- mcp/packages/server/src/PluginBridge.ts | 1 + 5 files changed, 66 insertions(+), 19 deletions(-) diff --git a/frontend/src/app/main/data/workspace.cljs b/frontend/src/app/main/data/workspace.cljs index fa2e04fe9d..f1c54caf48 100644 --- a/frontend/src/app/main/data/workspace.cljs +++ b/frontend/src/app/main/data/workspace.cljs @@ -377,7 +377,7 @@ (rx/map deref) (rx/mapcat (fn [value] (rx/of (mcp/update-mcp-connection value) - (mcp/disconnect-mcp)))))) + (mcp/user-disconnect-mcp)))))) (when (contains? cf/flags :mcp) (->> mbc/stream diff --git a/frontend/src/app/main/data/workspace/mcp.cljs b/frontend/src/app/main/data/workspace/mcp.cljs index a9198750c7..50b70fee43 100644 --- a/frontend/src/app/main/data/workspace/mcp.cljs +++ b/frontend/src/app/main/data/workspace/mcp.cljs @@ -17,9 +17,12 @@ [app.main.store :as st] [app.plugins.register :refer [mcp-plugin-id]] [app.util.i18n :refer [tr]] + [app.util.timers :as ts] [beicon.v2.core :as rx] [potok.v2.core :as ptk])) +(def retry-interval 10000) + (log/set-level! :info) (def ^:private default-manifest @@ -34,13 +37,53 @@ "comment:read" "comment:write" "content:write" "content:read"}}) +(defonce interval-sub (atom nil)) + (defn finalize-workspace? [event] (= (ptk/type event) :app.main.data.workspace/finalize-workspace)) -(defn disconnect-mcp +(defn set-mcp-active + [value] + (ptk/reify ::set-mcp-active + ptk/UpdateEvent + (update [_ state] + (assoc-in state [:workspace-local :mcp :active] value)))) + +(defn start-reconnect-watcher! [] - (st/emit! (ptk/data-event ::disconnect))) + (st/emit! (set-mcp-active true)) + (when (nil? @interval-sub) + (reset! + interval-sub + (ts/interval + retry-interval + (fn [] + ;; Try to reconnect if active and not connected + (when-not (contains? #{"connecting" "connected"} + (-> @st/state :workspace-local :mcp :connection)) + (.log js/console "Reconnecting to MCP...") + (st/emit! (ptk/data-event ::connect)))))))) + +(defn stop-reconnect-watcher! + [] + (st/emit! (set-mcp-active false)) + (when @interval-sub + (rx/dispose! @interval-sub) + (reset! interval-sub nil))) + +;; This event will arrive when the user selects disconnect on the menu +;; or there is a broadcast message for disconnection +(defn user-disconnect-mcp + [] + (ptk/reify ::remote-disconnect-mcp + ptk/WatchEvent + (watch [_ _ _] + (rx/of (ptk/data-event ::disconnect))) + + ptk/EffectEvent + (effect [_ _ _] + (stop-reconnect-watcher!)))) (defn connect-mcp [] @@ -58,12 +101,13 @@ (ptk/reify ::manage-mcp-notification ptk/WatchEvent (watch [_ state _] - (let [mcp-connected? (true? (-> state :workspace-local :mcp :connection)) + (let [mcp-connected? (= "connected" (-> state :workspace-local :mcp :connection)) mcp-enabled? (true? (-> state :profile :props :mcp-enabled)) num-sessions (-> state :workspace-presence count) - multi-session? (> num-sessions 1)] + multi-session? (> num-sessions 1) + mcp-active? (-> state :workspace-local :mcp :active)] (if (and mcp-enabled? multi-session?) - (if mcp-connected? + (if (or mcp-connected? mcp-active?) (rx/of (ntf/hide)) (rx/of (ntf/dialog :content (tr "notifications.mcp.active-in-another-tab") :cancel {:label (tr "labels.dismiss") @@ -76,6 +120,7 @@ ::ev/origin "workspace-notification"}))}))) (rx/of (ntf/hide))))))) +;; This event will arrive when the mcp is enabled in the main menu (defn update-mcp-status [value] (ptk/reify ::update-mcp-status @@ -121,13 +166,10 @@ :getServerUrl #(str cf/mcp-ws-uri) :setMcpStatus (fn [status] - (let [mcp-connection (case status - "connected" true - "disconnected" false - "error" nil - "")] - (st/emit! (update-mcp-connection mcp-connection)) - (log/info :hint "MCP STATUS" :status status))) + (when (= status "connected") + (start-reconnect-watcher!)) + (st/emit! (update-mcp-connection status)) + (log/info :hint "MCP STATUS" :status status)) :on (fn [event cb] diff --git a/frontend/src/app/main/ui/workspace/main_menu.cljs b/frontend/src/app/main/ui/workspace/main_menu.cljs index 223e30ce50..bdaf205ddd 100644 --- a/frontend/src/app/main/ui/workspace/main_menu.cljs +++ b/frontend/src/app/main/ui/workspace/main_menu.cljs @@ -750,7 +750,7 @@ workspace-local (mf/deref refs/workspace-local) mcp-enabled? (true? (-> profile :props :mcp-enabled)) - mcp-connected? (true? (-> workspace-local :mcp :connection)) + mcp-connected? (= "connected" (-> workspace-local :mcp :connection)) on-nav-to-integrations (mf/use-fn @@ -769,7 +769,7 @@ (mf/use-fn (fn [] (if mcp-connected? - (st/emit! (mcp/disconnect-mcp) + (st/emit! (mcp/user-disconnect-mcp) (ptk/event ::ev/event {::ev/name "disconnect-mcp-plugin" ::ev/origin "workspace-menu"})) (st/emit! (mcp/connect-mcp) @@ -980,8 +980,8 @@ (when (contains? cf/flags :mcp) (let [mcp-enabled? (true? (-> profile :props :mcp-enabled)) mcp-connection (-> workspace-local :mcp :connection) - mcp-connected? (true? mcp-connection) - mcp-error? (nil? mcp-connection)] + mcp-connected? (= mcp-connection "connected") + mcp-error? (= mcp-connection "error")] [:> dropdown-menu-item* {:class (stl/css :base-menu-item :menu-item) :on-click on-menu-click :on-key-down (fn [event] diff --git a/mcp/packages/plugin/src/main.ts b/mcp/packages/plugin/src/main.ts index 8aad137ec3..45396f421d 100644 --- a/mcp/packages/plugin/src/main.ts +++ b/mcp/packages/plugin/src/main.ts @@ -105,8 +105,12 @@ function connectToMcpServer(baseUrl?: string, token?: string): void { updateConnectionStatus("connecting", "Connecting..."); ws.onopen = () => { - console.log("Connected to MCP server"); - updateConnectionStatus("connected", "Connected"); + setTimeout(() => { + if (ws) { + console.log("Connected to MCP server"); + updateConnectionStatus("connected", "Connected"); + } + }, 100); }; ws.onmessage = (event) => { diff --git a/mcp/packages/server/src/PluginBridge.ts b/mcp/packages/server/src/PluginBridge.ts index 10dfb5eeb9..5147d361fd 100644 --- a/mcp/packages/server/src/PluginBridge.ts +++ b/mcp/packages/server/src/PluginBridge.ts @@ -68,6 +68,7 @@ export class PluginBridge { if (this.clientsByToken.has(userToken)) { this.logger.warn("Duplicate connection for given user token; rejecting new connection"); ws.close(1008, "Duplicate connection for given user token; close previous connection first."); + return; } this.clientsByToken.set(userToken, connection);