Skip to content

Commit fd32d63

Browse files
committed
Improve MCP handling for anthropic
1 parent d9de1d8 commit fd32d63

File tree

5 files changed

+69
-36
lines changed

5 files changed

+69
-36
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Allow comments on `json` configs.
66
- Improve MCP tool call feedback.
7+
- Add support for env vars in mcp configs.
78

89
## 0.0.4
910

src/eca/features/mcp.clj

Lines changed: 44 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
(ns eca.features.mcp
22
(:require
33
[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])
58
(:import
69
[com.fasterxml.jackson.databind ObjectMapper]
710
[io.modelcontextprotocol.client McpClient McpSyncClient]
@@ -21,12 +24,32 @@
2124

2225
(def ^:private logger-tag "[MCP]")
2326

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))))))))))
3053

3154
(defn ^:private ->client ^McpSyncClient [transport config]
3255
(-> (McpClient/sync transport)
@@ -37,19 +60,20 @@
3760
(.build)))
3861

3962
(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))))))
5377

5478
(defn tools-cached? [db]
5579
(boolean (:mcp-tools db)))

src/eca/llm_api.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
:api-key (:anthropicApiKey config)}
6565
{:on-message-received on-message-received-wrapper
6666
:on-error on-error
67+
:on-prepare-tool-call on-prepare-tool-call
6768
:on-tool-called on-tool-called})
6869

6970
(string/starts-with? model config/ollama-model-prefix)

src/eca/llm_providers/anthropic.clj

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,11 @@
3030

3131
(defn ^:private base-request! [{:keys [body api-key on-error on-response]}]
3232
(let [api-key (or api-key
33-
(System/getenv "ANTHROPIC_API_KEY"))]
33+
(System/getenv "ANTHROPIC_API_KEY"))
34+
url (url messages-path)]
35+
(logger/debug logger-tag (format "Sending body: '%s', url: '%s'" body url))
3436
(http/post
35-
(url messages-path)
37+
url
3638
{:headers {"x-api-key" api-key
3739
"anthropic-version" "2023-06-01"
3840
"Content-Type" "application/json"}
@@ -59,9 +61,8 @@
5961
api-key past-messages tools web-search]
6062
:or {max-tokens 1024
6163
temperature 1.0}}
62-
{:keys [on-message-received on-error on-tool-called]}]
63-
(let [messages (conj past-messages {:role "user" :content user-prompt})
64-
_ (logger/debug logger-tag (format "Sending messages: '%s' system: '%s'" messages context))
64+
{:keys [on-message-received on-error on-prepare-tool-call on-tool-called]}]
65+
(let [messages (vec (conj past-messages {:role "user" :content user-prompt}))
6566
body {:model model
6667
:messages messages
6768
:max_tokens max-tokens
@@ -75,27 +76,37 @@
7576
(fn handle-response [event data]
7677
(llm-util/log-response logger-tag event data)
7778
(case event
79+
"content_block_start" (case (-> data :content_block :type)
80+
"tool_use" (do
81+
(on-prepare-tool-call {:name (-> data :content_block :name)
82+
:id (-> data :content_block :id)
83+
:argumentsText ""})
84+
(swap! content-block* assoc (:index data) (:content_block data)))
85+
86+
nil)
7887
"content_block_delta" (case (-> data :delta :type)
7988
"text_delta" (on-message-received {:type :text
8089
:text (-> data :delta :text)})
81-
"input_json_delta" (swap! content-block* update-in [(:index data) :input-json] str (-> data :delta :partial_json))
90+
"input_json_delta" (let [text (-> data :delta :partial_json)
91+
_ (swap! content-block* update-in [(:index data) :input-json] str text)
92+
content-block (get @content-block* (:index data))]
93+
(on-prepare-tool-call {:name (:name content-block)
94+
:id (:id content-block)
95+
:argumentsText text}))
8296
"citations_delta" (case (-> data :delta :citation :type)
8397
"web_search_result_location" (on-message-received
8498
{:type :url
8599
:title (-> data :delta :citation :title)
86100
:url (-> data :delta :citation :url)})
87101
nil)
88102
(logger/warn "Unkown response delta type" (-> data :delta :type)))
89-
"content_block_start" (case (-> data :content_block :type)
90-
"tool_use" (swap! content-block* assoc (:index data) (:content_block data))
91-
92-
nil)
93103
"message_delta" (case (-> data :delta :stop_reason)
94104
"tool_use" (doseq [content-block (vals @content-block*)]
95105
(when (= "tool_use" (:type content-block))
96106
(let [function-name (:name content-block)
97107
function-args (:input-json content-block)
98-
response (on-tool-called {:name function-name
108+
response (on-tool-called {:id (:id content-block)
109+
:name function-name
99110
:arguments (json/parse-string function-args)})
100111
messages (concat messages
101112
[{:role "assistant"

src/eca/llm_providers/openai.clj

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,7 @@
2323
(let [api-key (or api-key
2424
(System/getenv "OPENAI_API_KEY"))
2525
url (url responses-path)]
26-
(logger/debug logger-tag (format "Sending input: '%s' instructions: '%s' tools: '%s' url: '%s'"
27-
(:input body)
28-
(:instructions body)
29-
(:tools body)
30-
url))
26+
(logger/debug logger-tag (format "Sending body: '%s', url: '%s'" body url))
3127
(http/post
3228
url
3329
{:headers {"Authorization" (str "Bearer " api-key)

0 commit comments

Comments
 (0)