A Model Context Protocol (MCP) server for Common Lisp, providing JSON-RPC 2.0 over stdio, TCP, and HTTP (Streamable HTTP). It enables AI agents to interact with Common Lisp environments through structured tools for REPL evaluation, system loading, file operations, code introspection, and structure-aware editing.
This repo is test-first and designed for editor/agent clients to drive Common Lisp development via MCP.
- JSON‑RPC 2.0 request/response framing (one message per line)
- MCP initialize handshake with capability discovery
notifications/cancelledsupport for cooperative request cancellation- Worker pool isolation — eval-dependent tools run in an isolated child SBCL process, separating the MCP server from potentially crashing evaluations. Features automatic crash recovery, circuit breaker, and per-session affinity for multi-client setups
- Tools API
repl-eval— evaluate forms (returnsresult_object_idfor non-primitive results)load-system— load ASDF systems with force-reload, output suppression, and timeout supportinspect-object— drill down into complex objects (CLOS instances, hash-tables, lists, arrays) by IDfs-read-file/fs-write-file/fs-list-directory— project-scoped file access with allow‑listfs-get-project-info— report project root and cwd info for path normalizationfs-set-project-root— set the server's project root and working directorylisp-read-file— Lisp-aware file viewer with collapsed/expanded modescode-find/code-describe/code-find-references— sb-introspect based symbol lookup/metadata/xreflisp-edit-form— structure-aware edits to top-level forms (replace/insert) using Eclector CSTlisp-patch-form— scoped text replacement within a matched form (token-efficient small edits)lisp-check-parens— detect mismatched delimiters in code slicesclgrep-search— semantic grep for Lisp files with structure awarenessclhs-lookup— Common Lisp HyperSpec reference (symbols and sections)run-tests— unified test runner with structured results (Rove, ASDF fallback)pool-status— worker pool diagnostics (worker counts, states, pool health)pool-kill-worker— kill and optionally reset the session's worker process
- Transports:
:stdio,:tcp, and:http(Streamable HTTP for Claude Code) - Structured JSON logs with level control via env var
- Rove test suite wired through ASDF
test-op
- Protocol versions recognized:
2025-06-18,2025-03-26,2024-11-05- On
initialize, if the client’sprotocolVersionis supported it is echoed back; if it is not supported the server returnserror.code = -32602withdata.supportedVersions.
- On
Eval-dependent tools (repl-eval, load-system, run-tests, code-find,
code-describe, code-find-references, inspect-object) run in isolated
child SBCL processes rather than the parent MCP server image.
- Per-session affinity — each MCP session is bound to a dedicated worker process. The worker persists for the session lifetime, preserving loaded systems and REPL state.
- Automatic crash recovery — if a worker crashes, a replacement is spawned and re-bound to the same session automatically.
- Circuit breaker — if a session's worker crashes repeatedly (3 times within 5 minutes by default), the pool stops respawning and reports the failure to the client.
- Warm standby pool — pre-spawned workers reduce first-request latency.
- Dual launcher — workers can be spawned via Roswell (
ros) or bare SBCL, auto-detected at startup.
File-system tools (fs-*, lisp-read-file, lisp-edit-form, lisp-patch-form, lisp-check-parens,
clgrep-search, clhs-lookup) continue to run inline in the parent process.
Disable the worker pool by setting MCP_NO_WORKER_POOL=1. When disabled, all
tools run inline in the parent process (single-image mode).
- SBCL 2.x (developed with SBCL 2.5.x)
- Quicklisp (optional; pure ASDF also works)
- Dependencies (via ASDF/Quicklisp): runtime —
alexandria,cl-ppcre,yason,usocket,bordeaux-threads,eclector,hunchentoot; tests —rove; optional —clhs(loaded on-demand byclhs-lookuptool).
IMPORTANT: Set the MCP_PROJECT_ROOT environment variable before starting:
export MCP_PROJECT_ROOT=/path/to/your/projectLoad and run from an existing REPL:
(asdf:load-system :cl-mcp) ; or (ql:quickload :cl-mcp) if using Quicklisp
;; Start TCP transport on port 12345 in a new thread.
(cl-mcp:start-tcp-server-thread :port 12345)Or run a minimal stdio loop (one JSON‑RPC line per request):
# With environment variable
export MCP_PROJECT_ROOT=$(pwd)
ros run -s cl-mcp -e "(cl-mcp:run :transport :stdio)"Alternative: If you don't set MCP_PROJECT_ROOT, you must call fs-set-project-root
tool immediately after connecting to initialize the project root.
Start the HTTP server and continue using your REPL:
(asdf:load-system :cl-mcp) ; or (ql:quickload :cl-mcp)
;; Start HTTP server on port 3000 (default)
(cl-mcp:start-http-server :port 3000)
;; Server is now running at http://127.0.0.1:3000/mcp
;; You can continue using your REPL normally
(+ 1 2) ; => 3
;; Stop the server when done
(cl-mcp:stop-http-server)Configure Claude Code to connect (in ~/.claude/settings.json or project .mcp.json):
{
"mcpServers": {
"cl-mcp": {
"type": "url",
"url": "http://127.0.0.1:3000/mcp"
}
}
}This is the recommended approach for Common Lisp development:
- Start your REPL as usual
- Load cl-mcp and start the HTTP server
- Configure Claude Code to connect
- Both you and Claude Code can use the same Lisp runtime simultaneously
- Python TCP one‑shot client (initialize):
python3 scripts/client_init.py --host 127.0.0.1 --port 12345 --method initialize --id 1- Stdio↔TCP bridge (connect editor’s stdio to the TCP server):
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' | \
python3 scripts/stdio_tcp_bridge.py --host 127.0.0.1 --port 12345The bridge uses a bounded connect timeout but disables read timeouts after connecting, so it can stay idle indefinitely (until stdin closes).
| Variable | Purpose | Default |
|---|---|---|
MCP_PROJECT_ROOT |
Project root directory for file operations | (required) |
MCP_LOG_LEVEL |
Log level: debug, info, warn, error |
info |
MCP_LOG_FILE |
Log to file (timestamped with PID, e.g., /tmp/cl-mcp.log → /tmp/cl-mcp-2026-03-01T09-15-30-12345.log) |
(stderr only) |
MCP_NO_WORKER_POOL |
Set to 1 to disable worker pool isolation |
(not set = pool enabled) |
Evaluate one or more forms and return the last value as a text item.
Input schema (JSON):
code(string, required): one or more s‑expressionspackage(string, optional): package to evaluate in (defaultCL-USER)print_level(integer|null): binds*print-level*print_length(integer|null): binds*print-length*timeout_seconds(number|null): abort evaluation after this many secondsmax_output_length(integer|null): truncatecontent/stdout/stderrto this many characterssafe_read(boolean|null): whentrue, disables*read-eval*while reading forms Output fields:content: last value as textstdout: concatenated standard output from evaluationstderr: concatenated standard error from evaluationresult_object_id(integer|null): when the result is a non-primitive object (list, hash-table, CLOS instance, etc.), this ID can be used withinspect-objectto drill down into its internal structureerror_context(object|null): when an error occurs, contains structured error info includingcondition_type,message,restarts, andframeswith local variable inspection
Example JSON‑RPC request:
{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"repl-eval","arguments":{"code":"(+ 1 2)"}}}Response (excerpt):
{"result":{"content":[{"type":"text","text":"3"}]}}Drill down into non-primitive objects by ID. Objects are registered when repl-eval
returns non-primitive results (the result_object_id field).
Input:
id(integer, required): Object ID fromrepl-eval'sresult_object_idor from a previousinspect-objectcallmax_depth(integer, optional): Nesting depth for expansion (0=summary only, default=1)max_elements(integer, optional): Maximum elements for lists/arrays/hash-tables (default=50)
Output fields:
kind: Object type (list,hash-table,array,instance,structure,function,other)summary: String representation of the objectid: The object's registry ID- Type-specific fields:
- Lists:
elementsarray with nested value representations - Hash-tables:
entriesarray withkey/valuepairs,testfunction name - Arrays:
elementsarray,dimensions,element_type - CLOS instances:
classname,slotsarray withname/valuepairs - Structures:
classname,slotsarray - Functions:
name,lambda_list(SBCL only)
- Lists:
meta: Containstruncatedflag, element counts, etc.
Nested objects are returned as object-ref with their own id for further inspection.
Circular references are detected and marked as circular-ref.
Example workflow:
// 1. Evaluate code that returns a complex object
{"method":"tools/call","params":{"name":"repl-eval","arguments":{"code":"(make-hash-table)"}}}
// Response includes: "result_object_id": 42
// 2. Inspect the object
{"method":"tools/call","params":{"name":"inspect-object","arguments":{"id":42}}}
// Response: {"kind":"hash-table","test":"EQL","entries":[...],"id":42}Load an ASDF system with structured output and reload support. Preferred over
(ql:quickload ...) or (asdf:load-system ...) via repl-eval for AI agents.
Input:
system(string, required): ASDF system name (e.g.,"cl-mcp","my-project/tests")force(boolean, defaulttrue): clear loaded state before loading to pick up file changesclear_fasls(boolean, defaultfalse): force full recompilation from sourcetimeout_seconds(number, default120): timeout for the load operation
Output fields:
system(string): echoed system namestatus(string):"loaded","timeout", or"error"duration_ms(integer): load time in millisecondswarnings(integer): number of compiler warnings (when loaded)warning_details(string|null): warning text (when warnings > 0)forced(boolean): whether force-reload was appliedclear_fasls(boolean): whether full recompilation was donemessage(string|null): error or timeout message
Solves three problems with using ql:quickload via repl-eval:
- Staleness:
force=true(default) clears loaded state before reloading - Output noise: suppresses verbose compilation/load output
- Timeout: dedicated timeout prevents hanging on large systems
Example JSON-RPC request:
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"load-system","arguments":{"system":"cl-mcp"}}}Read text from an allow‑listed path.
Input:
path(string, required): project‑relative or absolute inside a registered ASDF system’s source treeoffset/limit(integer, optional): substring window
Policy: reads are allowed only when the resolved path is under the project root or under asdf:system-source-directory of a registered system.
Dependency libs: reading source in Quicklisp/ASDF dependencies is permitted only via fs-read-file; do not shell out for metadata (wc, stat, etc.). File length is intentionally not returned—page through content with limit/offset when needed.
Write text to a file under the project root (directories auto-created).
Input:
path(string, required): must be relative to the project rootcontent(string, required)
Policy: writes outside the project root are rejected.
List entries in a directory (files/directories only, skips hidden and build artifacts).
Input:
path(string, required): project root or an ASDF system source dir.
Returns: entries array plus human-readable content.
Report project root and current working directory information for clients that need to normalize relative paths.
Output:
project_root(string): resolved project rootcwd(string|null): current working directoryproject_root_source(string): one ofenvorexplicitrelative_cwd(string|null): cwd relative to project root when inside it
Synchronize the server's project root and working directory with the client's location.
Input:
path(string, required): path to the project root directory (absolute preferred; relative is resolved to an absolute directory)
This tool allows AI agents to explicitly set the server's working directory, ensuring
path resolution works correctly. The server updates both *project-root* and the
current working directory (via uiop:chdir).
Output:
project_root(string): new project root pathcwd(string): new current working directoryprevious_root(string): previous project root pathstatus(string): confirmation message
Best Practice for AI Agents: Call fs-set-project-root at the beginning of your
session with your current working directory to ensure file operations work correctly.
Read a file with Lisp-aware collapsing and optional pattern-based expansion.
Inputs:
path(string, required): absolute path or project-relative.collapsed(boolean, defaulttrue): whentrueand the file is Lisp source (.lisp,.asd,.ros,.cl,.lsp), return only top-level signatures (e.g.,(defun name (args) ...)) while keepingin-packageforms fully shown.name_pattern(string, optional): CL-PPCRE regex; matching definition names are expanded even in collapsed mode.content_pattern(string, optional): CL-PPCRE regex applied to form bodies; if it matches, the full form is expanded. For non-Lisp files, this triggers a grep-like text filter with ±5 lines of context.offset/limit(integer, optional): slice window used whencollapsedisfalse; defaults tooffset=0,limit=500lines. When truncated, a[Showing lines A-B of N. Use offset=B to read more.]footer is appended.
Output fields:
content: formatted text (collapsed Lisp view, raw slice, or filtered text).path: normalized native pathname.mode: one oflisp-collapsed,raw,text-filtered,lisp-snippet,text-snippetdepending on inputs and file type.meta: includestotal_forms/expanded_formsfor collapsed Lisp, ortotal_linesplustruncatedflag for slices/filters.
Check balanced parentheses/brackets in a file slice or provided code; returns the first mismatch position.
Input:
path(string, optional): absolute path inside the project or registered ASDF system (mutually exclusive withcode)code(string, optional): raw code string (mutually exclusive withpath)offset/limit(integer, optional): window when reading frompath
Output:
ok(boolean)- when not ok:
kind(extra-close|mismatch|unclosed|too-large),expected,found, andposition(offset,line,column).
Notes:
- Uses the same read allow-list and 2 MB cap as
fs-read-file. - Ignores delimiters inside strings,
;line comments, and#| ... |#block comments.
Perform structure-aware edits to a top-level form using Eclector CST parsing while preserving surrounding formatting and comments. Supports replace, insert_before, and insert_after operations with automatic parinfer repair for missing closing parentheses.
Input:
file_path(string, required): absolute path or project-relative pathform_type(string, required): form constructor to match, e.g.,defun,defmacro,defmethodform_name(string, required): name/specializers to match; fordefmethodinclude specializers such as"print-object ((obj my-class) stream)"operation(string, required): one ofreplace,insert_before,insert_aftercontent(string, required): full form text to insert or replace withdry_run(boolean, defaultfalse): preview changes without writing to disknormalize_blank_lines(boolean, defaulttrue): normalize blank lines around edited formsreadtable(string, optional): named-readtable designator for files using custom reader macros
Operations:
- replace: Replace the entire matched form with
content - insert_before: Insert
contentas a new form before the matched form - insert_after: Insert
contentas a new form after the matched form
Output:
path,operation,form_type,form_namewould_change(boolean): whether the file was modifiedbytes: size of the updated file contentcontent: human-readable summary string of the applied change
Dry-run output (when dry_run is true):
would_change(boolean): whether the operation would modify the fileoriginal(string): the matched form text before changespreview(string): full file preview with changes appliedparinfer_warning(string, optional): auto-repair warning when closing delimiters are addedcontent: human-readable summary
Scoped text replacement within a matched top-level Lisp form. Finds old_text (exact,
whitespace-sensitive match) within the form and replaces it with new_text. Most
token-efficient way to make small changes to large forms.
Does NOT auto-repair parentheses — if the patch breaks form structure, it fails
immediately and no changes are written to disk. Use lisp-edit-form instead when
replacing or inserting entire forms.
Input:
file_path(string, required): absolute path or project-relative pathform_type(string, required): form constructor to match, e.g.,defun,defmacro,defmethodform_name(string, required): name/specializers to match; fordefmethodinclude specializers such as"print-object ((obj my-class) stream)"old_text(string, required): exact text to find within the matched form (whitespace-sensitive, must match exactly once)new_text(string, required): replacement textdry_run(boolean, defaultfalse): preview changes without writing to diskreadtable(string, optional): named-readtable designator for files using custom reader macros
Output:
path,form_type,form_namewould_change(boolean): whether the file was modifiedbytes: size of the updated file contentdelta(integer, present only whenwould_changeis true): character count difference (new_textlength minusold_textlength)content: human-readable summary string of the applied change
Dry-run output (when dry_run is true):
would_change(boolean): whether the operation would modify the fileoperation: always"patch"original(string): the matched form text before changespreview(string): modified form text after replacementcontent: human-readable summary with original and preview form text
Return definition location (path, line) for a symbol using SBCL sb-introspect.
Input:
symbol(string, required): prefer package-qualified, e.g.,"cl-mcp:version"package(string, optional): used whensymbolis unqualified; must exist
Output:
path(relative when inside project, absolute otherwise)line(integer or null if unknown)
Return symbol metadata (name, type, arglist, documentation).
Input:
symbol(string, required)package(string, optional): must exist whensymbolis unqualified
Output:
type("function" | "macro" | "variable" | "unbound")arglist(string)documentation(string|null)
Return cross-reference locations for a symbol using SBCL sb-introspect.
Input:
symbol(string, required)package(string, optional): package used whensymbolis unqualifiedproject_only(boolean, defaulttrue): limit results to files under the project root
Output:
refs(array): each element includespath,line,type(call,macro,bind,reference,set), andcontextcount(integer): number of references returnedsymbol: echoed symbol nameproject_only: whether results were filtered to the projectcontent: newline-separated human-readable summary of references
Look up a symbol or section in the Common Lisp HyperSpec (ANSI standard documentation).
Input:
query(string, required): either a symbol name (e.g.,"loop","handler-case") or a section number (e.g.,"22.3","3.1.2")include_content(boolean, defaulttrue): include extracted text content from local HyperSpec
Output:
symbolorsection: the query identifier (depends on query type)url: HyperSpec URL (file://for local,http://for remote fallback)source:"local"or"remote"content: extracted text content (wheninclude_contentis true and source is local)
The tool auto-detects whether the query is a section number (digits and dots only, starting with a digit) or a symbol name.
Example requests:
{"method":"tools/call","params":{"name":"clhs-lookup","arguments":{"query":"loop"}}}
{"method":"tools/call","params":{"name":"clhs-lookup","arguments":{"query":"22.3"}}}Notes:
- If the HyperSpec is not installed locally, the tool attempts auto-installation via
(clhs:install-clhs-use-local) - Section numbers map to filenames:
22.3→22_c.htm,22.3.1→22_ca.htm(a=1, b=2, c=3, etc.)
Run tests for a system and return structured results with pass/fail counts and failure details.
Input:
system(string, required): ASDF system name to test (e.g.,"my-project/tests")framework(string, optional): Force a specific framework ("rove","fiveam", or"auto"for auto-detect)test(string, optional): Run only a specific test by fully qualified name (e.g.,"my-package::my-test-name")
Output:
passed(integer): Number of passed testsfailed(integer): Number of failed testspending(integer): Number of pending/skipped tests (Rove only)framework(string): Framework used ("rove"or"asdf")duration_ms(integer): Execution time in millisecondsfailed_tests(array, when failures exist): Detailed failure information including:test_name: Name of the failing testdescription: Test descriptionform: The failing assertion formvalues: Evaluated valuesreason: Error message (string)source: Source location (file and line)
Example requests:
// Run all tests in a system
{"method":"tools/call","params":{"name":"run-tests","arguments":{"system":"cl-mcp/tests/clhs-test"}}}
// Run a single test
{"method":"tools/call","params":{"name":"run-tests","arguments":{"system":"cl-mcp/tests/clhs-test","test":"cl-mcp/tests/clhs-test::clhs-lookup-symbol-returns-hash-table"}}}Notes:
- Auto-detects Rove framework when loaded; falls back to ASDF
test-systemfor text capture - Single test execution requires the test package to be loaded first
- Test names must be fully qualified with package prefix (e.g.,
"package::test-name")
Return worker pool diagnostic information. No arguments required.
Output fields:
pool_running(boolean): whether the pool health monitor is activetotal_workers(integer): total live workers (bound + standby)bound_count(integer): workers assigned to sessionsstandby_count(integer): idle workers available for assignmentmax_pool_size(integer): configured maximum worker countwarmup_target(integer): target number of warm standby workersworkers(array): per-worker details includingid,state(boundorstandby),session(string or null, truncated to 8 chars),pid, andtcp_port
Example request:
{"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"pool-status","arguments":{}}}Kill the worker process bound to the current session. All Lisp state (loaded systems, REPL definitions, packages) in the worker is lost. Use this when a worker is stuck, has corrupted state, or you want a clean environment.
Input:
reset(boolean, defaultfalse): whentrue, immediately spawn a replacement worker after killing the current one; whenfalse, defer spawning until the next tool call that needs a worker
Output fields:
killed(boolean): whether a worker was actually killedreset(boolean|null): whether a replacement was spawned (only present whenkilledis true)cancelled_spawn(boolean|null): true if a pending spawn was cancelled instead of killing a live workerisError(boolean|null): true if kill succeeded but replacement spawn failed
In both modes, you must call load-system again to restore previously loaded systems.
Example requests:
// Kill worker, let next tool call spawn a fresh one
{"method":"tools/call","params":{"name":"pool-kill-worker","arguments":{}}}
// Kill and immediately spawn a replacement
{"method":"tools/call","params":{"name":"pool-kill-worker","arguments":{"reset":true}}}- Structured JSON line logs to
*error-output*by default. - Control level via
MCP_LOG_LEVELwith one of:debug,info,warn,error. - Optionally log to a file via
MCP_LOG_FILE. The value is a path template; the server inserts a timestamp and PID before the extension (e.g.,/tmp/cl-mcp.log→/tmp/cl-mcp-2026-03-01T09-15-30-12345.log). When set, logs are written to both stderr and the file.
Example:
MCP_LOG_LEVEL=debug MCP_LOG_FILE=/tmp/cl-mcp.log sbcl --eval '(asdf:load-system :cl-mcp)' ...ros install fukamachi/rove
rove cl-mcp.asdCI / sandbox note:
- Socket-restricted environments may fail
tests/tcp-test.lisp. Run core suites without TCP via:rove tests/core-test.lisp tests/protocol-test.lisp tests/tools-test.lisp tests/repl-test.lisp tests/fs-test.lisp tests/code-test.lisp tests/logging-test.lisp
- Run TCP-specific tests only where binding to localhost is permitted:
rove tests/tcp-test.lisp
What’s covered:
- Version/API surface sanity
- REPL evaluation semantics (reader eval enabled)
- Protocol handshake (
initialize,ping, tools listing/calls) - Logging of RPC dispatch/results
- TCP server accept/respond (newline‑delimited JSON)
- Stdio↔TCP bridge stays alive on idle and exits cleanly when stdin closes
- Worker pool lifecycle, crash recovery, circuit breaker, and pool-status
- Request cancellation (
notifications/cancelled) with worker termination
Note: Running tests compiles FASLs into ~/.cache/.... Ensure your environment
allows writing there or configure SBCL’s cache directory accordingly.
src/— core implementation (protocol, tools, transports)src/worker/— child worker process entry point (server, handlers, main)
tests/— Rove test suites invoked by ASDFtest-opscripts/— helper clients and a stdio↔TCP bridgeprompts/— system prompts for AI agents (repl-driven-development.md)agents/— agent persona guidelines (common-lisp-expert.md)cl-mcp.asd— main and test systems (delegatestest-opto Rove)
cl-mcp is a trusted, local-only development tool. It is designed to run on localhost, serving a single developer or AI agent that has the same level of trust as the user who started it. It is not suitable for multi-tenant, network-facing, or online service deployments.
repl-eval executes arbitrary Common Lisp code in the host image. Any user
who can reach the MCP endpoint can read/write any file, spawn processes, and
modify the running Lisp image. No amount of application-level access control
can secure this; true multi-tenant isolation would require OS-level mechanisms
(containers, separate processes, Unix users) and is out of scope.
The following mechanisms exist to prevent accidental mistakes, not to
enforce security. They are all trivially bypassable via repl-eval:
- Project root enforcement — File tools restrict reads/writes to the
project root (and ASDF system source directories for reads). This prevents
accidentally writing to
/etcor reading unrelated files, but(with-open-file ...)in the REPL has no such restriction. - Broad rootPath rejection —
initializeandfs-set-project-rootreject overly broad roots like"/". This is a safety default, not a security gate. - HTTP auth token —
start-http-serveraccepts an optional:tokenparameter (default:nil, no auth). When enabled, it gates HTTP access with a Bearer token. This can prevent accidental cross-process access on localhost but is not a substitute for network-level security. - safe_read — Disables the
#.reader macro to prevent read-time code execution. Useful when parsing untrusted Lisp syntax, butevalitself is always permitted. - Session management — HTTP sessions track connection state and manage worker process lifetimes. With the worker pool enabled, each session gets a dedicated child SBCL process, providing OS-level process isolation. This is a resource management and stability mechanism (a crash in one session's worker does not affect others), not a security boundary.
- Bridge exits after a few seconds of inactivity: ensure you’re using the
bundled
scripts/stdio_tcp_bridge.py(it disables read timeouts after connect) and that your stdin remains open. - Permission errors compiling FASLs during tests: allow writes under
~/.cacheor reconfigure SBCL’s cache path. - No output on stdio: remember the protocol is one JSON‑RPC message per line. Each request must end with a newline and the server will answer with exactly one line (or nothing for notifications).
The prompts/ directory contains recommended system prompts for AI agents working with cl-mcp.
File: prompts/repl-driven-development.md
This comprehensive guide teaches AI agents how to effectively use cl-mcp's tools for interactive Common Lisp development. It covers:
- Initial setup: Project root configuration and session initialization
- Core philosophy: The "Tiny Steps with Rich Feedback" approach (EXPLORE → DEVELOP → EDIT → VERIFY)
- Tool usage guidelines: When to use
lisp-edit-formvsfs-write-file,lisp-read-filevsfs-read-file, andrepl-evalbest practices - Common Lisp specifics: Package handling, dependency loading, pathname resolution
- Recommended workflows: Step-by-step guides for common tasks (modifying functions, debugging, running tests, adding features)
- Troubleshooting: Diagnosis and solutions for common errors
- Performance considerations: Token-efficient strategies for large codebases
Usage:
-
For Claude Code users: Reference this prompt in your
CLAUDE.mdorAGENTS.md:@/path/to/cl-mcp/prompts/repl-driven-development.md
-
For other AI agents: Include the prompt content in your agent's system instructions or configuration file.
-
For MCP client configuration: Add instructions field to your
mcp.json:{ "mcpServers": { "cl-mcp": { "instructions": "Follow the guidelines in prompts/repl-driven-development.md for Common Lisp development with this server." } } }
The prompt is designed to help AI agents make optimal use of cl-mcp's structural editing and introspection capabilities, avoiding common pitfalls like overwriting files or working in the wrong package context.
When using cl-mcp with AI agents like Claude Code, you should configure the agent to synchronize the project root at the start of each session.
Add server-specific instructions to your MCP client configuration. For Claude Code,
edit your mcp.json or configuration file:
{
"mcpServers": {
"cl-mcp": {
"command": "ros",
"args": ["run", "-l", "cl-mcp", "-e", "(cl-mcp:run)"],
"env": {
"MCP_PROJECT_ROOT": "${workspaceFolder}"
},
"instructions": "IMPORTANT: At the start of your session, call fs-set-project-root with the absolute path of your current working directory (e.g., /home/user/project) to synchronize the server's project root. This ensures all file operations work correctly."
}
}
}You can also set MCP_PROJECT_ROOT environment variable before starting the server:
export MCP_PROJECT_ROOT=/path/to/your/project
ros run -s cl-mcp -e "(cl-mcp:run)"The server will use this path during initialization, though calling fs-set-project-root
explicitly is still recommended for dynamic project switching.
MIT