|
3 | 3 | (:require |
4 | 4 | [babashka.fs :as fs] |
5 | 5 | [cheshire.core :as json] |
6 | | - [clj-yaml.core :as yaml] |
7 | 6 | [clojure.core :as c] |
8 | 7 | [clojure.core.async :as async] |
9 | 8 | [clojure.pprint :as pprint] |
|
19 | 18 | [lsp4clj.io-chan :as io-chan] |
20 | 19 | [lsp4clj.io-server :refer [stdio-server]] |
21 | 20 | [lsp4clj.server :as lsp.server] |
| 21 | + [medley.core :as medley] |
22 | 22 | [promesa.core :as p] |
23 | 23 | shutdown |
24 | 24 | state |
|
118 | 118 | {:description (or (:description metadata) name) |
119 | 119 | :messages (prompt-function (or arguments {}))})) |
120 | 120 |
|
121 | | -(defmethod lsp.server/receive-request "resources/list" [_ _ _] |
| 121 | +(defmethod lsp.server/receive-request "resources/list" [_ {:keys [db*]} _] |
122 | 122 | (logger/info "resources/list") |
123 | | - {:resources []}) |
124 | | - |
125 | | -(defmethod lsp.server/receive-request "resources/read" [_ _ params] |
| 123 | + (let [resources |
| 124 | + {:resources (or (->> (:mcp.prompts/resources @db*) |
| 125 | + (vals) |
| 126 | + (map #(select-keys % [:uri :name :description :mimeType]))) |
| 127 | + [])}] |
| 128 | + (logger/info resources) |
| 129 | + resources)) |
| 130 | + |
| 131 | +(defmethod lsp.server/receive-request "resources/read" [_ {:keys [db*]} params] |
126 | 132 | (logger/info "resouces/read" params) |
127 | | - {:contents []}) |
| 133 | + {:contents (concat |
| 134 | + [] |
| 135 | + (when-let [m (get-in @db* [:mcp.prompts/resources (:uri params)])] |
| 136 | + [(select-keys m [:uri :mimeType :text :blob])]))}) |
128 | 137 |
|
129 | 138 | (defmethod lsp.server/receive-request "resources/templates/list" [_ _ _] |
130 | 139 | (logger/info "resources/templates/list") |
|
149 | 158 | (assoc :inputSchema (or (-> m :function :parameters) {:type "object" :properties {}}))))) |
150 | 159 | (into []))}) |
151 | 160 |
|
| 161 | +(defn resource-uri [db-resources uri] |
| 162 | + ((->> db-resources |
| 163 | + vals |
| 164 | + (map (fn [{:keys [uri matches]}] [matches uri])) |
| 165 | + (into {})) uri)) |
| 166 | + |
| 167 | +(defn update-matched-resources |
| 168 | + "check if any collected-resources match the db ones and update them" |
| 169 | + [db-resources collected-resources] |
| 170 | + (let [matched (->> collected-resources |
| 171 | + (filter (comp (into #{} (->> db-resources (vals) (map :matches))) :uri :resource)))] |
| 172 | + (medley/deep-merge |
| 173 | + db-resources |
| 174 | + (->> matched |
| 175 | + (map (fn [{{:keys [uri text]} :resource}] [(resource-uri db-resources uri) {:text text}])) |
| 176 | + (into {}))))) |
| 177 | + |
| 178 | +(comment |
| 179 | + (update-matched-resources |
| 180 | + {"memo://insights" {:uri "memo://insights" :text "No Business Insights" :matches "resource:///thread/insights.txt"}} |
| 181 | + [{:resource {:uri "resource:///thread/insights.txt" :text "updated"}}])) |
| 182 | + |
| 183 | +(defn update-resources |
| 184 | + "update the resource list in the db |
| 185 | + params - resources coll of mcp resources extracted from last tool call" |
| 186 | + [{:keys [db* producer]} resources] |
| 187 | + (when (seq resources) |
| 188 | + (swap! db* update :mcp.prompts/resources update-matched-resources resources) |
| 189 | + (producer/publish-resource-list-changed producer {}))) |
| 190 | + |
| 191 | +(comment |
| 192 | + (def hey |
| 193 | + (atom |
| 194 | + {:mcp.prompts/resources |
| 195 | + {"memo://insights" |
| 196 | + {:uri "memo://insights" |
| 197 | + :text "No Business Insights" |
| 198 | + :matches "resource:///thread/insights.txt"}}})) |
| 199 | + (update-resources |
| 200 | + {:db* hey |
| 201 | + :producer (reify producer/IProducer (publish-resource-list-changed [_ _] (println "called")))} |
| 202 | + [{:resource {:uri "resource:///thread/insights.txt" :text "updated"}}])) |
| 203 | + |
152 | 204 | (defn mcp-tool-calls |
153 | 205 | " params |
154 | | - db* - uses mcp.prompts/registry and host-dir |
155 | | - params - tools/call mcp params" |
156 | | - [db* params] |
| 206 | + db* - uses mcp.prompts/registry and host-dir |
| 207 | + params - tools/call mcp params" |
| 208 | + [{:keys [db*] :as components} params] |
157 | 209 | (volumes/with-volume |
158 | 210 | (fn [thread-id] |
159 | 211 | (concat |
|
171 | 223 | (async/<!!) |
172 | 224 | (map :content) |
173 | 225 | (apply str))}] |
174 | | - (volumes/pick-up-mcp-resources thread-id))))) |
| 226 | + (volumes/pick-up-mcp-resources thread-id (partial update-resources components)))))) |
175 | 227 |
|
176 | | -(defmethod lsp.server/receive-request "tools/call" [_ {:keys [db*]} params] |
| 228 | +(defmethod lsp.server/receive-request "tools/call" [_ components params] |
177 | 229 | (logger/info "tools/call") |
178 | 230 | (logger/info "with params" params) |
179 | 231 | (lsp.server/discarding-stdout |
180 | | - (let [content (mcp-tool-calls db* params)] |
| 232 | + (let [content (mcp-tool-calls components params)] |
181 | 233 | (logger/info "content " (with-out-str (pprint/pprint content))) |
182 | 234 | ;; TODO with mcp, tool-calls with errors can be explicit |
183 | 235 | {:content content |
|
316 | 368 | (swap! db* merge {:log-path log-path} (dissoc opts :in)) |
317 | 369 | ;; register static prompts |
318 | 370 | (db/add-refs |
319 | | - (concat |
320 | | - (->> (:register opts) |
321 | | - (map (fn [ref] [:static ref]))) |
| 371 | + (concat |
| 372 | + (->> (:register opts) |
| 373 | + (map (fn [ref] [:static ref]))) |
322 | 374 | ;; register dynamic prompts |
323 | | - (when (fs/exists? (fs/file registry)) |
324 | | - (db/registry-refs registry)))) |
| 375 | + (when (fs/exists? (fs/file registry)) |
| 376 | + (db/registry-refs registry)))) |
325 | 377 | ;; watch dynamic prompts in background |
326 | 378 | (async/thread |
327 | 379 | (let [{x :container} |
|
0 commit comments