Guide to using the MCP logging utility to send structured log messages to your clients.
The MCP logging utility allows MCP servers built with mcp-clj to send structured log messages to their clients (like Claude Desktop). This provides a standardized way for servers to communicate diagnostic and operational information to clients.
Important: This is separate from the internal mcp-clj.log component, which is used for debugging the mcp-clj framework itself.
To enable logging capability, pass :logging {} in the :capabilities map when creating your server:
(require '[mcp-clj.mcp-server.core :as mcp])
(def server
  (mcp/create-server
    {:transport {:type :stdio}
     :capabilities {:logging {}}}))  ; Enable logging
When enabled, the server will:
logging capability during initializationlogging/setLevel requests from clientsnotifications/messageUse the convenience functions from mcp-clj.mcp-server.logging:
(require '[mcp-clj.mcp-server.logging :as logging])
;; Send error-level message
(logging/error server {:error "Connection failed" :host "localhost"}
               :logger "database")
;; Send info-level message
(logging/info server {:status "Server started"})
;; Send warning without logger
(logging/warn server {:msg "High memory usage"})
The MCP protocol supports 8 RFC 5424 severity levels (ordered from most to least severe):
| Level | Function | Description | Example Use Case | 
|---|---|---|---|
| emergency | (logging/emergency ...) | System is unusable | Complete system failure | 
| alert | (logging/alert ...) | Action must be taken immediately | Data corruption detected | 
| critical | (logging/critical ...) | Critical conditions | System component failures | 
| error | (logging/error ...) | Error conditions | Operation failures | 
| warning | (logging/warn ...) | Warning conditions | Deprecated feature usage | 
| notice | (logging/notice ...) | Normal but significant events | Configuration changes | 
| info | (logging/info ...) | General informational messages | Operation progress updates | 
| debug | (logging/debug ...) | Detailed debugging information | Function entry/exit points | 
For dynamic log level selection:
(logging/log-message server :error {:data "Something went wrong"}
                     :logger "my-component")
Clients can set their minimum log level using the logging/setLevel request. The server will only send messages at or above the client's threshold.
Default behavior: When a client hasn't set a log level, it defaults to :error, meaning it receives error, critical, alert, and emergency messages.
Example client request:
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "logging/setLevel",
  "params": {
    "level": "warning"
  }
}
After this request, the client will receive warning, error, critical, alert, and emergency messages.
Organize logs by component or subsystem:
(logging/error server {:msg "Query timeout"} :logger "database")
(logging/info server {:msg "Request processed"} :logger "api")
Use maps with clear keys rather than concatenated strings:
;; Good
(logging/error server
  {:error "Connection failed"
   :host "localhost"
   :port 5432
   :retry-count 3}
  :logger "database")
;; Less useful
(logging/error server
  "Connection to localhost:5432 failed after 3 retries"
  :logger "database")
NEVER include sensitive data in log messages:
❌ Avoid:
✅ Do:
;; Sanitize before logging
(logging/error server
  {:error "Authentication failed"
   :username (mask-username username)  ; Mask sensitive parts
   :ip-address client-ip}
  :logger "auth")
| Feature | MCP Logging ( mcp-clj.mcp-server.logging) | Internal Logging ( mcp-clj.log) | 
|---|---|---|
| Purpose | Send logs to MCP clients | Debug mcp-clj framework | 
| Destination | MCP clients (Claude Desktop, etc.) | stderr | 
| When to use | Application-level logging | Framework debugging | 
| Audience | End users/client applications | mcp-clj developers | 
(ns my-app.server
  (:require
    [mcp-clj.mcp-server.core :as mcp]
    [mcp-clj.mcp-server.logging :as logging]))
(defn my-tool-implementation
  [args]
  (logging/info server {:msg "Tool invoked" :args args} :logger "tools")
  (try
    (let [result (do-something args)]
      (logging/debug server {:result result} :logger "tools")
      result)
    (catch Exception e
      (logging/error server
        {:error (.getMessage e)
         :args args}
        :logger "tools")
      (throw e))))
(def server
  (mcp/create-server
    {:transport {:type :stdio}
     :capabilities {:logging {}}
     :tools {"my-tool" {:name "my-tool"
                        :description "Example tool"
                        :inputSchema {:type "object"}
                        :implementation my-tool-implementation}}}))
Q: My log messages aren't appearing
A: Check that:
:capabilities {:logging {}})initialized after initializeQ: Can I send logs to specific clients?
A: Currently, log messages are sent to all connected clients (filtered by their individual log levels). Per-client targeting is a future enhancement.
Q: What happens if I log before a client connects?
A: Messages are only sent to connected, initialized clients. Messages sent before client connection are not queued.
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 |