|
1 | 1 | (ns eca.features.mcp |
2 | 2 | (:require |
3 | 3 | [cheshire.core :as json] |
4 | | - [eca.logger :as logger]) |
| 4 | + [clojure.java.io :as io] |
| 5 | + [clojure.string :as string] |
| 6 | + [eca.logger :as logger] |
| 7 | + [eca.shared :as shared]) |
5 | 8 | (:import |
6 | 9 | [com.fasterxml.jackson.databind ObjectMapper] |
7 | 10 | [io.modelcontextprotocol.client McpClient McpSyncClient] |
|
21 | 24 |
|
22 | 25 | (def ^:private logger-tag "[MCP]") |
23 | 26 |
|
24 | | -(defn ^:private ->transport ^McpTransport [{:keys [command args env]}] |
25 | | - (StdioClientTransport. |
26 | | - (-> (ServerParameters/builder ^String command) |
27 | | - (.args ^List args) |
28 | | - (.env (update-keys env name)) |
29 | | - (.build)))) |
| 27 | +(def ^:private env-var-regex |
| 28 | + #"\$(\w+)|\$\{([^}]+)\}") |
| 29 | + |
| 30 | +(defn ^:private replace-env-vars [s] |
| 31 | + (let [env (System/getenv)] |
| 32 | + (string/replace s |
| 33 | + env-var-regex |
| 34 | + (fn [[_ var1 var2]] |
| 35 | + (or (get env (or var1 var2)) |
| 36 | + (str "$" var1) |
| 37 | + (str "${" var2 "}")))))) |
| 38 | + |
| 39 | +(defn ^:private ->transport ^McpTransport [{:keys [command args env]} workspaces] |
| 40 | + (let [command ^String (replace-env-vars command) |
| 41 | + b (ServerParameters/builder command) |
| 42 | + b (if args |
| 43 | + (.args b ^List (mapv replace-env-vars (or args []))) |
| 44 | + b) |
| 45 | + b (if env |
| 46 | + (.env b (update-keys env name)) |
| 47 | + b) |
| 48 | + pb-init-args []] |
| 49 | + (proxy [StdioClientTransport] [(.build b)] |
| 50 | + (getProcessBuilder [] (-> (ProcessBuilder. ^List pb-init-args) |
| 51 | + ;; TODO we are hard coding the first workspace |
| 52 | + (.directory (io/file (shared/uri->filename (:uri (first workspaces)))))))))) |
30 | 53 |
|
31 | 54 | (defn ^:private ->client ^McpSyncClient [transport config] |
32 | 55 | (-> (McpClient/sync transport) |
|
37 | 60 | (.build))) |
38 | 61 |
|
39 | 62 | (defn initialize! [{:keys [on-error]} db* config] |
40 | | - (doseq [[name server-config] (:mcpServers config)] |
41 | | - (try |
42 | | - (when-not (and (get-in @db* [:mcp-clients name]) |
43 | | - (get server-config :disabled false)) |
44 | | - (let [transport (->transport server-config) |
45 | | - client (->client transport config)] |
46 | | - (swap! db* assoc-in [:mcp-clients name :client] client) |
47 | | - (doseq [{:keys [name uri]} (:workspace-folders @db*)] |
48 | | - (.addRoot client (McpSchema$Root. uri name))) |
49 | | - (.initialize client))) |
50 | | - (catch Exception e |
51 | | - (logger/warn logger-tag (format "Could not initialize MCP server %s. Error: %s" name (.getMessage e))) |
52 | | - (on-error name e))))) |
| 63 | + (let [workspaces (:workspace-folders @db*)] |
| 64 | + (doseq [[name server-config] (:mcpServers config)] |
| 65 | + (try |
| 66 | + (when-not (and (get-in @db* [:mcp-clients name]) |
| 67 | + (get server-config :disabled false)) |
| 68 | + (let [transport (->transport server-config workspaces) |
| 69 | + client (->client transport config)] |
| 70 | + (swap! db* assoc-in [:mcp-clients name :client] client) |
| 71 | + (doseq [{:keys [name uri]} workspaces] |
| 72 | + (.addRoot client (McpSchema$Root. uri name))) |
| 73 | + (.initialize client))) |
| 74 | + (catch Exception e |
| 75 | + (logger/warn logger-tag (format "Could not initialize MCP server %s. Error: %s" name (.getMessage e))) |
| 76 | + (on-error name e)))))) |
53 | 77 |
|
54 | 78 | (defn tools-cached? [db] |
55 | 79 | (boolean (:mcp-tools db))) |
|
0 commit comments