Skip to content

Commit ad40785

Browse files
author
Kang Tu
committed
Remove eval_elisp mode and symbol restrictions
1 parent c660173 commit ad40785

3 files changed

Lines changed: 79 additions & 166 deletions

File tree

README.org

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ These tools add:
225225
- =get_feature_load_state=: inspect whether an Emacs feature is loaded and where it comes from
226226
- =get_recent_messages=: return the latest lines from =*Messages*=
227227
- =get_last_error_backtrace=: return the most recently recorded Emacs command error snapshot
228-
- =eval_elisp=: evaluate a single Emacs Lisp form in a chosen buffer context when explicitly enabled
228+
- =eval_elisp=: evaluate a single Emacs Lisp form in a chosen buffer context when explicitly enabled (no restrictions once enabled)
229229

230230
The old =ai-code-mcp-editor-tools.el= module has been removed. Its live-editor read tools are now built in, while =eval_elisp= remains an explicit opt-in.
231231

@@ -235,11 +235,7 @@ To register =eval_elisp=:
235235
(setq ai-code-mcp-debug-tools-enable-eval-elisp t)
236236
#+end_src
237237

238-
=eval_elisp= only allows =query= mode by default. To allow editor-local side effects too:
239-
240-
#+begin_src emacs-lisp
241-
(setq ai-code-mcp-debug-tools-allow-effect-eval t)
242-
#+end_src
238+
Once enabled, =eval_elisp= can evaluate any Emacs Lisp form without restrictions. It takes =code= (a single top-level form), optional =buffer_name= or =file_path= for evaluation context, and optional =timeout_ms=. This is useful for letting the AI apply fixes by evaluating modified function definitions directly.
243239

244240
screenshot inside Codex cli:
245241

ai-code-mcp-debug-tools.el

Lines changed: 25 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
;;; Code:
1010

11-
(require 'cl-lib)
1211
(require 'json)
1312
(require 'ai-code-mcp-common)
1413
(require 'nadvice)
@@ -31,11 +30,6 @@
3130
:type 'boolean
3231
:group 'ai-code-mcp-debug-tools)
3332

34-
(defcustom ai-code-mcp-debug-tools-allow-effect-eval nil
35-
"When non-nil, allow `eval_elisp' to run in effect mode."
36-
:type 'boolean
37-
:group 'ai-code-mcp-debug-tools)
38-
3933
(defvar ai-code-mcp--last-error-record nil
4034
"Most recent Emacs error snapshot recorded for MCP diagnostics tools.")
4135

@@ -91,10 +85,6 @@
9185
:args ((:name "code"
9286
:type string
9387
:description "Single Emacs Lisp form to evaluate.")
94-
(:name "mode"
95-
:type string
96-
:description "Evaluation mode."
97-
:optional t)
9888
(:name "buffer_name"
9989
:type string
10090
:description "Optional buffer context."
@@ -117,21 +107,6 @@
117107
:optional t)))
118108
"Optional MCP eval tool specification.")
119109

120-
(defconst ai-code-mcp-debug-tools--always-denied-symbols
121-
'(append-to-file async-shell-command call-interactively call-process
122-
command-execute compile copy-file delete-file delete-frame
123-
delete-window eval funcall kill-buffer load load-file
124-
make-directory make-network-process make-process rename-file
125-
recompile require save-buffer save-buffers-kill-emacs shell-command
126-
start-process url-retrieve write-file write-region)
127-
"Symbols that `eval_elisp' rejects in every mode.")
128-
129-
(defconst ai-code-mcp-debug-tools--query-denied-symbols
130-
'(add-hook delete-region erase-buffer indent-region insert kill-region
131-
newline put remove-hook replace-buffer-contents set setf setq
132-
setq-local switch-to-buffer yank)
133-
"Additional symbols that `eval_elisp' rejects in query mode.")
134-
135110
(defun ai-code-mcp-debug-tools-setup ()
136111
"Register optional MCP debugging tools when enabled."
137112
(when ai-code-mcp-debug-tools-enabled
@@ -224,31 +199,6 @@
224199
(line . ,(alist-get 'line position))
225200
(column . ,(alist-get 'column position)))))
226201

227-
(defun ai-code-mcp-debug-tools--symbol-denied-p (form denied-symbols)
228-
"Return the first symbol in FORM that appears in DENIED-SYMBOLS."
229-
(cond
230-
((symbolp form)
231-
(and (memq form denied-symbols) form))
232-
((consp form)
233-
(let ((head (car form)))
234-
(cond
235-
((and (symbolp head)
236-
(memq head denied-symbols))
237-
head)
238-
(t
239-
(or (ai-code-mcp-debug-tools--symbol-denied-p head denied-symbols)
240-
(cl-some
241-
(lambda (item)
242-
(ai-code-mcp-debug-tools--symbol-denied-p
243-
item denied-symbols))
244-
(cdr form)))))))
245-
((vectorp form)
246-
(cl-some
247-
(lambda (item)
248-
(ai-code-mcp-debug-tools--symbol-denied-p item denied-symbols))
249-
(append form nil)))
250-
(t nil)))
251-
252202
(defun ai-code-mcp-debug-tools--parse-single-form (code)
253203
"Parse CODE and return exactly one top-level Emacs Lisp form."
254204
(let* ((read-result (read-from-string code))
@@ -278,16 +228,15 @@
278228
(buffer-string)))
279229

280230
(defun ai-code-mcp-debug-tools--encode-eval-result
281-
(mode target-buffer before-messages capture-messages timed-out
231+
(target-buffer before-messages capture-messages timed-out
282232
value changed-buffers &optional error-object backtrace)
283-
"Return a JSON response for MODE in TARGET-BUFFER.
233+
"Return a JSON eval response for TARGET-BUFFER.
284234
BEFORE-MESSAGES and CAPTURE-MESSAGES control message collection.
285235
TIMED-OUT records timeout state, VALUE carries the result,
286236
CHANGED-BUFFERS lists modified buffers, and ERROR-OBJECT or BACKTRACE
287237
describe failures."
288238
(json-encode
289239
`((ok . ,(ai-code-mcp--json-bool (null error-object)))
290-
(mode . ,mode)
291240
(value_repr . ,(and (null error-object) (prin1-to-string value)))
292241
(value_type . ,(and (null error-object)
293242
(symbol-name (type-of value))))
@@ -302,10 +251,10 @@ describe failures."
302251
target-buffer))
303252
(timed_out . ,(ai-code-mcp--json-bool timed-out)))))
304253

305-
(defun ai-code-mcp-debug-tools--run-eval (form mode target-buffer timeout-ms
254+
(defun ai-code-mcp-debug-tools--run-eval (form target-buffer timeout-ms
306255
capture-messages
307256
include-backtrace)
308-
"Evaluate FORM in MODE within TARGET-BUFFER using TIMEOUT-MS.
257+
"Evaluate FORM within TARGET-BUFFER using TIMEOUT-MS.
309258
CAPTURE-MESSAGES controls message collection, and INCLUDE-BACKTRACE
310259
keeps the backtrace on failures."
311260
(let ((before-messages (ai-code-mcp--message-lines))
@@ -320,16 +269,9 @@ keeps the backtrace on failures."
320269
(setq timed-out t)
321270
(throw 'ai-code-mcp-debug-tools-timeout nil))
322271
(setq value
323-
(if (string= mode "query")
324-
(save-current-buffer
325-
(with-current-buffer target-buffer
326-
(save-excursion
327-
(save-match-data
328-
(save-restriction
329-
(eval form t))))))
330-
(save-current-buffer
331-
(with-current-buffer target-buffer
332-
(eval form t)))))))
272+
(save-current-buffer
273+
(with-current-buffer target-buffer
274+
(eval form t))))))
333275
(error
334276
(setq error-object
335277
(ai-code-mcp-debug-tools--error-alist
@@ -343,7 +285,6 @@ keeps the backtrace on failures."
343285
"timeout"
344286
"Evaluation exceeded the configured timeout")))
345287
(ai-code-mcp-debug-tools--encode-eval-result
346-
mode
347288
target-buffer
348289
before-messages
349290
capture-messages
@@ -606,14 +547,14 @@ existing bound variable."
606547
(limit . ,limit)
607548
(messages . ,(vconcat messages))))))
608549

609-
(defun ai-code-mcp-eval-elisp (code &optional mode buffer-name file-path
550+
(defun ai-code-mcp-eval-elisp (code &optional buffer-name file-path
610551
capture-messages include-backtrace
611552
timeout-ms)
612-
"Evaluate CODE as a single form using MODE and BUFFER-NAME.
613-
Return a JSON payload for BUFFER-NAME, FILE-PATH,
614-
CAPTURE-MESSAGES, INCLUDE-BACKTRACE, and TIMEOUT-MS."
615-
(let* ((mode (or mode "query"))
616-
(capture-messages (ai-code-mcp-debug-tools--bool-arg
553+
"Evaluate CODE as a single Emacs Lisp form.
554+
Return a JSON payload. BUFFER-NAME or FILE-PATH select the evaluation
555+
context. CAPTURE-MESSAGES, INCLUDE-BACKTRACE, and TIMEOUT-MS control
556+
diagnostics."
557+
(let* ((capture-messages (ai-code-mcp-debug-tools--bool-arg
617558
capture-messages
618559
t))
619560
(include-backtrace (ai-code-mcp-debug-tools--bool-arg
@@ -624,93 +565,32 @@ CAPTURE-MESSAGES, INCLUDE-BACKTRACE, and TIMEOUT-MS."
624565
buffer-name
625566
file-path))
626567
(parse-error nil)
627-
form
628-
always-denied
629-
query-denied)
630-
(unless (member mode '("query" "effect"))
631-
(error "Argument mode must be either query or effect"))
568+
form)
632569
(unless (and (integerp timeout-ms) (> timeout-ms 0))
633570
(error "Argument timeout_ms must be a positive integer"))
634571
(condition-case err
635572
(setq form (ai-code-mcp-debug-tools--parse-single-form code))
636573
(error
637574
(setq parse-error err)))
638-
(cond
639-
(parse-error
640-
(ai-code-mcp-debug-tools--encode-eval-result
641-
mode
642-
target-buffer
643-
(ai-code-mcp--message-lines)
644-
capture-messages
645-
nil
646-
nil
647-
'()
648-
(ai-code-mcp-debug-tools--error-alist
649-
(symbol-name (car parse-error))
650-
(error-message-string parse-error))
651-
(and include-backtrace
652-
(ai-code-mcp-debug-tools--backtrace-string))))
653-
(t
654-
(setq always-denied
655-
(ai-code-mcp-debug-tools--symbol-denied-p
656-
form
657-
ai-code-mcp-debug-tools--always-denied-symbols))
658-
(setq query-denied
659-
(and (string= mode "query")
660-
(ai-code-mcp-debug-tools--symbol-denied-p
661-
form
662-
ai-code-mcp-debug-tools--query-denied-symbols)))
663-
(cond
664-
(always-denied
665-
(ai-code-mcp-debug-tools--encode-eval-result
666-
mode
667-
target-buffer
668-
(ai-code-mcp--message-lines)
669-
capture-messages
670-
nil
671-
nil
672-
'()
673-
(ai-code-mcp-debug-tools--error-alist
674-
"symbol_denied"
675-
(format "Symbol `%s' is not allowed in eval_elisp"
676-
always-denied))
677-
nil))
678-
(query-denied
575+
(if parse-error
679576
(ai-code-mcp-debug-tools--encode-eval-result
680-
mode
681577
target-buffer
682578
(ai-code-mcp--message-lines)
683579
capture-messages
684580
nil
685581
nil
686582
'()
687583
(ai-code-mcp-debug-tools--error-alist
688-
"query_symbol_denied"
689-
(format "Symbol `%s' is not allowed in query mode"
690-
query-denied))
691-
nil))
692-
((and (string= mode "effect")
693-
(not ai-code-mcp-debug-tools-allow-effect-eval))
694-
(ai-code-mcp-debug-tools--encode-eval-result
695-
mode
696-
target-buffer
697-
(ai-code-mcp--message-lines)
698-
capture-messages
699-
nil
700-
nil
701-
'()
702-
(ai-code-mcp-debug-tools--error-alist
703-
"effect_mode_disabled"
704-
"Effect mode is disabled by configuration")
705-
nil))
706-
(t
707-
(ai-code-mcp-debug-tools--run-eval
708-
form
709-
mode
710-
target-buffer
711-
timeout-ms
712-
capture-messages
713-
include-backtrace)))))))
584+
(symbol-name (car parse-error))
585+
(error-message-string parse-error))
586+
(and include-backtrace
587+
(ai-code-mcp-debug-tools--backtrace-string)))
588+
(ai-code-mcp-debug-tools--run-eval
589+
form
590+
target-buffer
591+
timeout-ms
592+
capture-messages
593+
include-backtrace))))
714594

715595
(add-to-list 'ai-code-mcp-server-tool-setup-functions
716596
#'ai-code-mcp-debug-tools-setup)

test/test_ai-code-mcp-debug-tools.el

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,8 @@
121121
(alist-get 'tools tools-result))))
122122
(should (member "eval_elisp" tool-names)))))
123123

124-
(ert-deftest ai-code-test-mcp-eval-elisp-query-uses-target-buffer ()
125-
"Query evaluation should run against the requested buffer context."
124+
(ert-deftest ai-code-test-mcp-eval-elisp-uses-target-buffer ()
125+
"Eval should run against the requested buffer context."
126126
(let ((ai-code-mcp-server-tools nil)
127127
(ai-code-mcp-debug-tools-enabled t)
128128
(ai-code-mcp-debug-tools-enable-eval-elisp t)
@@ -178,21 +178,24 @@
178178
(when (buffer-live-p target-buffer)
179179
(kill-buffer target-buffer)))))
180180

181-
(ert-deftest ai-code-test-mcp-eval-elisp-query-rejects-denied-symbols ()
182-
"Query evaluation should reject denied symbols before running them."
181+
(ert-deftest ai-code-test-mcp-eval-elisp-allows-mutation ()
182+
"Eval should allow mutation symbols when eval is enabled."
183183
(let ((ai-code-mcp-server-tools nil)
184184
(ai-code-mcp-debug-tools-enabled t)
185-
(ai-code-mcp-debug-tools-enable-eval-elisp t))
186-
(let* ((payload
187-
(ai-code-test-mcp-debug-tools--read-json-payload
188-
(ai-code-mcp-dispatch
189-
"tools/call"
190-
'((name . "eval_elisp")
191-
(arguments . ((code . "(insert \"boom\")")))))))
192-
(error-object (alist-get 'error payload)))
193-
(should (equal :json-false (alist-get 'ok payload)))
194-
(should (equal "query_symbol_denied"
195-
(alist-get 'type error-object))))))
185+
(ai-code-mcp-debug-tools-enable-eval-elisp t)
186+
(buffer (generate-new-buffer " *ai-code-mcp-eval-insert*")))
187+
(unwind-protect
188+
(let ((payload
189+
(ai-code-test-mcp-debug-tools--read-json-payload
190+
(ai-code-mcp-dispatch
191+
"tools/call"
192+
`((name . "eval_elisp")
193+
(arguments . ((code . "(progn (insert \"boom\") (buffer-string))")
194+
(buffer_name . ,(buffer-name buffer)))))))))
195+
(should (equal t (alist-get 'ok payload)))
196+
(should (equal "\"boom\"" (alist-get 'value_repr payload))))
197+
(when (buffer-live-p buffer)
198+
(kill-buffer buffer)))))
196199

197200
(ert-deftest ai-code-test-mcp-get-variable-value-returns-bound-variable ()
198201
"Variable value tool should stringify the requested Emacs variable."
@@ -479,6 +482,40 @@
479482
(should (string-match-p "ai-code-test-mcp-frame-a"
480483
(car frames))))))
481484

485+
(ert-deftest ai-code-test-mcp-eval-elisp-allows-previously-denied-symbols ()
486+
"Eval should allow all symbols when enabled, including funcall."
487+
(let ((ai-code-mcp-server-tools nil)
488+
(ai-code-mcp-debug-tools-enabled t)
489+
(ai-code-mcp-debug-tools-enable-eval-elisp t))
490+
(let ((payload
491+
(ai-code-test-mcp-debug-tools--read-json-payload
492+
(ai-code-mcp-dispatch
493+
"tools/call"
494+
'((name . "eval_elisp")
495+
(arguments . ((code . "(funcall #'+ 1 2)"))))))))
496+
(should (equal t (alist-get 'ok payload)))
497+
(should (equal "3" (alist-get 'value_repr payload))))))
498+
499+
(ert-deftest ai-code-test-mcp-eval-elisp-defines-function ()
500+
"Eval should define a function and make it callable."
501+
(let ((ai-code-mcp-server-tools nil)
502+
(ai-code-mcp-debug-tools-enabled t)
503+
(ai-code-mcp-debug-tools-enable-eval-elisp t)
504+
(func-name "ai-code-test-mcp-eval-defined-func"))
505+
(unwind-protect
506+
(let ((payload
507+
(ai-code-test-mcp-debug-tools--read-json-payload
508+
(ai-code-mcp-dispatch
509+
"tools/call"
510+
`((name . "eval_elisp")
511+
(arguments . ((code . "(defun ai-code-test-mcp-eval-defined-func () 42)"))))))))
512+
(should (equal t (alist-get 'ok payload)))
513+
(should-not (alist-get 'mode payload))
514+
(should (fboundp (intern func-name)))
515+
(should (= 42 (funcall (intern func-name)))))
516+
(when (fboundp (intern-soft func-name))
517+
(fmakunbound (intern func-name))))))
518+
482519
(provide 'test_ai-code-mcp-debug-tools)
483520

484521
;;; test_ai-code-mcp-debug-tools.el ends here

0 commit comments

Comments
 (0)