Guide to receiving and managing structured log messages from MCP servers.
MCP clients built with mcp-clj can receive structured log messages from servers that support the logging utility. This allows clients to monitor server operations, diagnose issues, and provide visibility into server behavior.
The client logging API provides:
Control which log messages the server sends by setting a minimum log level. The server will only send messages at or above this level.
(require '[mcp-clj.mcp-client.core :as client])
;; Set log level to warning (receive warning, error, critical, alert, emergency)
@(client/set-log-level! mcp-client :warning)
;; Set log level to debug (receive all messages)
@(client/set-log-level! mcp-client :debug)
;; Set log level to error (receive only error, critical, alert, emergency)
@(client/set-log-level! mcp-client :error)
Important: Log level filtering happens on the server side. All subscribed callbacks receive the same filtered messages. The server does the filtering based on the level you set.
The 8 RFC 5424 severity levels (ordered from most to least severe):
Keyword | String | Description |
---|---|---|
:emergency | "emergency" | System is unusable |
:alert | "alert" | Action must be taken immediately |
:critical | "critical" | Critical conditions |
:error | "error" | Error conditions |
:warning | "warning" | Warning conditions |
:notice | "notice" | Normal but significant events |
:info | "info" | General informational messages |
:debug | "debug" | Detailed debugging information |
Subscribe to receive log messages from the server with a callback function:
(require '[mcp-clj.mcp-client.core :as client])
;; Subscribe to log messages
(def unsub-future
(client/subscribe-log-messages!
mcp-client
(fn [{:keys [level logger data]}]
(println (str "[" (name level) "]"
(when logger (str " " logger ":"))
" "
(pr-str data))))))
;; Get the unsubscribe function
(def unsub @unsub-future)
;; Later, unsubscribe
(unsub)
The callback receives a map with the following keys:
:level
- Keyword log level (:error
, :warning
, etc.):logger
- Optional string component name (may be nil
):data
- Message data (map, string, or other value)Example callback data:
{:level :error
:logger "database"
:data {:error "Connection failed" :host "localhost"}}
Multiple callbacks can subscribe simultaneously. Each receives all filtered messages:
;; Logger for console
(def unsub1
@(client/subscribe-log-messages!
mcp-client
(fn [msg] (println "Console:" msg))))
;; Logger for file
(def unsub2
@(client/subscribe-log-messages!
mcp-client
(fn [msg] (spit "app.log" (str msg "\n") :append true))))
;; Both receive the same messages
;; Later, unsubscribe individually
(unsub1)
(unsub2)
set-log-level!
validates the level before sending to the server:
;; Throws ExceptionInfo with :invalid-log-level
(client/set-log-level! mcp-client :invalid)
;; => ExceptionInfo: Invalid log level {:level :invalid, :valid-levels (...)}
;; Also rejects common typos
(client/set-log-level! mcp-client :warn) ; Should be :warning
;; => ExceptionInfo: Invalid log level
Exceptions in callbacks are caught and logged to prevent crashing the client:
(client/subscribe-log-messages!
mcp-client
(fn [msg]
(throw (ex-info "Callback error" {})))) ; Error is logged, client continues
;; Other subscribers still receive messages
When calling set-log-level!
, the client checks if the server declared the logging
capability. If not, a warning is logged but the request is still sent.
(ns my-app.client
(:require
[mcp-clj.mcp-client.core :as client]))
(defn setup-logging
[mcp-client]
;; Set log level to info
@(client/set-log-level! mcp-client :info)
;; Subscribe with formatted output
(client/subscribe-log-messages!
mcp-client
(fn [{:keys [level logger data]}]
(let [timestamp (.format (java.time.LocalDateTime/now)
(java.time.format.DateTimeFormatter/ISO_LOCAL_TIME))
logger-str (if logger (str " [" logger "]") "")
level-str (-> level name .toUpperCase)]
(println (format "%s %s%s: %s"
timestamp
level-str
logger-str
(pr-str data)))))))
;; Example output:
;; 14:23:45.123 INFO [api]: {:status "Request processed"}
;; 14:23:46.456 ERROR [database]: {:error "Connection failed", :host "localhost"}
For production:
@(client/set-log-level! mcp-client :warning) ; Only warnings and errors
For development/debugging:
@(client/set-log-level! mcp-client :debug) ; All messages
Process structured data in callbacks:
(client/subscribe-log-messages!
mcp-client
(fn [{:keys [level data]}]
(when (map? data)
;; Extract specific fields
(when-let [error (:error data)]
(alert-monitoring-system! error))
;; Log to analytics
(track-event! {:event "server-log"
:level level
:error-type (:error data)}))))
Always unsubscribe to prevent resource leaks:
(let [unsub @(client/subscribe-log-messages! client callback)]
(try
(do-work)
(finally
(unsub))))
Log data can be strings, maps, or other values:
(client/subscribe-log-messages!
mcp-client
(fn [{:keys [data]}]
(cond
(string? data) (println "Message:" data)
(map? data) (println "Structured:" (pr-str data))
:else (println "Other:" (pr-str data)))))
Aspect | Client Logging | Server Logging |
---|---|---|
Direction | Server → Client | Server → Clients |
Purpose | Monitor server operations | Send operational info to clients |
API | set-log-level! , subscribe-log-messages! | logging/error , logging/info , etc. |
Filtering | Server-side (via set-log-level! ) | Per-client level checking |
Namespace | mcp-clj.mcp-client.core | mcp-clj.mcp-server.logging |
Q: Not receiving log messages A: Check that:
logging
capability in its initialize
responsesubscribe-log-messages!
Q: How do I see all log levels? A:
@(client/set-log-level! mcp-client :debug) ; Lowest level = all messages
Q: Can I filter by logger name? A: No. Filtering happens in your callback:
(client/subscribe-log-messages!
mcp-client
(fn [{:keys [logger] :as msg}]
(when (= logger "database")
(process-db-log! msg))))
Q: Does unsubscribing send a request to the server?
A: No. Unsubscribing only removes the local callback. The server continues sending messages based on the log level you set with set-log-level!
.
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 |