Skip to content

Instantly share code, notes, and snippets.

@jacobemcken
Last active July 11, 2025 21:53
Show Gist options
  • Save jacobemcken/441244529a4d68e27db514fe7fa7ae7e to your computer and use it in GitHub Desktop.
Save jacobemcken/441244529a4d68e27db514fe7fa7ae7e to your computer and use it in GitHub Desktop.
Exploring writing "Tool servers" for AI with Babashka

Exploring writing "Tool servers" for AI with Babashka

A tool server is just a REST API that includes an OpenAPI specification, that the AI can use to navigate how to leverage the API.

Preferrably, the OpenAPI specification and the actual code should only be maintained using a single source of truth.

So far I've only found Reitit which seems to work when using vanilla Clojure: https://github.com/metosin/reitit/tree/master/examples/openapi

I've reduced the Clojure dependencies while still getting an Open API specificatoin, to the following, and highlighted which would cause problems when trying to use Babashka:

  • βœ… http-kit
  • πŸ”§ metosin/reitit-core
    • βœ… meta-merge
    • πŸ”§ has a Java class reitit.trie
  • ❌ metosin/reitit-ring
    • πŸ”§ metosin/reitit-core
    • ❌ ring/ring-core
      • βœ… org.ring-clojure/ring-core-protocols
      • βœ… org.ring-clojure/ring-websocket-protocols
      • βœ… ring/ring-codec
      • πŸ”§ commons-io
      • ❌ org.apache.commons/commons-fileupload2-core
      • πŸ”§ crypto-random
      • βœ… crypto-equality
    • fi.metosin/reitit-openapi

βœ… Works.
πŸ”§ Might have workaround.
❌ A good solution will be though.

metosin/reitit-core

The depencency meta-merge has no dependencies of its own: https://github.com/weavejester/meta-merge/blob/master/project.clj

But it requries a custom Java class for reitit.trie for performance. I wonder how it is done i ClojureScript πŸ€”

metosin/reitit-ring

Is mainly just a combination of problems from reitit-code and ring-code.

ring-code

org.apache.commons/commons-fileupload2-core is being used for "multipart" file uploads, which propably isn't a feature that can or should be excluded. Code for the dependency is here: https://github.com/apache/commons-fileupload/blob/master/commons-fileupload2-cor

Other dependencies might have easier workarounds. common-io is only providing a Java method to convert an InputStream to a byte array. The following might not be as performant, but should have the same signature (can/should this be written i Clojure?):

package ring;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.IOException;

public final class IOUtils {
    private IOUtils () {}

// https://codingtechroom.com/question/io-utils-to-byte-array-alternatives
    public static byte[] toByteArray(final InputStream inputStream) throws IOException {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            byteArrayOutputStream.write(buffer, 0, bytesRead);
        }
        return byteArrayOutputStream.toByteArray();
    }
}

Also, crypto-random seems to only be included to provide crypto.random/bytes which could be copied into the source code to avoid the other (unused) Java dependencies.

  ...
  (:import java.security.SecureRandom))

(defn random-bytes
  "Returns a random byte array of the specified size."
  [size]
  (let [seed (byte-array size)]
    (.nextBytes (SecureRandom.) seed)
    seed))

And (FileUtils/writeStringToFile resource "just testing") was used in a test and could be replaced with (spit resource "just testing").

Conclusion

There seems be quite a bit of work ahead to get Reitit and its OpenAPI library working with Babashka. Also the dependencies and setup for Reitit is a bit confusing. E.g. it isn't obvious that one needs :muuntaja m/instance and cannot just apply some wrap-json-response middleware.

(defproject test-server "0.1.0-SNAPSHOT"
:dependencies [[org.clojure/clojure "1.12.1"]
[http-kit "2.8.0"]
[metosin/reitit-core "0.9.1"]
[metosin/reitit-ring "0.9.1"]
[metosin/reitit-middleware "0.9.1"]
[metosin/muuntaja "0.6.11"]
[fi.metosin/reitit-openapi "0.9.1"]]
:repl-options {:init-ns test-server.core})
(ns test-server.core
(:require [reitit.ring :as ring]
[org.httpkit.server :as http]
[reitit.openapi :as openapi]
[reitit.ring.middleware.muuntaja :as muuntaja]
[muuntaja.core :as m]))
(def routes
[["/openapi.json"
{:get {:openapi {:info {:title "Some tool server API"
:description "reitit + openapi3"
:version "0.0.1"}
:tags [{:name "api"
:description "the api"}]}
:handler (openapi/create-openapi-handler)}}]
["/api"
{:tags ["api"]}
["/hello"
{:get {:summary "hello"
:handler (fn [_]
{:status 200
:body "hello world"})}}]
["/plus"
{:post {:summary "plus with malli body parameters"
:description "description here"
:parameters {:body {:x :int, :y :int}}
:responses {200 {:body {:total :int}}}
:handler (fn [request]
(let [{:keys [x y]} (-> request :parameters :body)]
{:status 200
:body {:total (+ x y)}}))}}]]])
(def app
(ring/ring-handler
(ring/router
routes
{:data {:muuntaja m/instance
:middleware [muuntaja/format-response-middleware]}})))
(defn -main []
(http/run-server app {:port 3000})
(println "Server started at http://localhost:3000"))
; run with lein run -m test-server.core/-main
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment