This guide explains how to use resource subscriptions in mcp-clj to receive real-time notifications when resources change.
Resource subscriptions allow clients to monitor specific resources and receive notifications when they are updated. This is more efficient than polling, as the server pushes updates only when changes occur.
Use resource subscriptions when:
resources/subscribe
request with resource URInotifications/resources/updated
to subscribed clientsresources/unsubscribe
when no longer interestedEnsure the server supports subscriptions by checking capabilities:
(require '[mcp-clj.mcp-client.core :as client])
(def mcp-client (client/create-client {...}))
;; Wait for initialization
(let [init-result (client/wait-for-ready mcp-client)]
(if (get-in init-result [:capabilities :resources :subscribe])
(println "Server supports resource subscriptions")
(println "Server does NOT support subscriptions")))
Subscribe by providing a URI and a callback function:
(client/subscribe-resource!
mcp-client
"file:///project/config.json"
(fn [notification]
(println "Resource updated!")
(println " URI:" (:uri notification))
(when-let [meta (:meta notification)]
(println " Metadata:" meta))))
The callback function receives a notification map with:
:uri
- The resource URI that was updated:meta
- Optional metadata about the update (map)Remove a subscription when you no longer need updates:
(client/unsubscribe-resource! mcp-client "file:///project/config.json")
Note: Subscriptions are automatically cleaned up when the client closes.
(require '[mcp-clj.mcp-client.core :as client])
(defn handle-config-update [notification]
(println "Config file changed!")
;; Re-read the resource to get updated content
@(client/read-resource mcp-client (:uri notification)))
(defn run-client []
(with-open [mcp-client (client/create-client
{:transport {:type :stdio
:command "my-server"}
:client-info {:name "config-monitor"
:version "1.0.0"}})]
;; Wait for ready
(client/wait-for-ready mcp-client 5000)
;; Subscribe to config file
@(client/subscribe-resource!
mcp-client
"file:///project/config.json"
handle-config-update)
;; Do work...
(Thread/sleep 60000)
;; Cleanup happens automatically via with-open
))
Declare subscription support in server capabilities:
(require '[mcp-clj.mcp-server.core :as server])
(def mcp-server
(server/create-server
{:capabilities {:resources {:subscribe true
:listChanged true}}}))
Important: Setting subscribe: true
enables:
resources/subscribe
requestsresources/unsubscribe
requestsRegister resources as usual:
(server/add-resource! mcp-server
{:uri "file:///project/config.json"
:name "Project Configuration"
:description "Main configuration file"
:mime-type "application/json"
:implementation (fn [_context _uri]
{:contents [{:uri "file:///project/config.json"
:mimeType "application/json"
:text (slurp "config.json")}]})})
When a resource changes, notify subscribers:
;; Simple notification
(server/notify-resource-updated! mcp-server "file:///project/config.json")
;; With metadata
(server/notify-resource-updated!
mcp-server
"file:///project/config.json"
{:reason "manual-edit"
:timestamp (System/currentTimeMillis)})
Note: Only clients subscribed to that specific URI will receive the notification.
(require '[mcp-clj.mcp-server.core :as server])
(def config-file "config.json")
(defn watch-config-file [mcp-server]
"Watch config file and notify on changes"
(let [watcher (java.nio.file.FileSystems/getDefault)
path (java.nio.file.Paths/get config-file (into-array String []))]
;; Simplified file watching - use proper file watcher in production
(future
(loop [last-modified (.lastModified (java.io.File. config-file))]
(Thread/sleep 1000)
(let [current-modified (.lastModified (java.io.File. config-file))]
(when (> current-modified last-modified)
(server/notify-resource-updated!
mcp-server
"file:///project/config.json"
{:timestamp current-modified}))
(recur current-modified))))))
(defn run-server []
(let [mcp-server (server/create-server
{:capabilities {:resources {:subscribe true}}})]
;; Register the config resource
(server/add-resource! mcp-server
{:uri "file:///project/config.json"
:name "Config"
:mime-type "application/json"
:implementation (fn [_ctx _uri]
{:contents [{:uri "file:///project/config.json"
:mimeType "application/json"
:text (slurp config-file)}]})})
;; Start watching for changes
(watch-config-file mcp-server)
;; Server runs until stopped
@(promise)))
;; Good: Fast callback with async work
(client/subscribe-resource!
client
uri
(fn [notification]
(future ; Offload to background thread
(process-update notification))))
;; Bad: Slow blocking callback
(client/subscribe-resource!
client
uri
(fn [notification]
(expensive-database-query notification))) ; Blocks notification thread!
resources/list_changed
instead;; Good: Informative metadata
(server/notify-resource-updated!
server
uri
{:change-type "content-update"
:affected-sections ["database" "cache"]
:triggered-by "admin-user"})
;; Acceptable: No metadata
(server/notify-resource-updated! server uri)
Problem: Server doesn't implement subscription methods.
Solutions:
subscribe: true
in capabilitiesChecklist:
subscribe-resource!
notify-resource-updated!
when changes occur?Problem: Server memory grows over time.
Solutions:
subscribe: true
in server capabilities (enables automatic cleanup)(subscribe-resource! client uri callback-fn)
- Subscribe to resource updates(unsubscribe-resource! client uri)
- Remove subscription(read-resource client uri)
- Read current resource content(notify-resource-updated! server uri)
- Notify subscribers of update(notify-resource-updated! server uri meta)
- Notify with metadata(notify-resources-list-changed server)
- Notify that resource list changedClient callbacks receive a map:
{:uri "file:///project/config.json"
:meta {:timestamp 1633024800000
:reason "file-modified"}}
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
Ctrl+k | Jump to recent docs |
← | Move to previous article |
→ | Move to next article |
Ctrl+/ | Jump to the search field |