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
4 changes: 3 additions & 1 deletion clojure-mcp-dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ PORT=7888
# PORT=58709 # conj-talk

# Start tee process to capture stdin in background
tee "$STDIN_LOG" < "$PIPE" | clojure -X:dev-mcp :port $PORT 2>&1 | tee "$STDOUT_LOG" &
tee "$STDIN_LOG" < "$PIPE" | clojure -X:dev-mcp :enable-logging? true :port $PORT 2>&1 | tee "$STDOUT_LOG" &
# tee "$STDIN_LOG" < "$PIPE" | clojure -X:mcp-shadow :enable-logging? true :port $PORT 2>&1 | tee "$STDOUT_LOG" &
# tee "$STDIN_LOG" < "$PIPE" | clojure -X:mcp-shadow-dual :enable-logging? true :port 7888 :shadow-port $PORT 2>&1 | tee "$STDOUT_LOG" &

# Get the PID of the background pipeline
CLOJURE_PID=$!
Expand Down
4 changes: 1 addition & 3 deletions src/clojure_mcp/config.clj
Original file line number Diff line number Diff line change
Expand Up @@ -110,9 +110,7 @@
(assoc :nrepl-env-type (:nrepl-env-type config)))))

(defn load-config
"Loads configuration from both user home (~/.clojure-mcp/config.edn) and project directory.
User home config provides defaults, project config provides overrides.
Validates both configs before merging."
"Loads the configuration from user home and project directories."
[cli-config-file user-dir]
;; Load user home config first (provides defaults)
(let [home-config (load-home-config)
Expand Down
10 changes: 1 addition & 9 deletions src/clojure_mcp/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,6 @@

This function handles the complete setup process including:
- Creating the nREPL client connection
- Starting the polling mechanism
- Loading required namespaces and helpers (if Clojure environment)
- Setting up the working directory
- Loading configuration
Expand All @@ -285,10 +284,7 @@
(try
(let [nrepl-client-map (nrepl/create (dissoc initial-config :project-dir :nrepl-env-type))
cli-env-type (:nrepl-env-type initial-config)
_ (do
(log/info "nREPL client map created")
(nrepl/start-polling nrepl-client-map)
(log/info "Started polling nREPL"))
_ (log/info "nREPL client map created")
;; Detect environment type early
;; TODO this needs to be sorted out
env-type (dialects/detect-nrepl-env-type nrepl-client-map)
Expand All @@ -314,7 +310,6 @@
(log/info "Creating additional nREPL connection" initial-config)
(try
(let [nrepl-client-map (nrepl/create initial-config)]
(nrepl/start-polling nrepl-client-map)
;; copy config
;; maybe we should create this just like the normal nrelp connection?
;; we should introspect the project and get a working directory
Expand All @@ -331,15 +326,12 @@
"Convenience higher-level API function to gracefully shut down MCP and nREPL servers.

This function handles the complete shutdown process including:
- Stopping nREPL polling if a client exists in nrepl-client-atom
- Gracefully closing the MCP server
- Proper error handling and logging"
[nrepl-client-atom]
(log/info "Shutting down servers")
(try
(when-let [client @nrepl-client-atom]
(log/info "Stopping nREPL polling")
(nrepl/stop-polling client)
;; Clean up auto-started nREPL process if present
(when-let [nrepl-process (:nrepl-process client)]
(log/info "Cleaning up auto-started nREPL process")
Expand Down
27 changes: 17 additions & 10 deletions src/clojure_mcp/dialects.clj
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@

Supports different Clojure-like environments by providing expressions
and initialization sequences specific to each dialect."
(:require [clojure.edn :as edn]
[clojure.java.io :as io]
(:require [clojure.java.io :as io]
[clojure.string :as str]
[taoensso.timbre :as log]
[nrepl.core :as nrepl-core]
[clojure-mcp.nrepl :as nrepl]
[clojure-mcp.utils.file :as file-utils]))

Expand Down Expand Up @@ -79,8 +80,10 @@
;; default to fetching from the nrepl
(when-let [exp (fetch-project-directory-exp nrepl-env-type)]
(try
(edn/read-string
(nrepl/tool-eval-code nrepl-client-map exp))
(let [result-value (->> (nrepl/eval-code nrepl-client-map exp :session-type :tools)
nrepl-core/combine-responses
:value)]
result-value)
(catch Exception e
(log/warn e "Failed to fetch project directory")
nil))))
Expand All @@ -93,10 +96,14 @@
"Fetches the project directory for the given nREPL client.
If project-dir is provided in opts, returns it directly.
Otherwise, evaluates environment-specific expression to get it."
[nrepl-client-map nrepl-env-type project-dir]
(if project-dir
(.getCanonicalPath (io/file project-dir))
(fetch-project-directory-helper nrepl-env-type nrepl-client-map)))
[nrepl-client-map nrepl-env-type project-dir-arg]
(if project-dir-arg
(.getCanonicalPath (io/file project-dir-arg))
(let [raw-result (fetch-project-directory-helper nrepl-env-type nrepl-client-map)]
;; nrepl sometimes returns strings with extra quotes and in a vector
(if (and (vector? raw-result) (= 1 (count raw-result)) (string? (first raw-result)))
(str/replace (first raw-result) #"^\"|\"$" "")
raw-result))))

;; High-level wrapper functions that execute the expressions

Expand All @@ -107,15 +114,15 @@
(log/debug "Initializing Clojure environment")
(when-let [init-exps (not-empty (initialize-environment-exp nrepl-env-type))]
(doseq [exp init-exps]
(nrepl/eval-code nrepl-client-map exp identity)))
(nrepl/eval-code nrepl-client-map exp)))
nrepl-client-map)

(defn load-repl-helpers
"Loads REPL helper functions appropriate for the environment."
[nrepl-client-map nrepl-env-type]
(when-let [helper-exps (not-empty (load-repl-helpers-exp nrepl-env-type))]
(doseq [exp helper-exps]
(nrepl/tool-eval-code nrepl-client-map exp)))
(nrepl/eval-code nrepl-client-map exp :session-type :tools)))
nrepl-client-map)

(defn detect-nrepl-env-type [nrepl-client-map]
Expand Down
33 changes: 15 additions & 18 deletions src/clojure_mcp/main_examples/shadow_main.clj
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ JavaScript interop is fully supported including `js/console.log`, `js/setTimeout

**IMPORTANT**: This repl is intended for CLOJURESCRIPT CODE only.")

(defn start-shadow-repl [nrepl-client-atom cljs-session {:keys [shadow-build shadow-watch]}]
(defn start-shadow-repl [nrepl-client-atom {:keys [shadow-build shadow-watch]}]
(let [start-code (format
;; TODO we need to check if its already running
;; here and only initialize if it isn't
Expand All @@ -39,36 +39,33 @@ JavaScript interop is fully supported including `js/console.log`, `js/setTimeout
"(do (shadow/repl %s) %s)")
(pr-str (keyword (name shadow-build)))
(pr-str (keyword (name shadow-build))))]
(nrepl/eval-code-msg
@nrepl-client-atom start-code {:session cljs-session}
(->> identity
(nrepl/out-err #(log/info %) #(log/info %))
(nrepl/value #(log/info %))
(nrepl/done (fn [_] (log/info "done")))
(nrepl/error (fn [args]
(log/info (pr-str args))
(log/info "ERROR in shadow start")))))
cljs-session))
(log/info "Starting Shadow CLJS...")
(try
(nrepl/eval-code @nrepl-client-atom start-code :session-type :shadow)
(log/info "Shadow CLJS started (or command sent)")
(catch Exception e
(log/error e "ERROR in shadow start")))
:shadow))

;; when having a completely different connection for cljs
(defn shadow-eval-tool-secondary-connection-tool [nrepl-client-atom {:keys [shadow-port _shadow-build _shadow-watch] :as config}]
(let [cljs-nrepl-client-map (core/create-additional-connection nrepl-client-atom {:port shadow-port})
cljs-nrepl-client-atom (atom cljs-nrepl-client-map)]
(start-shadow-repl
cljs-nrepl-client-atom
(nrepl/eval-session cljs-nrepl-client-map)
config)
(-> (eval-tool/eval-code cljs-nrepl-client-atom)
(-> (eval-tool/eval-code cljs-nrepl-client-atom {:session-type :shadow})
(assoc :name tool-name)
(assoc :id (keyword tool-name))
(assoc :description description))))

;; when sharing the clojure and cljs repl
(defn shadow-eval-tool [nrepl-client-atom {:keys [_shadow-build _shadow-watch] :as config}]
(let [cljs-session (nrepl/new-session @nrepl-client-atom)
_ (start-shadow-repl nrepl-client-atom cljs-session config)]
(-> (eval-tool/eval-code nrepl-client-atom {:nrepl-session cljs-session})
(assoc :name tool-name)
(assoc :description description))))
(start-shadow-repl nrepl-client-atom config)
(-> (eval-tool/eval-code nrepl-client-atom {:session-type :shadow})
(assoc :name tool-name)
(assoc :id (keyword tool-name))
(assoc :description description)))

;; So we can set up shadow two ways
;; 1. as a single repl connection using the shadow clojure connection for cloj eval
Expand Down
Loading