Emacs Integration
The mcp__emacs__emacs_eval tool evaluates Emacs Lisp in the running Emacs instance.
When to Use Emacs vs CLI Tools
Use Emacs (emacs_eval) for:
- Semantic operations (LSP-aware):
elisp1(eglot-rename "new_name") ; LSP knows scope, not just text 2(xref-find-references "func") ; Understands imports, aliases 3(flymake-diagnostics) ; Type errors, not just syntax 4(project-find-file) ; Project-aware file finding
- Elisp codebase exploration (STRONGLY PREFERRED over grep/glob):
elisp1;; Discover symbols matching a pattern 2(apropos-internal "agent-shell.*hook" 'boundp) ; Find variables 3(apropos-internal "^gptel-make" 'fboundp) ; Find functions 4 5;; Get function/variable documentation instantly 6(describe-function 'agent-shell-mcp-server-start) 7(describe-variable 'agent-shell-mode-hook) 8 9;; Check current runtime state 10(agent-shell-mcp-server-running-p) ; Live state check 11(symbol-value 'some-variable) ; Current value 12 13;; Find files 14(directory-files-recursively "~/.emacs.d" "\\.el$")
Use CLI tools (Bash/Grep) only for:
bash1grep -r "TODO" --include="*.py" . # Bulk text search in non-Elisp 2sed -i 's/old/new/g' *.py # Global text replace 3find . -name "*.test.js" ; File finding by pattern
Rule of thumb: For Emacs/Elisp codebases, ALWAYS use emacs_eval first. It's more token-efficient (returns only what you need), provides live runtime state, and understands Elisp semantics. Only fall back to grep/glob for non-Elisp code or bulk text operations.
Why Elisp is Powerful for Agents
- No access control - Everything is public, agent can modify anything
- Advice system - Wrap any function without modifying source:
(advice-add 'fn :before #'my-fn) - Instant eval - No compilation step, immediate feedback
- Self-documenting -
(describe-function 'fn)at runtime - Hooks everywhere - Event-driven observation and intervention
Documentation Discovery
Query Emacs directly for any Elisp documentation:
elisp1(describe-function 'function-name) 2(describe-variable 'variable-name) 3(apropos "search-term")
Current Context
The Claude Code session runs in an agent-shell-mode buffer. Get context:
elisp1major-mode ; => agent-shell-mode 2(buffer-name) ; => "Claude Code Agent @ project-name" 3(agent-shell--current-shell) ; Get the shell object
Self-Prompting with Timers
Set up a timer to automatically queue a prompt after idle time:
elisp1(defvar my-agent-idle-timer nil) 2 3(defun my-agent-idle-prompt () 4 "Queue a nudge to the agent after idle timeout." 5 (when-let ((shell-buf (get-buffer "Claude Code Agent @ .emacs.d"))) 6 (with-current-buffer shell-buf 7 ;; Use enqueue - agent-shell-insert errors if agent is busy 8 (agent-shell--enqueue-request 9 :prompt "Continue working on the current task.")))) 10 11;; Start: prompt after 15 minutes (900 seconds) of no activity 12(setq my-agent-idle-timer 13 (run-with-idle-timer 900 t #'my-agent-idle-prompt)) 14 15;; Stop the timer 16(when my-agent-idle-timer 17 (cancel-timer my-agent-idle-timer) 18 (setq my-agent-idle-timer nil))
Edge case: agent-shell-insert with :submit t returns "Busy" or "Not yet supported" errors while the agent is processing. Use agent-shell--enqueue-request instead - it queues the request to run after the current task completes.
Queueing Requests
Queue prompts that execute when the agent is no longer busy:
elisp1;; Queue a request (runs when current task completes) 2(agent-shell--enqueue-request :prompt "Next task to do") 3 4;; Check pending queue 5(with-current-buffer (agent-shell--current-shell) 6 (alist-get :pending-requests agent-shell--state))
Response Size Limits
Large return values may exceed the MCP response limit. Strategies:
elisp1;; Truncate long strings 2(substring (buffer-string) 0 (min 10000 (length (buffer-string)))) 3 4;; Return counts instead of full lists 5(length (buffer-list)) 6 7;; Filter before returning 8(seq-take (buffer-list) 10) 9 10;; Write to file, return path 11(with-temp-file "/tmp/output.txt" 12 (insert large-content)) 13"/tmp/output.txt" ; Return just the path
Buffer Context Gotcha
Evaluations run in the agent-shell buffer by default. To operate on other buffers:
elisp1;; WRONG: operates on agent-shell buffer 2(buffer-string) 3 4;; RIGHT: explicitly target a buffer 5(with-current-buffer "target-buffer" 6 (buffer-string)) 7 8;; Or use a temp buffer for scratch work 9(with-temp-buffer 10 (insert-file-contents "/path/to/file") 11 (buffer-string))
Periodic Background Tasks
Use run-at-time for scheduled operations:
elisp1;; Run once after 5 minutes 2(run-at-time 300 nil #'my-function) 3 4;; Run every 60 seconds 5(run-at-time t 60 #'my-repeating-function) 6 7;; Cancel a timer 8(cancel-timer timer-object)
Coordinating Multiple Agents
If multiple agent-shell sessions exist:
elisp1;; List all agent buffers 2(agent-shell-buffers) 3 4;; Get project-specific buffers 5(agent-shell-project-buffers) 6 7;; Queue request to a specific shell (safe even if busy) 8(with-current-buffer "Claude Code Agent @ other-project" 9 (agent-shell--enqueue-request :prompt "message"))
Inspecting Agent State
Access internal state for debugging or coordination:
elisp1(with-current-buffer (agent-shell--current-shell) 2 ;; Available state keys 3 (mapcar #'car agent-shell--state) 4 ;; => (:agent-config :buffer :client :request-count :pending-requests ...) 5 6 ;; Check request count 7 (alist-get :request-count agent-shell--state) 8 9 ;; Check if authenticated 10 (alist-get :authenticated agent-shell--state))
Error Handling
Errors return with isError: true. To handle gracefully in Elisp:
elisp1(condition-case err 2 (potentially-failing-operation) 3 (error (format "Failed: %s" (error-message-string err))))
Event-Driven Agent Behavior (Hooks)
Emacs's hook system enables event-driven agent behavior:
elisp1;; OBSERVE user actions 2(add-hook 'after-save-hook #'agent-on-file-saved) 3(add-hook 'compilation-finish-functions #'agent-on-compile-done) 4(add-hook 'flymake-diagnostic-functions #'agent-on-diagnostics) 5(add-hook 'buffer-list-update-hook #'agent-on-focus-change) 6 7;; INTERVENE at key moments 8(add-hook 'before-save-hook #'agent-maybe-format) 9(add-hook 'find-file-hook #'agent-setup-buffer) 10 11;; ADVICE any function non-invasively 12(advice-add 'compile :before #'agent-log-compile-command) 13(advice-add 'save-buffer :after #'agent-notify-save)
This enables proactive assistance without polling - the agent becomes an event-driven observer that reacts to user actions in real-time.
Key Insight: Dynamic Modification
Unlike static languages, Elisp lets agents modify behavior at runtime:
elisp1;; Add logging to ANY function, instantly, no rebuild 2(advice-add 'some-function :before 3 (lambda (&rest args) (message "Called with: %S" args))) 4 5;; Remove it just as easily 6(advice-remove 'some-function #'my-advice-fn)
This is the core advantage over TypeScript/VS Code extensions - no compilation, no packaging, no restart. The agent can experiment and iterate in real-time.
Repository Structure
This is a literate Emacs configuration using Org-Babel. Assumes Emacs 30+ as baseline.
Configuration Hierarchy
All modules are loaded at startup via org-babel-load-file:
init.org (2,288 lines) - MAIN ENTRY POINT
├── emacs24.helm.org - Helm completion
├── emacs24.smartparens.org - Structural editing
├── emacs24.hacky.org - Misc utilities
├── emacs24.python.org - Python dev
├── emacs24.eshell.org - Shell with Git prompt
├── emacs24.ess.org - R/statistics
├── emacs24.elisp.org - Elisp dev
├── emacs24.js2.org - JavaScript
├── emacs24.sql.org - SQL (5 formatters, sqlfluff preferred)
├── emacs24.scheme.org - Scheme
├── emacs29.llm.org - LLM/AI integration
└── private.org - API keys (external, not in repo)
Note: "emacs24" naming is historical, not version-specific.
Key Sections in init.org
| Lines | Purpose |
|---|---|
| 1-150 | Core setup: GC, keybindings, platform detection, straight.el |
| 150-450 | Org mode configuration |
| 374-450 | External module loading |
| 450-2100 | Language modes, UI, navigation |
| 2100+ | Themes, finalization, auto-tangle |
Runtime Data (do not commit)
bookmarks- Emacs bookmark locationskeyfreq- Key frequency statisticsprompts/- LLM prompt templatessnippets/- Yasnippet templateselpa/,straight/repos/- Package cacheseln-cache/- Native compilation cache
Package Management
Uses straight.el with use-package for declarative configuration. ~116 packages configured.
Known Issues (documented for cleanup)
- emacs24.eshell.org: Git status runs shell commands on every keystroke (performance)
- emacs24.hacky.org:
date2unixdefined twice; legacy keyboard macros - emacs24.scheme.org: xscheme is ancient/unmaintained
- emacs24.js2.org: js2-mode superseded by js-ts-mode