Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

## Unreleased

- Bugfix: OpenAI Responses tool calls now opt out of strict schema normalization so optional tool parameters remain optional.
- Bugfix: MCP tool calls now route to the selected server when multiple servers expose the same tool name.

## 0.133.6

- Bugfix: `network.caCertFile` (and `clientCert`/`clientKey`/`clientKeyPassphrase`) set via `config.json` were silently ignored due to a key-case mismatch between config normalization and the network reader; only the env-var fallbacks worked. #457
Expand Down
8 changes: 4 additions & 4 deletions src/eca/features/tools.clj
Original file line number Diff line number Diff line change
Expand Up @@ -256,10 +256,10 @@
:call-state-fn call-state-fn
:state-transition-fn state-transition-fn
:trust trust})
(f.mcp/call-tool! tool-name arguments {:db db
:db* db*
:config config
:metrics metrics})))
(f.mcp/call-tool! server-name tool-name arguments {:db db
:db* db*
:config config
:metrics metrics})))
(tools.util/maybe-truncate-output config tool-call-id))]
(logger/debug logger-tag "Tool call result: " result)
(metrics/count-up! "tool-called" {:name resolved-full-name :error (:error result)} metrics)
Expand Down
14 changes: 6 additions & 8 deletions src/eca/features/tools/mcp.clj
Original file line number Diff line number Diff line change
Expand Up @@ -855,13 +855,11 @@
(do-call-tool new-client name arguments nil)
(tool-call-error (format "Failed to re-initialize MCP server '%s'" server-name))))

(defn call-tool! [name arguments {:keys [db db* config metrics]}]
(if-let [[server-name mcp-client needs-reinit?*]
(->> (:mcp-clients db)
(keep (fn [[sn {:keys [client tools needs-reinit?*]}]]
(when (some #(= name (:name %)) tools)
[sn client needs-reinit?*])))
first)]
(defn call-tool! [server-name name arguments {:keys [db db* config metrics]}]
(if-let [[mcp-client needs-reinit?*]
(when-let [{:keys [client tools needs-reinit?*]} (get-in db [:mcp-clients server-name])]
(when (some #(= name (:name %)) tools)
[client needs-reinit?*]))]
(if (and needs-reinit?* @needs-reinit?* db* config metrics)
;; Already flagged — reinit before attempting the call
(reinit-and-call-tool! server-name mcp-client db* config metrics name arguments)
Expand All @@ -876,7 +874,7 @@
(reinit-and-call-tool! server-name mcp-client db* config metrics name arguments)

:else result)))
(tool-call-error (format "Tool '%s' not found in any connected MCP server" name))))
(tool-call-error (format "Tool '%s' not found in MCP server '%s'" name server-name))))

(defn all-prompts [db]
(into []
Expand Down
2 changes: 1 addition & 1 deletion src/eca/features/tools/mcp/clojure_mcp.clj
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
(when (not= "0.1.0" (:version server))
(when ask-approval?
(let [path (get args "file_path")
{:keys [error contents]} (f.mcp/call-tool! name (assoc args "dry_run" "new-source") {:db db})]
{:keys [error contents]} (f.mcp/call-tool! (:name server) name (assoc args "dry_run" "new-source") {:db db})]
(when-not error
(when-let [new-source (some->> contents (filter #(= :text (:type %))) first :text)]
(let [{:keys [added removed diff]} (diff/diff (if new-file?
Expand Down
3 changes: 2 additions & 1 deletion src/eca/llm_providers/openai.clj
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@
{:type "function"
:name (:full-name tool)
:description (:description tool)
:parameters (:parameters tool)})
:parameters (:parameters tool)
:strict false})
tools)
web-search (conj {:type "web_search"})
image-generation (conj {:type "image_generation" :output_format "png"})))
Expand Down
24 changes: 24 additions & 0 deletions test/eca/features/tools/mcp_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,30 @@
(mcp/all-tools {:mcp-clients {"server1" {:tools tools1}
"server2" {:tools tools2}}}))))))

(deftest call-tool-routes-to-server-test
(testing "uses the requested MCP server when tool names collide"
(let [db {:mcp-clients (array-map
"server-a" {:client :client-a
:tools [{:name "shared_tool"}]}
"server-b" {:client :client-b
:tools [{:name "shared_tool"}]})}
calls* (atom [])]
(with-redefs [mcp/do-call-tool (fn [client name arguments needs-reinit?*]
(swap! calls* conj {:client client
:name name
:arguments arguments
:needs-reinit?* needs-reinit?*})
{:error false
:contents [{:type :text :text (str client)}]})]
(is (= {:error false
:contents [{:type :text :text ":client-b"}]}
(mcp/call-tool! "server-b" "shared_tool" {"query" "value"} {:db db})))
(is (= [{:client :client-b
:name "shared_tool"
:arguments {"query" "value"}
:needs-reinit?* nil}]
@calls*))))))

(deftest all-prompts-test
(testing "empty db"
(is (= []
Expand Down
15 changes: 14 additions & 1 deletion test/eca/llm_providers/openai_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,20 @@
{:type "image_generation" :output_format "png"}]
(#'llm-providers.openai/->tools
[{:full-name "eca__foo" :description "d" :parameters {}}]
true true)))))
true true))))
(testing "function tools explicitly opt out of Responses strict mode"
(is (= [{:type "function"
:name "mcp__search_records"
:description "Search records"
:parameters {:type "object"
:properties {"limit" {:type "number"}}}
:strict false}]
(#'llm-providers.openai/->tools
[{:full-name "mcp__search_records"
:description "Search records"
:parameters {:type "object"
:properties {"limit" {:type "number"}}}}]
false false)))))

(deftest create-response-oauth-preserves-built-in-tools-test
(testing "OAuth requests keep web_search and image_generation when capabilities are enabled"
Expand Down
Loading