Skip to content

Commit ca7d494

Browse files
authored
Merge pull request #80 from editor-code-assistant/clojure-mcp-tool-details
Support tool call details for clojure-mcp
2 parents b583f63 + 6f5b998 commit ca7d494

File tree

8 files changed

+210
-31
lines changed

8 files changed

+210
-31
lines changed

src/eca/diff.clj

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,28 @@
5555
(->> (DiffUtils/generateUnifiedDiff file file original-lines patch 3)
5656
(drop 2) ;; removes file header
5757
unlines)})))
58+
59+
(defn unified-diff-counts
60+
"Given a unified diff string, return a map with counts of added and removed lines.
61+
62+
It ignores diff headers (---, +++), hunk markers (@@), and metadata lines starting with \\."
63+
[diff-text]
64+
(let [lines (string/split-lines diff-text)]
65+
(reduce (fn [{:keys [added removed] :as acc} line]
66+
(cond
67+
(or (string/starts-with? line "---")
68+
(string/starts-with? line "+++")
69+
(string/starts-with? line "@@")
70+
(string/starts-with? line "\\")
71+
(string/blank? line))
72+
acc
73+
74+
(string/starts-with? line "+")
75+
(update acc :added inc)
76+
77+
(string/starts-with? line "-")
78+
(update acc :removed inc)
79+
80+
:else acc))
81+
{:added 0 :removed 0}
82+
lines)))

src/eca/features/chat.clj

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@
188188
(let [calls (doall
189189
(for [{:keys [id name arguments] :as tool-call} tool-calls]
190190
(let [approved?* (promise)
191-
details (f.tools/get-tool-call-details name arguments)
191+
details (f.tools/tool-call-details-before-invocation name arguments)
192192
summary (f.tools/tool-call-summary all-tools name arguments)
193193
origin (tool-name->origin name all-tools)
194194
manual-approval? (f.tools/manual-approval? name config)]
@@ -216,7 +216,8 @@
216216
(if @approved?*
217217
(do
218218
(assert-chat-not-stopped! chat-ctx)
219-
(let [result (f.tools/call-tool! name arguments @db* config messenger)]
219+
(let [result (f.tools/call-tool! name arguments @db* config messenger)
220+
details (f.tools/tool-call-details-after-invocation name arguments details result)]
220221
(add-to-history! {:role "tool_call" :content (assoc tool-call
221222
:details details
222223
:summary summary

src/eca/features/tools.clj

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
"This ns centralizes all available tools for LLMs including
33
eca native tools and MCP servers."
44
(:require
5-
[babashka.fs :as fs]
65
[clojure.string :as string]
7-
[eca.diff :as diff]
86
[eca.features.tools.editor :as f.tools.editor]
97
[eca.features.tools.filesystem :as f.tools.filesystem]
108
[eca.features.tools.mcp :as f.mcp]
9+
[eca.features.tools.mcp.clojure-mcp]
1110
[eca.features.tools.shell :as f.tools.shell]
1211
[eca.features.tools.util :as tools.util]
1312
[eca.logger :as logger]
@@ -133,29 +132,12 @@
133132
(logger/error (format "Error in tool call summary fn %s: %s" name (.getMessage e)))
134133
nil))))
135134

136-
(defn get-tool-call-details [name arguments]
137-
(case name
138-
"eca_write_file" (let [path (get arguments "path")
139-
content (get arguments "content")]
140-
(when (and path content)
141-
(let [{:keys [added removed diff]} (diff/diff "" content path)]
142-
{:type :fileChange
143-
:path path
144-
:linesAdded added
145-
:linesRemoved removed
146-
:diff diff})))
147-
("eca_plan_edit_file"
148-
"eca_edit_file") (let [path (get arguments "path")
149-
original-content (get arguments "original_content")
150-
new-content (get arguments "new_content")
151-
all? (get arguments "all_occurrences")]
152-
(when-let [{:keys [original-full-content
153-
new-full-content]} (and path (fs/exists? path) original-content new-content
154-
(f.tools.filesystem/file-change-full-content path original-content new-content all?))]
155-
(let [{:keys [added removed diff]} (diff/diff original-full-content new-full-content path)]
156-
{:type :fileChange
157-
:path path
158-
:linesAdded added
159-
:linesRemoved removed
160-
:diff diff})))
161-
nil))
135+
(defn tool-call-details-before-invocation
136+
"Return the tool call details before invoking the tool."
137+
[name arguments]
138+
(tools.util/tool-call-details-before-invocation name arguments))
139+
140+
(defn tool-call-details-after-invocation
141+
"Return the tool call details after invoking the tool."
142+
[name arguments details result]
143+
(tools.util/tool-call-details-after-invocation name arguments details result))

src/eca/features/tools/filesystem.clj

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[clojure.java.io :as io]
55
[clojure.java.shell :as shell]
66
[clojure.string :as string]
7+
[eca.diff :as diff]
78
[eca.features.tools.util :as tools.util]
89
[eca.shared :as shared]))
910

@@ -330,3 +331,32 @@
330331
:required ["path" "pattern"]}
331332
:handler #'grep
332333
:summary-fn #'grep-summary}})
334+
335+
(defmethod tools.util/tool-call-details-before-invocation :eca_edit_file [name arguments]
336+
(let [path (get arguments "path")
337+
original-content (get arguments "original_content")
338+
new-content (get arguments "new_content")
339+
all? (get arguments "all_occurrences")]
340+
(when-let [{:keys [original-full-content
341+
new-full-content]} (and path (fs/exists? path) original-content new-content
342+
(file-change-full-content path original-content new-content all?))]
343+
(let [{:keys [added removed diff]} (diff/diff original-full-content new-full-content path)]
344+
{:type :fileChange
345+
:path path
346+
:linesAdded added
347+
:linesRemoved removed
348+
:diff diff}))))
349+
350+
(defmethod tools.util/tool-call-details-before-invocation :eca_plan_edit_file [name arguments]
351+
(tools.util/tool-call-details-before-invocation :eca_edit_file name arguments))
352+
353+
(defmethod tools.util/tool-call-details-before-invocation :eca_write_file [name arguments]
354+
(let [path (get arguments "path")
355+
content (get arguments "content")]
356+
(when (and path content)
357+
(let [{:keys [added removed diff]} (diff/diff "" content path)]
358+
{:type :fileChange
359+
:path path
360+
:linesAdded added
361+
:linesRemoved removed
362+
:diff diff}))))
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
(ns eca.features.tools.mcp.clojure-mcp
2+
(:require [clojure.string :as string]
3+
[eca.diff :as diff]
4+
[eca.features.tools.util :as tools.util]))
5+
6+
(defmethod tools.util/tool-call-details-after-invocation :clojure_edit [name arguments details result]
7+
(tools.util/tool-call-details-after-invocation :file_edit arguments details result))
8+
9+
(defmethod tools.util/tool-call-details-after-invocation :clojure_edit_replace_sexp [name arguments details result]
10+
(tools.util/tool-call-details-after-invocation :file_edit arguments details result))
11+
12+
(defmethod tools.util/tool-call-details-after-invocation :file_edit [name arguments details result]
13+
(when-not (:error result)
14+
(when-let [diff (some->> result :contents (filter #(= :text (:type %))) first :text)]
15+
(let [{:keys [added removed]} (diff/unified-diff-counts diff)]
16+
{:type :fileChange
17+
:path (get arguments "file_path")
18+
:linesAdded added
19+
:linesRemoved removed
20+
:diff diff}))))
21+
22+
(defmethod tools.util/tool-call-details-after-invocation :file_write [name arguments details result]
23+
(when-not (:error result)
24+
(when-let [diff (some->> result :contents
25+
(filter #(= :text (:type %)))
26+
first :text
27+
(string/split-lines)
28+
(drop 2)
29+
(string/join "\n"))]
30+
(let [{:keys [added removed]} (diff/unified-diff-counts diff)]
31+
{:type :fileChange
32+
:path (get arguments "file_path")
33+
:linesAdded added
34+
:linesRemoved removed
35+
:diff diff}))))

src/eca/features/tools/util.clj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@
44
[clojure.string :as string]
55
[eca.shared :as shared]))
66

7+
(defmulti tool-call-details-before-invocation
8+
"Return the tool call details before invoking the tool."
9+
(fn [name arguments] (keyword name)))
10+
11+
(defmethod tool-call-details-before-invocation :default [name arguments]
12+
nil)
13+
14+
(defmulti tool-call-details-after-invocation
15+
"Return the tool call details after invoking the tool."
16+
(fn [name arguments details result] (keyword name)))
17+
18+
(defmethod tool-call-details-after-invocation :default [name arguments details result]
19+
details)
20+
721
(defn single-text-content [text & [error]]
822
{:error (boolean error)
923
:contents [{:type :text

test/eca/diff_test.clj

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,22 @@
3939
(is (some #{"-c"} lines) "diff should include -c line")))
4040

4141
(testing "new file"
42-
(let [revised (string/join "\n" ["a" "b" "c" "d"])
42+
(let [revised (string/join "\n" ["a" "b" "c" "d"])
4343
{:keys [added removed diff]} (diff/diff "" revised "file.txt")
4444
lines (split-diff-lines diff)]
4545
(is (= 4 added) "two lines added")
4646
(is (= 0 removed) "no lines removed")
4747
(is (some #{"+c"} lines) "diff should include +c line")
4848
(is (some #{"+d"} lines) "diff should include +d line"))))
49+
50+
(deftest unified-diff-counts-test
51+
(testing "counts added and removed lines from unified diff"
52+
(let [example-diff (string/join "\n" ["--- original.txt"
53+
"+++ revised.txt"
54+
"@@ -1,1 +1,2 @@"
55+
"-a"
56+
"+b"
57+
"+c"])
58+
{:keys [added removed]} (diff/unified-diff-counts example-diff)]
59+
(is (= 2 added) "one line added in the diff body")
60+
(is (= 1 removed) "one line removed in the diff body"))))
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
(ns eca.features.tools.mcp.clojure-mcp-test
2+
(:require
3+
[clojure.string :as string]
4+
[clojure.test :refer [deftest is testing]]
5+
[eca.features.tools :as f.tools]
6+
[matcher-combinators.test :refer [match?]]))
7+
8+
(def ^:private example-diff
9+
(string/join "\n" ["--- original.txt"
10+
"+++ revised.txt"
11+
"@@ -1,1 +1,2 @@"
12+
"-a"
13+
"+b"
14+
"+c"]))
15+
16+
(deftest tool-call-details-after-invocation-clojure-mcp-clojure-edit-test
17+
(testing "Tool call details for the Clojure MCP clojure_edit tool"
18+
(is (match? {:type :fileChange
19+
:path "/home/alice/my-org/my-proj/project.clj"
20+
:linesAdded 2
21+
:linesRemoved 1
22+
:diff example-diff}
23+
(f.tools/tool-call-details-after-invocation
24+
:clojure_edit
25+
{"file_path" "/home/alice/my-org/my-proj/project.clj"
26+
"form_identifier" "a"
27+
"form_type" "atom"
28+
"operation" "replace"
29+
"content" "b\nc"}
30+
nil
31+
{:error false :contents [{:type :text :text example-diff}]})))))
32+
33+
(deftest tool-call-details-after-invocation-clojure-mcp-clojure-edit-replace-sexp-test
34+
(testing "Tool call details for the Clojure MCP clojure_edit_replace_sexp tool"
35+
(is (match? {:type :fileChange
36+
:path "/home/alice/my-org/my-proj/project.clj"
37+
:linesAdded 2
38+
:linesRemoved 1
39+
:diff example-diff}
40+
(f.tools/tool-call-details-after-invocation
41+
:clojure_edit_replace_sexp
42+
{"file_path" "/home/alice/my-org/my-proj/project.clj"
43+
"match_form" "a"
44+
"new_form" "b\nc"
45+
"replace_all" false}
46+
nil
47+
{:error false :contents [{:type :text :text example-diff}]})))))
48+
49+
(deftest tool-call-details-after-invocation-clojure-mcp-file-edit-test
50+
(testing "Tool call details for the Clojure MCP file_edit tool"
51+
(is (match? {:type :fileChange
52+
:path "/home/alice/my-org/my-proj/project.clj"
53+
:linesAdded 2
54+
:linesRemoved 1
55+
:diff example-diff}
56+
(f.tools/tool-call-details-after-invocation
57+
:file_edit
58+
{"file_path" "/home/alice/my-org/my-proj/project.clj"
59+
"old_string" "a"
60+
"new_string" "b\nc"}
61+
nil
62+
{:error false :contents [{:type :text :text example-diff}]})))))
63+
64+
(deftest tool-call-details-after-invocation-clojure-mcp-file-write-test
65+
(testing "Tool call details for the Clojure MCP file_write tool"
66+
(is (match? {:type :fileChange
67+
:path "/home/alice/my-org/my-proj/project.clj"
68+
:linesAdded 2
69+
:linesRemoved 1
70+
:diff example-diff}
71+
(f.tools/tool-call-details-after-invocation
72+
:file_write
73+
{"file_path" "/home/alice/my-org/my-proj/project.clj"
74+
"content" "my-content"}
75+
nil
76+
{:error false
77+
:contents [{:type :text
78+
:text (string/join "\n"
79+
["Clojure file updated: /home/alice/my-org/my-proj/project.clj"
80+
"Changes:" example-diff])}]})))))

0 commit comments

Comments
 (0)