Anyone knows a good elisp mcp server/skill/etc ? I want to let LLM change my running emacs instance and eval elisp there
No, no, I didn't mean to sound like: "you gave me the wrong thing", sorry, mate. Your tool does work, it just forces the model to fail first and then re-adjust immediately, but it succeeds eventually.
Without your shared idea I wouldn't even know what I was missing, so you have my gratitude, thank you! This mcp also does another thing differently - instead of just trying to read the result of the execution from stderr, it gets *Messages* buffer output and checks other special buffers.
Your thinking also made me want to dig for more, and since I'm on a Mac most of the time I baked jxa-browser-server that uses JXA/Applescript that does stuff like listing browser tabs, finding stuff in the opened tabs, getting currently selected text, getting the content of the active tab, etc. very cool. I'll keep playing with this, maybe will share once I test it out
Heh that's interesting! The mcp you shared is very cool and I might take it up. It also shows me how to create simple mpcs with babashka which is very valuable
Not really, I suck at elisp tests, never managed to have a project with good tests like for clojure... But I know some elisp, so when I can't make it work LLM usually finds the reason behind it
Most of my Elisp projects have a lot of tests - buttercup is a pretty library for doing specs, and LLMs handle it pretty well. They are also great to backfill libraries without tests to begin with, although for this they definitely need a bit of guidances to do something meaningful.
Given that neither Clojure not Elisp are tier 1 languages in any LLM, the results are quite good and I guess they'll only get better.
Yeah that sounds like a great idea, I will try to backfill tests
@ovidiu.stoica1094 this thing seems to be struggling a lot, trying to collapse some complex elisp into a string, properly parsing quotes, etc. I tortured it and made it spit out a nice little, functional MCP - bb script.
| Test | Result |
|----------------------------------------------|------------------------------------|
| Basic arithmetic | ✓ |
| Emacs version query | ✓ Emacs 30.2, aarch64 macOS |
| Recursive cl-labels (factorial) | ✓ |
| Higher-order functions (compose) | ✓ returned 42 |
| Hash tables | ✓ |
| cl-loop FizzBuzz | ✓ |
| State persistence across calls | ✓ variable survived between evals |
| Regex parsing | ✓ extracted date, user, env |
| Buffer creation/manipulation | ✓ |
| Emacs introspection (pid, buffers, features) | ✓ 59 buffers, 1286 features |
| defmacro + timing | ✓ |
| cl-defstruct | ✓ |
| pcase pattern matching | ✓ all types classified correctly |
| Real buffer mode census | ✓ top mode: fundamental-mode (25) |
| Shell command from Emacs | ✓ round-tripped through shell |
| cl-loop destructuring + multi-accumulators | ✓ |
| Cleanup ( makunbound / fmakunbound ) | ✓ |
17/17 passed. The MCP server is rock solid - handles lambdas, macros, structs, regex, buffer ops, shell calls, state persistence, introspection, and cleanup without a hitch.That's very nice! Maybe I didn't stress test it enough but it worked quite well for my needs. What model did you use? I use it with sonnet-4.5 & opus-4.6
Found https://github.com/rhblind/emacs-mcp-server but seems complex to setup
https://github.com/steveyegge/efrit ? haven't tried
why does one need an mcp for this sort of thing? Doesn't the LLM already know how to evaluate in a running emacs instance?
I'm trying to fix a nasty eca-emacs bug and give LLM a way to eval to my current eca session would be great, right now it knows only to launch a new emacs and run elisp from there but it's not as powerful
one should be able to evaluate in the running session with emacsclient, provided one's running emacs in server mode, which most frameworks nowadays do
yeah that would work, I don't use emacsClient tho
you don't have to, your LLM would 🙂
it only caused problems in my NixOS + doom so I just gave it a way
probably a skill or something to teach LLM how to do that would be nice
I don't get it, surely the nix package comes with the emacsclient binary?
ah yeah just confirmed I have it and works hehe
will give it a try, let's see
I've been thinking the other day if Karthik's introspector agent can be used with eca https://github.com/karthink/gptel-agent/blob/master/agents/introspector.md
I'm shipping subagents for ECA so that will be better :)
certainly a skill is deserved for that to teach LLM that emacsclient is avaiable and good to test on the running emacs instance
did you find your bug?
yep, that was the only way LLM managed to fix it!
it was awesome to see things poping up in my running emacs, because eca was running emacsclient -e to eval things and test things :)
I confirm I'm using emacsclient eval with eca and it works quite well. I'm very happy. Here's my setup:
config.json
"customTools": {
"elisp-eval": {
"description": "${file:tools/elisp-eval.md}",
"command": "emacsclient --eval '{{code}}'",
"schema": {
"properties": {
"code": {
"type": "string",
"description": "A string of Emacs Lisp code to evaluate in the running Emacs server. Single quotes in strings must be escaped as '\\'' for shell. Example: (message \"Hello\") or (progn (setq x 10) (+ x 20))"
}
},
"required": ["code"]
}
}
},
eca/tools/elisp-eval.md
Evaluate Emacs Lisp code in the running Emacs server. Returns the result of evaluation.
Usage:
- `code` parameter accepts Emacs Lisp expressions as a string
- State persists between calls (defined functions/variables remain available)
- Requires a running Emacs server (start with `emacs --daemon` or `M-x server-start`)
Important Notes:
- Only the return value of the last expression is captured
- Output from `message`, `print`, or `princ` goes to the Emacs *Messages* buffer, not stdout
- Errors are reported via stderr with exit code 1
Escaping Rules:
- The code is passed to emacsclient via shell, so escaping follows shell rules
- Single quotes in elisp strings: Escape as '\''
Example: (message "It'\''s working")
- Double quotes: Use normally within the elisp code
Example: (message "Hello World")
- Backslashes: Use \\\\ for literal backslash
Example: (insert "path\\\\to\\\\file")
- Multi-line code: Use literal newlines or combine with progn
Example: (progn (setq x 10) (+ x 20))
Examples:
- Simple: (+ 1 2 3)
Returns: 6
- Define function: (defun greet (name) (format "Hello, %s!" name))
Returns: greet
- Call function: (greet "World")
Returns: "Hello, World!"
- Require package: (progn (require 'dash) (dash-version))
Returns: "2.19.1" (or current version)
- Multi-step: (progn (setq my-var 42) (message "Set to %d" my-var) (* my-var 2))
Returns: 84
- Get buffer info: (buffer-name)
Returns: "*scratch*" (or current buffer name)
- List buffers: (mapcar #'buffer-name (buffer-list))
Returns: ("*scratch*" "*Messages*" ...)
Common Use Cases:
- Test Emacs configuration changes interactively
- Develop and debug elisp functions
- Query Emacs state (buffers, variables, modes)
- Prototype package behavior before adding to init.el
- Inspect and modify Emacs internals
Limitations:
- Requires Emacs server to be running
- Cannot capture intermediate output (only return values)
- No interactive input (cannot use `read-string` etc.)
- Evaluation happens in the server context, not in a specific bufferFor emacs server I just have:
;; Start Emacs server for emacsclient
(use-package server
:commands server-running-p
:defer 2
:config (unless (server-running-p)
(message "Starting emacs server...")
(server-start)))oh that custom tool is awesome, I may add to https://eca.dev/examples, a skill would be nice putting it together as well, thanks @ovidiu.stoica1094!
Sure! Please go ahead!
@ericdallo Out of curiosity - wouldn’t you do this by having the AI agent look for the solution against some test showcasing it? That’s what I usually do with Claude when using for it Elisp and it works pretty well, even for some tricky issues (but admittedly it takes a bit of time until it sorts out everything involved).
In my experience MCPs don’t add that much value, if they have some alternative (e.g. directly running some tool) and some of them are brutal on the token usage.
I thought this was semi relevant to the convo, given it is about eca & elisp_eval :
Sometimes eca doesnt' believe the LSP diagnostics changed so it would use elisp_eval to revert buffer
The LSP cache is stale. Let me trigger a re-index by touching the db file:
Called tool: eca__elisp-eval ✅ 0.04 s
Tool: elisp-eval
Server: eca
Arguments:
code: "(progn
(let ((buf (find-buffer-visiting \"/path/to/db.cljs\")))
(when buf
(with-current-buffer buf
(revert-buffer t t))))
\"db.cljs reverted\")"
Output: Stdout:
"db.cljs reverted"
Actually this might be a emacs/flycheck/flymake issue, since some times emacs updates the diagnostics if you visit the buffer or something, maybe we can improve eca-emacs to force that update
Yes. I'm not sure diagnostics change if eca edits a file and I don't directly visit that file in emacs. Could be good. Happy to move this in a #eca thread
yeah let's do it