From 27107bcef189b339350c00348165cf2fa18feb22 Mon Sep 17 00:00:00 2001 From: Bruce Hauman Date: Fri, 2 Jan 2026 11:24:53 -0600 Subject: [PATCH] Add ENABLE_TOOLS and DISABLE_TOOLS environment variable support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Environment variables override config file settings for tool filtering. Accepts comma-separated tool names with optional whitespace. Hyphens are normalized to underscores (e.g., file-edit -> file_edit). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- src/clojure_mcp/config.clj | 44 +++++++- test/clojure_mcp/config/tool_filter_test.clj | 105 +++++++++++++++++++ 2 files changed, 145 insertions(+), 4 deletions(-) diff --git a/src/clojure_mcp/config.clj b/src/clojure_mcp/config.clj index 73edaf8..479bfe8 100644 --- a/src/clojure_mcp/config.clj +++ b/src/clojure_mcp/config.clj @@ -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] @@ -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. diff --git a/test/clojure_mcp/config/tool_filter_test.clj b/test/clojure_mcp/config/tool_filter_test.clj index 5b092ea..82159ef 100644 --- a/test/clojure_mcp/config/tool_filter_test.clj +++ b/test/clojure_mcp/config/tool_filter_test.clj @@ -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 {}}]