A Clojure SDK for building Model Context Protocol servers and clients.
MCP protocol version: 2025-06-18
Add the dependency to your deps.edn:
{:deps {io.github.cleancoders/mcp {:git/url "https://github.com/cleancoders/mcp.git" :git/sha "..."}}}See the server example for a runnable implementation.
Build a server spec and pass it to mcp.server.core/->server:
(require '[mcp.server.core :as server]
'[mcp.server.tool :as tool]
'[mcp.server.resource :as resource]
'[mcp.server.stdio :as stdio])
(def my-tool
{:name "greet"
:description "Greets the user by name"
:handler (fn [req]
(let [name (get-in req [:params :arguments :name])]
(str "Hello, " name "!")))
:inputSchema {:type {:name {:type :string}}}})
(def my-server
(-> {:name "my-server"
:server-version "0.1.0"
:protocol-version "2025-06-18"}
(tool/with-tool my-tool)
(resource/with-resource {:kind :file :path "/some/file.txt"})
server/->server))Register tools with tool/with-tool. A tool spec has these keys:
| Key | Required | Description |
|---|---|---|
:name |
yes | Unique identifier |
:description |
yes | Description shown to the LLM |
:handler |
yes | (fn [req] ...) invoked on tools/call |
:inputSchema |
no | Apron schema for tool arguments |
:outputSchema |
no | Apron schema for structured response |
:title |
no | Human-readable display name |
:annotations |
no | Additional MCP metadata |
Plain value (legacy) -- returned as JSON text content:
(fn [req] {:files ["a.txt" "b.txt"]})Structured response -- explicit control over content blocks and errors:
(fn [req]
{:structured {:files ["a.txt"]}
:content [{:type "text" :text "Found 1 file"}]
:error? true})Register file resources with resource/with-resource:
(resource/with-resource spec {:kind :file :path "/path/to/file.txt"})Resources are served via resources/list and resources/read endpoints.
Run a stdio loop for use with tools like Claude Desktop:
(loop []
(stdio/handle-stdio my-server)
(recur))Handle HTTP requests with CORS origin validation:
(require '[mcp.server.http :as http])
;; Uses default config (localhost only)
(http/handle-request ring-request my-server)
;; Custom allowed origins
(http/handle-request ring-request my-server
{:allowed-origins #{#"^https://example\.com$"}})mcp.server.shell/tool provides a pre-configured shell execution tool:
(require '[mcp.server.shell :as shell])
(tool/with-tool spec shell/tool)Enable request/response tracing by adding a :trace key to your server spec:
(require '[mcp.server.trace :as trace])
{:name "my-server"
:trace {:enabled? true
:sink (trace/->file-sink "/tmp/mcp-trace.jsonl")}}See the client example for a runnable implementation.
(require '[mcp.client.core :as client]
'[mcp.client.stdio :as client-stdio]
'[clojure.java.io :as io]
'[clojure.java.process :as process])
(let [proc (process/start {:in :pipe :out :pipe :err :inherit}
"clojure" "-Mserve")
transport (client-stdio/->IOTransport
(io/reader (process/stdout proc))
(io/writer (process/stdin proc)))
config {:transport transport
:client (client/->client {:name "my-client" :version "1.0.0"})
:next-id-fn (let [id (atom 0)] #(swap! id inc))}]
@(client/initialize! config)
@(client/request! config "tools/list"))This library implements the open Model Context Protocol and works with any MCP-compatible client.
Stdio-based MCP clients like Claude Desktop need a command that starts your server. Point it at whatever starts your server's stdio loop:
{
"mcpServers": {
"my-server": {
"command": "/absolute/path/to/your/server/start-command",
"args": ["arg1", "arg2"]
}
}
}clojure -M:test:spec