Skip to content

Commit d64a651

Browse files
leifericfclaude
andcommitted
fix(mcp): auto-proxy to daemon when database is locked
When a daemon process holds the database lock, serve now auto-detects it via daemon.edn and proxies tool calls to it instead of failing. Also resolves noum binary path via PATH for Homebrew installs, and surfaces actionable error messages instead of generic "unexpected internal error" on tool failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 0a85414 commit d64a651

2 files changed

Lines changed: 35 additions & 9 deletions

File tree

launcher/src/noum/setup.clj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
(defn- noum-bin-path []
2424
(or (System/getenv "NOUM_BIN")
25+
(some-> (fs/which "noum") str)
2526
(str (fs/path (fs/home) ".local" "bin" "noum"))))
2627

2728
(defn- merge-mcp-config [existing]

src/noumenon/mcp.clj

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
[noumenon.sync :as sync]
2323
[noumenon.synthesize :as synthesize]
2424
[noumenon.util :as util :refer [log!]])
25-
(:import [java.io BufferedReader PrintWriter]))
25+
(:import [java.io BufferedReader PrintWriter]
26+
[java.lang ProcessHandle]))
2627

2728
;; --- Helpers ---
2829

@@ -1031,12 +1032,21 @@
10311032
(assoc defaults :progress-fn progress-fn)
10321033
defaults))
10331034
(catch clojure.lang.ExceptionInfo e
1034-
(log! "tool/error" tool-name (.getMessage e))
1035-
(tool-error (or (:user-message (ex-data e))
1036-
(str "Internal error: " (.getMessage e)))))
1035+
(let [msg (.getMessage e)
1036+
user-msg (:user-message (ex-data e))]
1037+
(log! "tool/error" tool-name msg)
1038+
(tool-error (or user-msg
1039+
(str "Internal error: " msg)))))
10371040
(catch Exception e
1038-
(log! "tool/error" tool-name (.getMessage e))
1039-
(tool-error "An unexpected internal error occurred.")))
1041+
(let [msg (.getMessage e)]
1042+
(log! "tool/error" tool-name msg)
1043+
(tool-error
1044+
(if (and msg (str/includes? msg ".lock"))
1045+
(str "FATAL: Database locked by another process (likely a running daemon). "
1046+
"DO NOT RETRY — all Noumenon tool calls will fail until the lock is released. "
1047+
"Tell the user to kill the daemon: ps aux | grep 'noumenon.*daemon' | grep -v grep | awk '{print $2}' | xargs kill")
1048+
(str "Internal error in " tool-name ": " msg
1049+
". DO NOT RETRY — report this error to the user."))))))
10401050
(tool-error (str "Unknown tool: " tool-name)))))
10411051

10421052
;; --- Main loop ---
@@ -1080,6 +1090,19 @@
10801090
(when (and active (not= active "local"))
10811091
(get-in config [:connections active]))))))
10821092

1093+
(defn- detect-local-daemon
1094+
"Check ~/.noumenon/daemon.edn for a running local daemon.
1095+
Returns a proxy connection map if the daemon PID is alive, nil otherwise."
1096+
[]
1097+
(let [daemon-file (io/file (System/getProperty "user.home") ".noumenon" "daemon.edn")]
1098+
(when (.exists daemon-file)
1099+
(try
1100+
(let [{:keys [port pid]} (edn/read-string {:readers {}} (slurp daemon-file))
1101+
handle (.orElse (ProcessHandle/of pid) nil)]
1102+
(when (and handle (.isAlive handle) port)
1103+
{:host (str "http://127.0.0.1:" port)}))
1104+
(catch Exception _ nil)))))
1105+
10831106
(defn- repo-path->db-name
10841107
"Translate a local repo_path to a database name for remote queries.
10851108
Tries: git remote origin URL -> org-repo, else basename."
@@ -1163,7 +1186,8 @@
11631186
arguments)]
11641187
(try
11651188
(let [;; Pass auth header via stdin to avoid token in process list
1166-
curl-config (str "header = \"Authorization: Bearer " token "\"\n"
1189+
curl-config (str (when token
1190+
(str "header = \"Authorization: Bearer " token "\"\n"))
11671191
"header = \"Content-Type: application/json\"\n")
11681192
;; For GET, append non-empty args as query params
11691193
get-url (if (and (= method :get) (seq args))
@@ -1211,7 +1235,7 @@
12111235
(log! "noumenon MCP server starting")
12121236
(let [reader (BufferedReader. (io/reader System/in))
12131237
writer (PrintWriter. System/out true)
1214-
remote-conn (load-connection-config)
1238+
remote-conn (or (load-connection-config) (detect-local-daemon))
12151239
defaults (cond-> (select-keys opts [:db-dir :provider :model])
12161240
(:no-auto-update opts) (assoc :auto-update false))
12171241
dispatch (fn [id method params]
@@ -1225,7 +1249,8 @@
12251249
"resources/list" (format-response id {:resources []})
12261250
(format-error id -32601 (str "Method not found: " method))))]
12271251
(when remote-conn
1228-
(log! (str "MCP proxy mode: forwarding to " (:host remote-conn))))
1252+
(log! (str "MCP proxy mode: forwarding to " (:host remote-conn)
1253+
(when-not (load-connection-config) " (auto-detected local daemon)"))))
12291254
(log! "noumenon MCP server ready")
12301255
(loop []
12311256
(when-let [line (try

0 commit comments

Comments
 (0)