Building an MCP Server with Common Lisp

I recently wanted to learn about MCP (Model Context Protocol). As someone whose default programming language is Common Lisp, I naturally decided to build an MCP server using Lisp.

Thanks to the creators of 40ants-mcp, the library provides a nice pattern and code structure that I really like. However, I struggled significantly with installation and getting started. What should have taken minutes ended up taking days.

I'm sharing my experience here so that others who want to build MCP servers in Common Lisp can get started in minutes, not days like I did.

Prerequisites

Before you begin, make sure you have the following installed:

Installing Ultralisp

In SBCL with Quicklisp, you can enable Ultralisp by:

(ql-dist:install-dist "http://dist.ultralisp.org/" :prompt nil)

(How to install SBCL and Quicklisp is in the appendix.)

The Gotcha: Loading 40ants-mcp

Here's the issue that cost me days: when you try to load 40ants-mcp with:

(ql:quickload :40ants-mcp)

You might encounter errors. The solution is simple but not obvious—load jsonrpc first:

(ql:quickload :jsonrpc)
(ql:quickload :40ants-mcp)

This dependency isn't automatically resolved, which was the source of my frustration.

Creating Your MCP Server

Here's a minimal example from my mcp-exper package:

(in-package :mcp-exper)

(openrpc-server:define-api (mi-tools :title "mi-tools"))

(40ants-mcp/tools:define-tool (mi-tools add) (a b)
  (:summary "just add")
  (:param a integer "a")
  (:param b integer "b")
  (:result text-content)
  (make-instance 'text-content :text (format nil "~a" (+ a b))))

(defun start-server ()
  (40ants-mcp/server/definition:start-server mi-tools))

Key points:

  1. Use openrpc-server:define-api to define your API
  2. Use 40ants-mcp/tools:define-tool to define tools
  3. Return text-content instances for text results (MCP requires specific content types)

Running the Server

Create a Roswell script (mi-mcp-server.ros):

#!/bin/sh
#|-*- mode:lisp -*-|#
exec ros -Q -- $0 "$@"
|#
(progn
  (ros:ensure-asdf)
  #+quicklisp(ql:quickload '(:mcp-exper) :silent t))

(defun main (&rest argv)
  (declare (ignorable argv))
  (mcp-exper:start-server))

Quick Test

Run directly with Roswell:

ros mi-mcp-server.ros

Production Installation

Build and install as an executable:

ros build mi-mcp-server.ros
install -m 0755 mi-mcp-server $HOME/.local/bin/

Make sure $HOME/.local/bin is in your PATH.

Integrating with Opencode

To enable your MCP server in opencode, add this to ~/.config/opencode/opencode.json:

{
    "mcp": {
        "mi-tools": {
            "type": "local",
            "command": ["mi-mcp-server"],
            "enabled": true
        }
    }
}

Conclusion

Building MCP servers with Common Lisp is straightforward once you know the tricks. The 40ants-mcp library is well-designed, and the OpenRPC integration works smoothly.

I hope this guide saves you the days of frustration I experienced. Happy hacking!


The full source code for this example is available at mcp-exper.

Appendix: Installing SBCL

macOS

brew install sbcl

Debian/Ubuntu

apt install sbcl

Arch Linux

pacman -S sbcl

Appendix: Installing Quicklisp

Download and install Quicklisp:

wget https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp \
        --eval '(quicklisp-quickstart:install)' \
        --eval '(ql-util:without-prompting (ql:add-to-init-file))' \
        --quit