|
22 | 22 | [noumenon.sync :as sync] |
23 | 23 | [noumenon.synthesize :as synthesize] |
24 | 24 | [noumenon.util :as util :refer [log!]]) |
25 | | - (:import [java.io BufferedReader PrintWriter])) |
| 25 | + (:import [java.io BufferedReader PrintWriter] |
| 26 | + [java.lang ProcessHandle])) |
26 | 27 |
|
27 | 28 | ;; --- Helpers --- |
28 | 29 |
|
|
1031 | 1032 | (assoc defaults :progress-fn progress-fn) |
1032 | 1033 | defaults)) |
1033 | 1034 | (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))))) |
1037 | 1040 | (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.")))))) |
1040 | 1050 | (tool-error (str "Unknown tool: " tool-name))))) |
1041 | 1051 |
|
1042 | 1052 | ;; --- Main loop --- |
|
1080 | 1090 | (when (and active (not= active "local")) |
1081 | 1091 | (get-in config [:connections active])))))) |
1082 | 1092 |
|
| 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 | + |
1083 | 1106 | (defn- repo-path->db-name |
1084 | 1107 | "Translate a local repo_path to a database name for remote queries. |
1085 | 1108 | Tries: git remote origin URL -> org-repo, else basename." |
|
1163 | 1186 | arguments)] |
1164 | 1187 | (try |
1165 | 1188 | (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")) |
1167 | 1191 | "header = \"Content-Type: application/json\"\n") |
1168 | 1192 | ;; For GET, append non-empty args as query params |
1169 | 1193 | get-url (if (and (= method :get) (seq args)) |
|
1211 | 1235 | (log! "noumenon MCP server starting") |
1212 | 1236 | (let [reader (BufferedReader. (io/reader System/in)) |
1213 | 1237 | writer (PrintWriter. System/out true) |
1214 | | - remote-conn (load-connection-config) |
| 1238 | + remote-conn (or (load-connection-config) (detect-local-daemon)) |
1215 | 1239 | defaults (cond-> (select-keys opts [:db-dir :provider :model]) |
1216 | 1240 | (:no-auto-update opts) (assoc :auto-update false)) |
1217 | 1241 | dispatch (fn [id method params] |
|
1225 | 1249 | "resources/list" (format-response id {:resources []}) |
1226 | 1250 | (format-error id -32601 (str "Method not found: " method))))] |
1227 | 1251 | (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)")))) |
1229 | 1254 | (log! "noumenon MCP server ready") |
1230 | 1255 | (loop [] |
1231 | 1256 | (when-let [line (try |
|
0 commit comments