By default, the MCP server operates synchronously. This guide explains how to enable asynchronous execution when needed.
The simplest way to make your handlers asynchronous is to return CompletableFuture objects from your tool handlers. You can generate these futures using any method you prefer:
(defn async-tool-handler [exchange arguments]
(CompletableFuture/supplyAsync
(fn []
;; Your async work here
"Result from async operation")))
When you return CompletableFuture objects, only your specific handlers become asynchronous. The internal JSON-RPC handlers (such as ping, client responses, and initialization) will still run synchronously because they're not exposed to your code.
To make all dispatch table handlers asynchronous, you can wrap them during or after dispatch table creation. This approach affects both your handlers and the internal system handlers.
(server/add-async-to-dispatch (server/make-dispatch))
This wraps all handlers with a virtual thread executor (if available in your Java version) or falls back to a cached thread pool executor.
(server/add-async-to-dispatch (server/make-dispatch) my-executor)
Replace my-executor with your preferred ExecutorService implementation.
The add-async-to-dispatch function uses the middleware system to wrap all dispatch handlers:
(rpc/with-middleware dispatch [[rpc/wrap-executor executor]])
This approach ensures consistent asynchronous behavior across all handlers in your MCP server.
Can you improve this documentation? These fine people already did:
Rok Lenarcic & Rok Lenarčič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 |