by theronic
Modex is a Clojure MCP Library to augment your AI models with Tools, Resources & Prompts using Clojure (Model Context Protocol). Implements MCP Server & Client.
# Add to your Claude Code skills
git clone https://github.com/theronic/modexModex (MOdel + ContEXt) is a Clojure library that lets you augment your AI with new tools, resources and prompts.
Modex implements (most of) the Model Context Protocol to build MCP Servers & Clients in 'native' Clojure.
Because it's native Clojure, you don't need to deal with Anthropic's MCP Java SDK.
Modex implements the stdio transport in the 2024-11-05 MCP spec, so no need for a proxy like
mcp-proxy to translate between SSE <=> stdio or vice versa.
Claude Desktop can talk to a Modex MCP Server via its MCP client:
git clone git@github.com:theronic/modex.gitcd modex./build.sh builds an uberjar at .No comments yet. Be the first to share your thoughts!
target/modex-mcp-0.2.2.jar~/Library/Application\ Support/Claude/claude_desktop_config.json{
"mcpServers": {
"modex-mcp-hello-world": {
"command": "java",
"args": ["-jar", "/Users/your-username/code/modex/target/modex-mcp-0.2.2.jar"]
}
},
"globalShortcut": ""
}
MCP lets you augment your AI models with Tools, Resources & Prompts:
Modex is used by datomic-mcp, which exposes our production Datomic databases to an MCP client like Claude Desktop. The AI model intelligently diagnoses support queries in production by reading our database schema and running queries that checks server state & IPs, so it can try to reach it and compare the desired state of VMs against the actual state in our clusters.
Over time, I hope to automate the bulk of our recurring support queries using Modex + other MCP tools.
There is an MCP server example in src/modex/mcp/core.clj that defines an MCP server with some basic tools.
Your MCP client (e.g. Claude Desktop) can connect to this server and use exposed tools to provide additional context to your AI models.
Internally, a tool is just Tool record with several Parameter arguments:
(defrecord Tool [name doc args handler])(defrecord Parameter [name doc type required default])However, it is more convenient to define tools using the tool & tools macros below.
tool macro:The tool macro acts like defrecord, where the handler definition takes a map of arguments ala {:keys [arg1 arg2 ...]} but with additional (optional) maps for :type, :or & :doc. This metadata is used to describe the tool to the MCP Client.
:string & :number tool parameter types.:or map implies optionality.(require '[modex.mcp.tools :as tools])
(def add-tool
(tools/tool
; feels like defrecord.
(add [{:keys [x y]
:type {x :number
y :number}
:or {y 0} ; y is optional due to its presence in the :or map.
:doc {x "First number"
y "Second number"}}]
[(+ x y)]))) ; tools should return a vector (to support multiple values).
invoke-tool incl. validation:Invocation uses a map of arguments like an MCP client would for a tools/call request:
(tools/invoke-tool add-tool {:x 5 :y 6}) ; Modex will map these arguments and call the handler.
=> {:success true, :results [11]} ; note :results is vector to support multiple values.
(tools/invoke-handler (:handler add-tool) {:x 5 :y 6})
=> [11] ; note vector result to support multiple values.
tools macroThe tools macro just calls the tool macro for each tool definition and returns a map of tools keyed on tool name (keyword):
(def my-tools
"Define your tools here."
(tools/tools
(greet
"Greets a person by name." ; tools can have a docstring
[{:keys [first-name last-name]
:doc {first-name "A person's first name."
last-name "A person's last name (optional)."}
:type {first-name :string
last-name :string}
:or {last-name nil}}] ; last-name is optional, implied by presence in `:or` map.
; tools should return collection.
[(str "Hello from Modex, "
(if last-name ; args can be optional
(str first-name " " last-name)
first-name) "!")])
(add
"Adds two numbers."
; Tool handler args also support deprecated vector arg-style,
; but this is superseded by the newer map-destructuring style:
[^{:type :number :doc "First number to add."} a
^{:type :number :doc "Second number to add."} b]
[(+ a b)])
(subtract
"Subtracts two numbers (- a b)"
[^{:type :number :doc "First number."} a
^{:type :number :doc "Second number."} b]
[(- a b)])
(error-handling
"This tool throws intentionally. Modex will handle errors for you."
[]
(throw (ex-info "Modex will handle exceptions." {})))))
(require '[modex.mcp.server :as server])
(def my-mcp-server
"Here we create a reified instance of AServer. Only tools are presently supported."
(server/->server
{:name "Modex MCP Server"
:version "0.0.2"
:initialize (fn [_init-params] ; init-params, but may contain client capabilities in future.
"Do long-running setup & blocking I/O here, like connecting to prod database.")
:tools my-tools
:prompts nil ; Prompts are WIP.
:resources nil})) ; Resources are WIP.
(server/start-server! my-mcp-server)
Or put that in your -main function.
Modex exposes an AServer protocol and a DSL to define tools protocols that describe MCP servers, which expose tools, resources & prompts.
AServer Protocol:
(defprotocol AServer
(protocol-version [this])
(server-name [this])
(version [this])
(capabilities [this])
(initialize [this _init-params]) ; init-params is empty for now, but may contain client capabilities in future.
(list-tools [this])
(call-tool [this tool-name arg-map])
(list-resources [this])
(list-prompts [this]))
Before you can run it, you have to build it first. The build outputs an uberjar, which is like a Java executable.
clojure -T:build uber
or run the helper which does that:
./build.sh
(you might need to run chmod +x build.sh)
Open your Claude Desktop Configuration file, claude_desktop_config.json, which on MacOS should be at:
~/Library/Application\ Support/Claude/claude_desktop_config.json
Add an element under mcpServers so it looks like this:
{
"mcpServers": {
"modex": {
"command": "java",
"args": ["-jar", "/Users/your-username/code/modex/target/modex-mcp-0.2.2.jar"]
}
},
"globalShortcut": ""
}
This tells Claude Desktop there is a tool named modex and it can connect to by running java -jar /path/to/your/uber.jar.
The way this works is that your local MCP Client (i.e. Claude Desktop), starts your MCP server process and communicates with it via stdin/stdout pipes.
You should now be able to ask Claude "run foo", or "what does foo say?" and it will run
the foo tool and reply with the response, "Hello, AI!".
Modex implements an MCP client & server in Clojure that is mostly compliant with the 2024-11-05 MCP Spec.
Messages are encoded using the JSON-RPC 2.0 wire format.
There are 3 message types:
{:keys [id method ?params]}{:keys [id result ?error]}{:keys [method ?params}MCP supports two transport types:
There is an existing library [mcp-clj](https://github.com/hugodun