Mcparen is a small Common Lisp client for the Model Context Protocol. Its core loads on SBCL and Clozure CL on Unix-like systems and implements MCP 2025-11-25 and 2025-06-18 over standard I/O and Streamable HTTP. It requests the newer revision and accepts the older revision when selected during protocol negotiation.
The library handles:
- JSON-RPC lifecycle
- concurrent requests
- typed errors
- timeouts and cancellation
- cursor pagination
- tool calls, resources, and prompts
- HTTP sessions
- bounded process cleanup
List pagination is bounded before pages are accumulated. One list operation may
read at most 1,000 pages and retain at most 10,000 items, 16 MiB of conservative
encoded item and cursor data, and 100,000 item and cursor tree nodes. Session
restarts begin a fresh accumulation and are limited to eight attempts. These
reloadable bounds are exported with the *mcp-pagination- prefix.
(ql:quickload :mcparen)Create a lazy Streamable HTTP client:
(defparameter *client*
(mcparen:make-mcp-client
(mcparen:make-mcp-streamable-http-transport
"https://example.test/mcp"
:headers-function
(lambda ()
(let ((token (uiop:getenv "EXAMPLE_MCP_TOKEN")))
(and token
(list (cons "Authorization"
(format nil "Bearer ~A" token)))))))
:name "example-client"
:version "1.0.0"))
(unwind-protect
(progn
(mcparen:mcp-client-connect *client*)
(dolist (tool (mcparen:mcp-client-list-tools *client*))
(format t "~A: ~A~%"
(mcparen:mcp-tool-name tool)
(mcparen:mcp-tool-description tool))))
(mcparen:mcp-client-close *client*))Create a standard I/O client:
(mcparen:make-mcp-client
(mcparen:make-mcp-stdio-transport
"/absolute/path/to/server"
:arguments '("--stdio")
:directory #P"/workspace/"))Credential providers run at request or process-launch time. Callers can retain environment-variable names instead of secret values in long-lived objects.
Streamable HTTP transports also accept :exchange-scope-function. This function
receives and synchronously invokes a zero-argument thunk around each complete
HTTP exchange, from late header resolution through response parsing and
dispatch. It can establish application-owned dynamic state for normal requests,
SSE resumptions, idle listener cycles, and session deletion.
Both transport constructors accept :maximum-message-characters. The default
is 16 MiB. Mcparen rejects larger standard I/O messages, HTTP JSON bodies, SSE
lines, and aggregate SSE event data before retaining an unbounded document.
Outbound documents are checked against the same transport-specific limit before
encoding. Every document is also held to the structural bounds in
*mcp-json-limits*, an argo json-limits value that defaults to
argo:*json-strict-limits* (nesting depth, nodes, string and key lengths,
members, elements, and number lengths). Exceeding the character limit signals
mcp-message-too-large; malformed JSON, an exceeded structural bound, or an
outbound value with no JSON form signals mcp-protocol-error.
Mcparen decodes and encodes with argo and uses its value model for every public raw MCP value:
- JSON objects are hash tables with
equalstring keys - JSON arrays are vectors
- JSON strings and numbers are Common Lisp strings and numbers
- JSON
trueisT - JSON
falseis the marker returned byargo:json-false, whichargo:json-getreads asNIL - JSON
nullisNIL; usegethashorargo:json-get-presentwhere a present null must differ from an absent member
Decoding is strict RFC 8259. json-object is argo’s constructor of an object
from alternating string keys and values. When building values, yason’s boolean
symbols and :null are also accepted. These representations are also used by tool schemas, raw tool metadata, tool
arguments, structured results, resources, and prompts.
mcp-client-connect performs initialize followed by
notifications/initialized. Public operations reconnect a deliberately closed
client when it is used again. mcp-client-close terminates owned resources.
mcp-client-detach only closes inherited descriptors and is suitable for a
forked image that must not signal a parent-owned server.
On supported POSIX systems, a standard I/O server must enter a process group led by its direct child process. Shutdown closes stdin, then uses bounded TERM and KILL phases.
Load mcparen/managed for shared restartable connections and discovery snapshots.
Construct mcp-managed-server instances with :name, :required-p and either
:client or :client-factory. A factory receives the server and a notification
callback; pass that callback to the transport constructor.
(asdf:load-system :mcparen/managed)
(defparameter *servers*
(make-instance 'mcparen:mcp-connection-manager
:maximum-schema-bytes (* 1024 1024)
:runtimes
(list (make-instance
'mcparen:mcp-managed-server :name "example"
:client-factory
(lambda (server notification-handler)
(declare (ignore server))
(mcparen:make-mcp-client
(mcparen:make-mcp-streamable-http-transport
"https://example.test/mcp"
:notification-handler notification-handler)))))))
(unwind-protect
(progn
(mcparen:mcp-manager-start *servers*)
(mcparen:mcp-manager-snapshot *servers*))
(mcparen:mcp-manager-close *servers*))Use mcp-manager-build with zero-argument server factories for cleanup across
factory and partial-startup failures. A server has one lifecycle owner; share
that manager among application consumers. Required servers receive aggregate
schema budget first, preserving presentation order. Optional failures are
isolated, and failed discovery is cached until a refresh is requested.
Use mcp-manager-refresh for explicit rediscovery, or
mcp-server-runtime-connect at use boundaries. Notifications and transparent
reconnections invalidate discovery. Read detached mcp-discovery-snapshot
values through mcp-server-runtime-snapshot or mcp-manager-snapshot; each
contains state, capabilities, generation, revision, tools and a diagnostic.
Use mcp-manager-collect to aggregate resources or prompts, supplying a client
list function and optionally an item identity function. Results and failures
are separate, server-qualified lists; duplicate identities within a server
are rejected when an identity function is supplied.
Use mcp-server-runtime-call for scoped client operations. Consume or project
secret-bearing results inside its callback before returning to the caller.
Specialize the mcp-managed- protocol for credential scopes, diagnostic
redaction, metadata projection and provider-specific schema validation.
mcp-managed-credential-key returns a non-secret identity of the exact active
credential snapshot and an optional resolution condition; enable persistent
identity checks with mcp-managed-credential-check-p. Specialize
mcp-managed-call-with-local-cleanup when local fallback needs application
bindings without fresh credential resolution. Before callback entry, errors
and nonlocal scope exits still trigger exactly one local teardown attempt. Use
mcp-server-runtime-cancel to interrupt in-flight requests before closing.
Close and detach leave connections restartable. Manager teardown is serialized
against discovery.
The lock order is manager, server, then notification revision. Scope and projection methods run synchronously. Within those methods, inspect the given server rather than reentering manager operations.
./script/bootstrap
./script/check
./script/check --fix./script/check runs the tests, then checks the source with
cheque as cheque.sexp
configures it. --fix skips the tests and repairs what cheque can repair.
cheque and its tools are development dependencies: checks/qlfile lists them,
and the check installs them on first use into ~/.cache/cheque/tools/.
Part of the Lambda Symbolics library shelf.
Applications that store MCP server definitions can validate and detach a
configuration without starting a process or reading the environment. Use
mcp-read-transport-configuration with a plist whose :type is :stdio or
:http.
(mcp-read-transport-configuration
'(:type :stdio
:command "/usr/local/bin/example-mcp"
:arguments ("--stdio")
:directory :workspace
:environment (("API_TOKEN" :environment "EXAMPLE_API_TOKEN")))
:default-directory "/srv/example")
(mcp-read-transport-configuration
'(:type :http
:url "https://example.test/mcp"
:headers (("Authorization" :environment "EXAMPLE_TOKEN"))
:connect-timeout-seconds 15))The reader returns an mcp-stdio-transport-configuration or
mcp-http-transport-configuration. Stdio values are available through
mcp-stdio-configuration-command, mcp-stdio-configuration-arguments,
mcp-stdio-configuration-directory, and
mcp-stdio-configuration-environment-bindings. HTTP values are available
through mcp-http-configuration-url,
mcp-http-configuration-header-bindings, and
mcp-http-configuration-connect-timeout-seconds. Bindings use
mcp-environment-binding-target and mcp-environment-binding-source.
The reader accepts only bounded ASCII environment names and validates the URL
scheme and authority. HTTPS accepts valid DNS and IP authorities. HTTP is
limited to localhost and loopback IP authorities. Password-bearing authorities,
reserved MCP headers, duplicate names, unknown plist keys, malformed lists,
and values outside the transport bounds signal mcp-configuration-error.
When :directory is omitted, :default-directory defaults to :workspace.
An explicit :workspace value is retained for the caller to resolve. Transport
limits are exported reloadable parameters. The reader accepts a default HTTP
connection timeout and a maximum timeout through keyword arguments.
mcp-validate-input-schema validates and detaches a JSON-compatible MCP input
schema. It returns two values: the normalized hash table and its compact UTF-8
byte count. Missing type defaults to "object" and missing
properties defaults to an empty object. An object-containing type union is
accepted. Present properties and required values are checked for their
required JSON shapes. JSON false, null, and annotation members are preserved.
(multiple-value-bind (schema bytes)
(mcp-validate-input-schema
(json-object "type" "object"
"properties"
(json-object "name" (json-object "type" "string"))))
(format t "~D bytes~%" bytes)
schema)The original value and normalized value are checked against the supplied byte,
depth, node, string, and key limits. Cycles, unrepresentable Lisp values, and
limit violations signal mcp-input-schema-error, whose reason and value
readers identify the failure.