Skip to content
Merged
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
44 changes: 40 additions & 4 deletions src/clojure_mcp/config.clj
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
(ns clojure-mcp.config
(:require
[clojure.java.io :as io]
[clojure.string :as string]
[clojure-mcp.config.schema :as schema]
[clojure-mcp.utils.file :as file-utils]
[clojure.edn :as edn]
Expand Down Expand Up @@ -311,11 +312,46 @@
(log/warn "Invalid :dispatch-agent-context value, defaulting to true")
true))))

(defn get-enable-tools [nrepl-client-map]
(get-config nrepl-client-map :enable-tools))
(def ^:dynamic *env-overrides*
"Dynamic var for overriding environment variables in tests.
When non-nil, will be checked before System/getenv."
nil)

(defn- get-env
"Gets environment variable, checking *env-overrides* first for testing."
[var-name]
(or (when *env-overrides*
(get *env-overrides* var-name))
(System/getenv var-name)))

(defn- parse-tools-env-var
"Parses a comma-separated string of tool names into a vector of keywords.
Returns nil if the env var is not set or empty.
Hyphens are normalized to underscores (e.g., file-edit -> file_edit)."
[env-var-name]
(when-let [value (get-env env-var-name)]
(when-not (empty? value)
(->> (string/split value #",")
(map string/trim)
(remove empty?)
(map #(string/replace % "-" "_"))
(mapv keyword)))))

(defn get-enable-tools
"Returns the list of enabled tools.
ENABLE_TOOLS env var overrides config if set."
[nrepl-client-map]
(if-let [env-tools (parse-tools-env-var "ENABLE_TOOLS")]
env-tools
(get-config nrepl-client-map :enable-tools)))

(defn get-disable-tools [nrepl-client-map]
(get-config nrepl-client-map :disable-tools))
(defn get-disable-tools
"Returns the list of disabled tools.
DISABLE_TOOLS env var overrides config if set."
[nrepl-client-map]
(if-let [env-tools (parse-tools-env-var "DISABLE_TOOLS")]
env-tools
(get-config nrepl-client-map :disable-tools)))

(defn tool-id-enabled?
"Check if a tool should be enabled based on :enable-tools and :disable-tools config.
Expand Down
105 changes: 105 additions & 0 deletions test/clojure_mcp/config/tool_filter_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,111 @@
(:require [clojure.test :refer [deftest is testing]]
[clojure-mcp.config :as config]))

;; Tests for ENABLE_TOOLS and DISABLE_TOOLS environment variable support

(deftest test-enable-tools-env-var
(testing "ENABLE_TOOLS env var overrides config"
(binding [config/*env-overrides* {"ENABLE_TOOLS" "bash,eval_code,read_file"}]
(let [nrepl-map {::config/config {:enable-tools [:other_tool :another_tool]}}]
;; Env var should override config
(is (true? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :eval_code)))
(is (true? (config/tool-id-enabled? nrepl-map :read_file)))
(is (false? (config/tool-id-enabled? nrepl-map :other_tool)))
(is (false? (config/tool-id-enabled? nrepl-map :grep))))))

(testing "ENABLE_TOOLS env var with spaces around commas"
(binding [config/*env-overrides* {"ENABLE_TOOLS" "bash , eval_code , read_file"}]
(let [nrepl-map {::config/config {}}]
(is (true? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :eval_code)))
(is (true? (config/tool-id-enabled? nrepl-map :read_file)))
(is (false? (config/tool-id-enabled? nrepl-map :grep))))))

(testing "ENABLE_TOOLS env var normalizes hyphens to underscores"
(binding [config/*env-overrides* {"ENABLE_TOOLS" "file-edit,eval-code,read-file"}]
(let [nrepl-map {::config/config {}}]
;; Hyphens in env var should match underscore tool IDs
(is (true? (config/tool-id-enabled? nrepl-map :file_edit)))
(is (true? (config/tool-id-enabled? nrepl-map :eval_code)))
(is (true? (config/tool-id-enabled? nrepl-map :read_file)))
(is (false? (config/tool-id-enabled? nrepl-map :grep))))))

(testing "Empty ENABLE_TOOLS env var is ignored (config used)"
(binding [config/*env-overrides* {"ENABLE_TOOLS" ""}]
(let [nrepl-map {::config/config {:enable-tools [:bash :grep]}}]
;; Empty string should be ignored, config should be used
(is (true? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :grep)))
(is (false? (config/tool-id-enabled? nrepl-map :eval-code))))))

(testing "No env var uses config"
(binding [config/*env-overrides* {}]
(let [nrepl-map {::config/config {:enable-tools [:bash :grep]}}]
(is (true? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :grep)))
(is (false? (config/tool-id-enabled? nrepl-map :eval-code)))))))

(deftest test-disable-tools-env-var
(testing "DISABLE_TOOLS env var overrides config"
(binding [config/*env-overrides* {"DISABLE_TOOLS" "bash,dispatch_agent"}]
(let [nrepl-map {::config/config {:disable-tools [:other_tool]}}]
;; Env var should override config
(is (false? (config/tool-id-enabled? nrepl-map :bash)))
(is (false? (config/tool-id-enabled? nrepl-map :dispatch_agent)))
(is (true? (config/tool-id-enabled? nrepl-map :other_tool))) ; config ignored
(is (true? (config/tool-id-enabled? nrepl-map :grep))))))

(testing "DISABLE_TOOLS env var with spaces"
(binding [config/*env-overrides* {"DISABLE_TOOLS" " bash , dispatch_agent "}]
(let [nrepl-map {::config/config {}}]
(is (false? (config/tool-id-enabled? nrepl-map :bash)))
(is (false? (config/tool-id-enabled? nrepl-map :dispatch_agent)))
(is (true? (config/tool-id-enabled? nrepl-map :grep))))))

(testing "DISABLE_TOOLS env var normalizes hyphens to underscores"
(binding [config/*env-overrides* {"DISABLE_TOOLS" "dispatch-agent,file-edit"}]
(let [nrepl-map {::config/config {}}]
;; Hyphens in env var should match underscore tool IDs
(is (false? (config/tool-id-enabled? nrepl-map :dispatch_agent)))
(is (false? (config/tool-id-enabled? nrepl-map :file_edit)))
(is (true? (config/tool-id-enabled? nrepl-map :grep))))))

(testing "Empty DISABLE_TOOLS env var is ignored (config used)"
(binding [config/*env-overrides* {"DISABLE_TOOLS" ""}]
(let [nrepl-map {::config/config {:disable-tools [:bash]}}]
;; Empty string should be ignored, config should be used
(is (false? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :grep)))))))

(deftest test-enable-and-disable-tools-env-vars
(testing "Both env vars can be used together"
(binding [config/*env-overrides* {"ENABLE_TOOLS" "bash,grep,eval_code"
"DISABLE_TOOLS" "bash"}]
(let [nrepl-map {::config/config {}}]
;; bash is in enable but also in disable - disable wins
(is (false? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :grep)))
(is (true? (config/tool-id-enabled? nrepl-map :eval_code)))
(is (false? (config/tool-id-enabled? nrepl-map :read_file)))))) ; not in enable list

(testing "ENABLE_TOOLS env var with DISABLE_TOOLS from config"
(binding [config/*env-overrides* {"ENABLE_TOOLS" "bash,grep,eval_code"}]
(let [nrepl-map {::config/config {:disable-tools [:bash]}}]
;; ENABLE from env, DISABLE from config
(is (false? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :grep)))
(is (true? (config/tool-id-enabled? nrepl-map :eval_code))))))

(testing "DISABLE_TOOLS env var with ENABLE_TOOLS from config"
(binding [config/*env-overrides* {"DISABLE_TOOLS" "bash"}]
(let [nrepl-map {::config/config {:enable-tools [:bash :grep :eval_code]}}]
;; ENABLE from config, DISABLE from env
(is (false? (config/tool-id-enabled? nrepl-map :bash)))
(is (true? (config/tool-id-enabled? nrepl-map :grep)))
(is (true? (config/tool-id-enabled? nrepl-map :eval_code)))
(is (false? (config/tool-id-enabled? nrepl-map :read_file)))))))

(deftest test-tool-id-enabled?
(testing "No configuration - all tools enabled"
(let [nrepl-map {::config/config {}}]
Expand Down