Fix TUI blocking, terminal reset, /mcp command, user msg display

ober

6d1e33d15e159248276e6438a2e51ac568a7991c

diff --git a/lib/jcode/ui/cli.sls b/lib/jcode/ui/cli.sls
index 93a50f8..dc6413e 100644
--- a/lib/jcode/ui/cli.sls
+++ b/lib/jcode/ui/cli.sls
@@ -18,6 +18,7 @@
    (jerboa runtime))
   (def logger (make-logger "cli"))
   (def *version* "0.1.0")
+  (def *cli-mcp-disabled* #f)
   (def (cli-main args)
        (let ([t (current-time)])
          (random-seed
@@ -174,7 +175,7 @@
          (cond
            [(equal? cmd "help")
             (display
-              "\nCommands:\n  /help              Show this help\n  /model [name]      Show or set model\n  /provider [name]   Show or set provider\n  /plan              Switch to PLAN mode (read-only)\n  /build             Switch to BUILD mode (read+write)\n  /mode              Show current mode\n  /tools             List available tools\n  /clear             Start a new session\n  /sessions          List saved sessions\n  /compact           Show message count\n  /quit              Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n")]
+              "\nCommands:\n  /help              Show this help\n  /model [name]      Show or set model\n  /provider [name]   Show or set provider\n  /plan              Switch to PLAN mode (read-only)\n  /build             Switch to BUILD mode (read+write)\n  /mode              Show current mode\n  /mcp               Toggle MCP tools on/off\n  /tools             List available tools\n  /clear             Start a new session\n  /sessions          List saved sessions\n  /compact           Show message count\n  /quit              Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n")]
            [(equal? cmd "model")
             (printf
               "Provider: ~a~n"
@@ -207,6 +208,19 @@
             (printf
               "Available tools: ~a~n"
               (string-join (list-tools) ", "))]
+           [(equal? cmd "mcp")
+            (let* ([mcp-tools (filter
+                                (lambda (n) (string-prefix? "mcp_" n))
+                                (list-tools))]
+                   [hide? (not *cli-mcp-disabled*)])
+              (set! *cli-mcp-disabled* hide?)
+              (for-each
+                (lambda (n) (set-tool-internal! n hide?))
+                mcp-tools)
+              (printf
+                "MCP tools ~a (~a tools).~n"
+                (if hide? "disabled" "enabled")
+                (length mcp-tools)))]
            [(or (equal? cmd "plan") (equal? cmd "mode plan"))
             (current-mode 'plan)
             (printf
@@ -342,50 +356,20 @@
                  (format-elapsed elapsed))))
          (flush-output-port (current-output-port))))
   (def (one-shot-mode prompt opts)
-       (let ([t0 (time-second (current-time))]
-             [term-state (save-terminal-state)])
-         (dynamic-wind
-           (lambda () (set-cbreak-mode!))
-           (lambda ()
-             (try (md-reset!)
-                  (let ([watcher (start-escape-watcher!)])
-                    (parameterize ([current-stream-cb
-                                    interruptible-stream-cb]
-                                   [current-tool-cb tool-indicator])
-                      (agent-chat prompt)))
-                  (md-flush!)
-                  (let ([elapsed (- (time-second (current-time)) t0)])
-                    (printf
-                      "~n\x1B;[2m(~a)\x1B;[0m~n"
-                      (format-elapsed elapsed)))
-                  (mcp-stop-all!) (exit 0)
-                  (catch
-                    (e)
-                    (if (and (message-condition? e)
-                             (string-contains
-                               (condition-message e)
-                               "stream-aborted"))
-                        (begin
-                          (md-flush!)
-                          (let ([elapsed (- (time-second (current-time))
-                                            t0)])
-                            (printf
-                              "~n\x1B;[2m(interrupted after ~a)\x1B;[0m~n"
-                              (format-elapsed elapsed)))
-                          (mcp-stop-all!)
-                          (exit 0))
-                        (begin
-                          (log-error
-                            logger
-                            "error"
-                            `((msg . ,(err->string e))))
-                          (printf "Error: ~a~n" (format-error e))
-                          (mcp-stop-all!)
-                          (exit 1))))))
-           (lambda ()
-             (set-car! *stream-abort* #t)
-             (restore-terminal-state! term-state)
-             (drain-stdin!)))))
+       (let ([t0 (time-second (current-time))])
+         (try (md-reset!)
+              (parameterize ([current-stream-cb md-stream-token]
+                             [current-tool-cb tool-indicator])
+                (agent-chat prompt))
+              (md-flush!)
+              (let ([elapsed (- (time-second (current-time)) t0)])
+                (printf
+                  "~n\x1B;[2m(~a)\x1B;[0m~n"
+                  (format-elapsed elapsed)))
+              (mcp-stop-all!) (exit 0)
+              (catch (e) (log-error logger "error" `((msg . ,(err->string e))))
+                (printf "Error: ~a~n" (format-error e)) (mcp-stop-all!)
+                (exit 1)))))
   (def *md-line-buf* "")
   (def *md-in-code-block* #f)
   (def *backtick* (integer->char 96))
diff --git a/lib/jcode/ui/tui-dialog.sls b/lib/jcode/ui/tui-dialog.sls
index 1aad277..daf708f 100644
--- a/lib/jcode/ui/tui-dialog.sls
+++ b/lib/jcode/ui/tui-dialog.sls
@@ -14,8 +14,8 @@
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
-    (jcode ui tui-ffi) (jcode ui tui-theme) (jerboa core)
-    (jerboa runtime))
+    (jcode ui tui-ffi) (jerboa core) (jerboa runtime)
+    (jcode ui tui-theme))
   (defstruct
     dialog
     (title body actions selected result)
diff --git a/lib/jcode/ui/tui-diff.sls b/lib/jcode/ui/tui-diff.sls
index 6b3f004..a581098 100644
--- a/lib/jcode/ui/tui-diff.sls
+++ b/lib/jcode/ui/tui-diff.sls
@@ -8,8 +8,8 @@
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
-    (std misc string) (jcode ui tui-theme) (jerboa core)
-    (jerboa runtime))
+    (std misc string) (jerboa core) (jerboa runtime)
+    (jcode ui tui-theme))
   (defstruct diff-line (type text) transparent: #t)
   (def (diff-parse-lines diff-text)
        "Parse unified diff string into list of diff-line structs."
diff --git a/lib/jcode/ui/tui-ffi.sls b/lib/jcode/ui/tui-ffi.sls
index 12b3116..d3bca48 100644
--- a/lib/jcode/ui/tui-ffi.sls
+++ b/lib/jcode/ui/tui-ffi.sls
@@ -43,11 +43,10 @@
     (jerboa core)
     (jerboa runtime))
   (def _shim-loaded
-       (or (guard (e [list #t #f])
+       (or (guard (e [#t #f])
              (load-shared-object "jcode_tui_shim.dylib"))
-           (guard (e [list #t #f])
-             (load-shared-object "jcode_tui_shim.so"))
-           (guard (e [list #t #f])
+           (guard (e [#t #f]) (load-shared-object "jcode_tui_shim.so"))
+           (guard (e [#t #f])
              (load-shared-object
                (path-join
                  (path-directory (car (command-line)))
@@ -60,6 +59,16 @@
              (foreign-procedure c-name arg-types ret-type)
              (lambda args
                (error 'tui-ffi "termbox shim not loaded" c-name)))))
+  (def c-tb-peek
+       (if (and _shim-loaded
+                (foreign-entry? "jcode_tb_peek_event"))
+           (foreign-procedure __collect_safe "jcode_tb_peek_event"
+             (int)
+             int)
+           (lambda args
+             (error 'tui-ffi
+               "termbox shim not loaded"
+               "jcode_tb_peek_event"))))
   (defstruct tui-event (type mod key ch w h x y))
   (def (read-event!)
        (make-tui-event (c-tb-ev-type) (c-tb-ev-mod) (c-tb-ev-key) (c-tb-ev-ch)
@@ -237,7 +246,6 @@
     (int)
     int)
   (define-tb c-tb-poll "jcode_tb_poll_event" () int)
-  (define-tb c-tb-peek "jcode_tb_peek_event" (int) int)
   (define-tb c-tb-ev-type "jcode_tb_event_type" () int)
   (define-tb c-tb-ev-mod "jcode_tb_event_mod" () int)
   (define-tb c-tb-ev-key "jcode_tb_event_key" () int)
diff --git a/lib/jcode/ui/tui-input.sls b/lib/jcode/ui/tui-input.sls
index d63e10d..e861033 100644
--- a/lib/jcode/ui/tui-input.sls
+++ b/lib/jcode/ui/tui-input.sls
@@ -17,8 +17,8 @@
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
-    (std misc string) (jcode ui tui-ffi) (jcode ui tui-theme)
-    (jerboa core) (jerboa runtime))
+    (std misc string) (jerboa core) (jerboa runtime)
+    (jcode ui tui-ffi) (jcode ui tui-theme))
   (defstruct
     input-state
     (text cursor-pos history history-idx completion
diff --git a/lib/jcode/ui/tui-markdown.sls b/lib/jcode/ui/tui-markdown.sls
index f74ed44..3928647 100644
--- a/lib/jcode/ui/tui-markdown.sls
+++ b/lib/jcode/ui/tui-markdown.sls
@@ -9,8 +9,8 @@
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
-    (std misc string) (jcode ui tui-ffi) (jcode ui tui-theme)
-    (jerboa core) (jerboa runtime))
+    (std misc string) (jerboa core) (jerboa runtime)
+    (jcode ui tui-ffi) (jcode ui tui-theme))
   (defstruct
     md-state
     (in-code-block? code-lang)
diff --git a/lib/jcode/ui/tui-message.sls b/lib/jcode/ui/tui-message.sls
index fd8fcbb..c0ff0fc 100644
--- a/lib/jcode/ui/tui-message.sls
+++ b/lib/jcode/ui/tui-message.sls
@@ -17,8 +17,8 @@
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
     (std misc string) (jcode ui tui-ffi) (jcode ui tui-theme)
-    (jcode ui tui-markdown) (jcode ui tui-diff) (jerboa core)
-    (jerboa runtime))
+    (jcode ui tui-markdown) (jerboa core) (jerboa runtime)
+    (jcode ui tui-diff))
   (defstruct
     msg-block
     (role content lines height tool-name tool-status collapsed?
@@ -112,14 +112,17 @@
          [(system) (render-system-content content)]
          [else (list (list (cons content 'default)))]))
   (def (render-user-content content)
-       (let ([label-line (list (cons "You:" 'user-label))])
-         (let ([text-lines (map (lambda (line)
-                                  (list
-                                    (cons
-                                      (string-append "  " line)
-                                      'user-text)))
-                                (string-split content #\newline))])
-           (cons label-line text-lines))))
+       (let ([lines (string-split content #\newline)])
+         (if (null? lines)
+             (list (list (cons "You:" 'user-label)))
+             (cons
+               (list
+                 (cons "You: " 'user-label)
+                 (cons (car lines) 'user-text))
+               (map (lambda (line)
+                      (list
+                        (cons (string-append "      " line) 'user-text)))
+                    (cdr lines))))))
   (def (render-assistant-content content)
        (if (string-empty? content)
            '()
diff --git a/lib/jcode/ui/tui-sidebar.sls b/lib/jcode/ui/tui-sidebar.sls
index 9bf3bee..211907a 100644
--- a/lib/jcode/ui/tui-sidebar.sls
+++ b/lib/jcode/ui/tui-sidebar.sls
@@ -14,8 +14,8 @@
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
-    (jcode ui tui-ffi) (jcode ui tui-theme) (jerboa core)
-    (jerboa runtime))
+    (jcode ui tui-ffi) (jerboa core) (jerboa runtime)
+    (jcode ui tui-theme))
   (defstruct
     sidebar-state
     (sessions current-session files tools mcp lsp)
diff --git a/lib/jcode/ui/tui-status.sls b/lib/jcode/ui/tui-status.sls
index 7d844d4..100db4c 100644
--- a/lib/jcode/ui/tui-status.sls
+++ b/lib/jcode/ui/tui-status.sls
@@ -8,8 +8,8 @@
     (except (chezscheme) make-hash-table hash-table? iota \x31;+ \x31;-
       getenv path-extension path-absolute? thread? make-mutex
       mutex? mutex-name)
-    (std misc string) (jcode ui tui-ffi) (jcode ui tui-theme)
-    (jerboa core) (jerboa runtime))
+    (std misc string) (jerboa core) (jerboa runtime)
+    (jcode ui tui-ffi) (jcode ui tui-theme))
   (def (render-status-bar! x y width provider model tokens-in
          tokens-out cost cwd mode)
        "Render the status bar at row y across width columns."
diff --git a/lib/jcode/ui/tui.sls b/lib/jcode/ui/tui.sls
index d315ebf..e65582a 100644
--- a/lib/jcode/ui/tui.sls
+++ b/lib/jcode/ui/tui.sls
@@ -24,6 +24,7 @@
   (def *version* "0.1.0")
   (def *tui-log-port* (make-parameter #f))
   (def *main-thread* (make-parameter #f))
+  (def *mcp-disabled* #f)
   (def (tui-log fmt . args)
        (let ([p (*tui-log-port*)])
          (when p
@@ -339,6 +340,7 @@
                      "  /clear      Start new session"
                      "  /sessions   List sessions"
                      "  /search <term>  Search session history"
+                     "  /mcp        Toggle MCP tools on/off (for local models)"
                      "  /theme      Cycle theme (Ctrl-T)"
                      "  /sidebar    Toggle sidebar (Ctrl-B)"
                      "  /quit       Exit" ""
@@ -389,6 +391,23 @@
            [(equal? cmd "theme")
             (cycle-theme!)
             (add-message! state (msg-block-system "Theme cycled."))]
+           [(equal? cmd "mcp")
+            (let* ([mcp-tools (filter
+                                (lambda (n) (string-prefix? "mcp_" n))
+                                (list-tools))]
+                   [hide? (not *mcp-disabled*)])
+              (set! *mcp-disabled* hide?)
+              (for-each
+                (lambda (n) (set-tool-internal! n hide?))
+                mcp-tools)
+              (add-message!
+                state
+                (msg-block-system
+                  (format
+                    "MCP tools ~a (~a tools). Use /mcp to toggle."
+                    (if hide? "disabled" "enabled")
+                    (length mcp-tools))))
+              (refresh-mcp-sidebar! state))]
            [(equal? cmd "sidebar")
             (app-state-sidebar-visible?-set!
               state
diff --git a/src/jcode/ui/cli.ss b/src/jcode/ui/cli.ss
index 6c5fbc5..52f2f45 100644
--- a/src/jcode/ui/cli.ss
+++ b/src/jcode/ui/cli.ss
@@ -26,6 +26,7 @@
 
 (def logger (make-logger "cli"))
 (def *version* "0.1.0")
+(def *cli-mcp-disabled* #f)
 
 (def (cli-main args)
   ;; Seed with ns+s mixed, clamped to 32 bits (random-seed max)
@@ -191,7 +192,7 @@ EXAMPLES:
   (let ((cmd (string-trim (substring input 1 (string-length input)))))
     (cond
       ((equal? cmd "help")
-       (display "\nCommands:\n  /help              Show this help\n  /model [name]      Show or set model\n  /provider [name]   Show or set provider\n  /plan              Switch to PLAN mode (read-only)\n  /build             Switch to BUILD mode (read+write)\n  /mode              Show current mode\n  /tools             List available tools\n  /clear             Start a new session\n  /sessions          List saved sessions\n  /compact           Show message count\n  /quit              Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n"))
+       (display "\nCommands:\n  /help              Show this help\n  /model [name]      Show or set model\n  /provider [name]   Show or set provider\n  /plan              Switch to PLAN mode (read-only)\n  /build             Switch to BUILD mode (read+write)\n  /mode              Show current mode\n  /mcp               Toggle MCP tools on/off\n  /tools             List available tools\n  /clear             Start a new session\n  /sessions          List saved sessions\n  /compact           Show message count\n  /quit              Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n"))
       ((equal? cmd "model")
        (printf "Provider: ~a~n" (or (current-provider-override) (config-provider)))
        (printf "Model:    ~a~n" (or (current-model-override) (config-model)))
@@ -209,6 +210,13 @@ EXAMPLES:
          (printf "Provider set to: ~a (model: ~a)~n" new-provider (current-model-override))))
       ((equal? cmd "tools")
        (printf "Available tools: ~a~n" (string-join (list-tools) ", ")))
+      ((equal? cmd "mcp")
+       (let* ((mcp-tools (filter (lambda (n) (string-prefix? "mcp_" n)) (list-tools)))
+              (hide? (not *cli-mcp-disabled*)))
+         (set! *cli-mcp-disabled* hide?)
+         (for-each (lambda (n) (set-tool-internal! n hide?)) mcp-tools)
+         (printf "MCP tools ~a (~a tools).~n"
+           (if hide? "disabled" "enabled") (length mcp-tools))))
       ((or (equal? cmd "plan") (equal? cmd "mode plan"))
        (current-mode 'plan)
        (printf "\x1b;[1;33mMode: PLAN\x1b;[0m (read-only — write tools disabled)~n"))
@@ -339,40 +347,22 @@ EXAMPLES:
     (flush-output-port (current-output-port))))
 
 (def (one-shot-mode prompt opts)
-  (let ((t0 (time-second (current-time)))
-        (term-state (save-terminal-state)))
-    (dynamic-wind
-      (lambda () (set-cbreak-mode!))
-      (lambda ()
-        (try
-          (md-reset!)
-          (let ((watcher (start-escape-watcher!)))
-            (parameterize ((current-stream-cb interruptible-stream-cb)
-                           (current-tool-cb tool-indicator))
-              (agent-chat prompt)))
-          (md-flush!)
-          (let ((elapsed (- (time-second (current-time)) t0)))
-            (printf "~n\x1b;[2m(~a)\x1b;[0m~n" (format-elapsed elapsed)))
-          (mcp-stop-all!)
-          (exit 0)
-          (catch (e)
-            (if (and (message-condition? e)
-                     (string-contains (condition-message e) "stream-aborted"))
-              (begin
-                (md-flush!)
-                (let ((elapsed (- (time-second (current-time)) t0)))
-                  (printf "~n\x1b;[2m(interrupted after ~a)\x1b;[0m~n" (format-elapsed elapsed)))
-                (mcp-stop-all!)
-                (exit 0))
-              (begin
-                (log-error logger "error" `((msg . ,(err->string e))))
-                (printf "Error: ~a~n" (format-error e))
-                (mcp-stop-all!)
-                (exit 1))))))
-      (lambda ()
-        (set-car! *stream-abort* #t)
-        (restore-terminal-state! term-state)
-        (drain-stdin!)))))
+  (let ((t0 (time-second (current-time))))
+    (try
+      (md-reset!)
+      (parameterize ((current-stream-cb md-stream-token)
+                     (current-tool-cb tool-indicator))
+        (agent-chat prompt))
+      (md-flush!)
+      (let ((elapsed (- (time-second (current-time)) t0)))
+        (printf "~n\x1b;[2m(~a)\x1b;[0m~n" (format-elapsed elapsed)))
+      (mcp-stop-all!)
+      (exit 0)
+      (catch (e)
+        (log-error logger "error" `((msg . ,(err->string e))))
+        (printf "Error: ~a~n" (format-error e))
+        (mcp-stop-all!)
+        (exit 1)))))
 
 ;; --- line-buffered markdown renderer ---
 
diff --git a/src/jcode/ui/tui-dialog.ss b/src/jcode/ui/tui-dialog.ss
index a220b8d..951bcba 100644
--- a/src/jcode/ui/tui-dialog.ss
+++ b/src/jcode/ui/tui-dialog.ss
@@ -14,6 +14,8 @@
   show-list-dialog! render-list-dialog!)
 
 (import :jcode/ui/tui-ffi
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-theme)
 
 ;; ---- Dialog struct ----
diff --git a/src/jcode/ui/tui-diff.ss b/src/jcode/ui/tui-diff.ss
index 610f3be..227e20a 100644
--- a/src/jcode/ui/tui-diff.ss
+++ b/src/jcode/ui/tui-diff.ss
@@ -6,6 +6,8 @@
   diff-parse-lines)
 
 (import :std/misc/string
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-theme)
 
 ;; ---- Parse diff text into typed lines ----
diff --git a/src/jcode/ui/tui-ffi.ss b/src/jcode/ui/tui-ffi.ss
index 26f968c..015cf68 100644
--- a/src/jcode/ui/tui-ffi.ss
+++ b/src/jcode/ui/tui-ffi.ss
@@ -54,7 +54,9 @@
   ;; Constants — cursor
   TB_HIDE_CURSOR)
 
-(import :std/os/path)
+(import :std/os/path
+        :jerboa/core
+        :jerboa/runtime)
 
 ;; ---- Load shared library ----
 
@@ -92,7 +94,12 @@
 (define-tb c-tb-set-input  "jcode_tb_set_input_mode"  (int) int)
 (define-tb c-tb-set-output "jcode_tb_set_output_mode" (int) int)
 (define-tb c-tb-poll       "jcode_tb_poll_event" ()    int)
-(define-tb c-tb-peek       "jcode_tb_peek_event" (int) int)
+;; __collect_safe: releases the GC/scheduler lock during the blocking poll,
+;; allowing agent worker threads to run while we wait for terminal input.
+(def c-tb-peek
+  (if (and _shim-loaded (foreign-entry? "jcode_tb_peek_event"))
+    (foreign-procedure __collect_safe "jcode_tb_peek_event" (int) int)
+    (lambda args (error 'tui-ffi "termbox shim not loaded" "jcode_tb_peek_event"))))
 (define-tb c-tb-ev-type    "jcode_tb_event_type" ()    int)
 (define-tb c-tb-ev-mod     "jcode_tb_event_mod"  ()    int)
 (define-tb c-tb-ev-key     "jcode_tb_event_key"  ()    int)
diff --git a/src/jcode/ui/tui-input.ss b/src/jcode/ui/tui-input.ss
index 02dc8fa..bcf4817 100644
--- a/src/jcode/ui/tui-input.ss
+++ b/src/jcode/ui/tui-input.ss
@@ -16,6 +16,8 @@
   *slash-commands*)
 
 (import :std/misc/string
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-ffi
         :jcode/ui/tui-theme)
 
diff --git a/src/jcode/ui/tui-keys.ss b/src/jcode/ui/tui-keys.ss
index 9de5d45..944106b 100644
--- a/src/jcode/ui/tui-keys.ss
+++ b/src/jcode/ui/tui-keys.ss
@@ -5,7 +5,9 @@
   event->key-name
   make-keymap)
 
-(import :jcode/ui/tui-ffi)
+(import :jcode/ui/tui-ffi
+        :jerboa/core
+        :jerboa/runtime)
 
 ;; ---- Keymap ----
 
diff --git a/src/jcode/ui/tui-markdown.ss b/src/jcode/ui/tui-markdown.ss
index 7275e4c..5dd37d0 100644
--- a/src/jcode/ui/tui-markdown.ss
+++ b/src/jcode/ui/tui-markdown.ss
@@ -6,6 +6,8 @@
   md-state-make md-state-in-code-block? md-state-in-code-block?-set!)
 
 (import :std/misc/string
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-ffi
         :jcode/ui/tui-theme)
 
diff --git a/src/jcode/ui/tui-message.ss b/src/jcode/ui/tui-message.ss
index 0ad8992..fc147de 100644
--- a/src/jcode/ui/tui-message.ss
+++ b/src/jcode/ui/tui-message.ss
@@ -16,6 +16,8 @@
         :jcode/ui/tui-ffi
         :jcode/ui/tui-theme
         :jcode/ui/tui-markdown
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-diff)
 
 ;; ---- Message block struct ----
@@ -123,11 +125,17 @@
     (else        (list (list (cons content 'default))))))
 
 (def (render-user-content content)
-  (let ((label-line (list (cons "You:" 'user-label))))
-    (let ((text-lines (map (lambda (line)
-                             (list (cons (string-append "  " line) 'user-text)))
-                           (string-split content #\newline))))
-      (cons label-line text-lines))))
+  (let ((lines (string-split content #\newline)))
+    (if (null? lines)
+      (list (list (cons "You:" 'user-label)))
+      (cons
+        ;; First line: "You: <text>" on same line
+        (list (cons "You: " 'user-label)
+              (cons (car lines) 'user-text))
+        ;; Remaining lines indented
+        (map (lambda (line)
+               (list (cons (string-append "      " line) 'user-text)))
+             (cdr lines))))))
 
 (def (render-assistant-content content)
   (if (string-empty? content)
diff --git a/src/jcode/ui/tui-sidebar.ss b/src/jcode/ui/tui-sidebar.ss
index b703139..628eaef 100644
--- a/src/jcode/ui/tui-sidebar.ss
+++ b/src/jcode/ui/tui-sidebar.ss
@@ -11,6 +11,8 @@
   render-sidebar!)
 
 (import :jcode/ui/tui-ffi
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-theme)
 
 ;; ---- Sidebar state ----
diff --git a/src/jcode/ui/tui-status.ss b/src/jcode/ui/tui-status.ss
index 445c782..2208051 100644
--- a/src/jcode/ui/tui-status.ss
+++ b/src/jcode/ui/tui-status.ss
@@ -4,6 +4,8 @@
 (export render-status-bar!)
 
 (import :std/misc/string
+        :jerboa/core
+        :jerboa/runtime
         :jcode/ui/tui-ffi
         :jcode/ui/tui-theme)
 
diff --git a/src/jcode/ui/tui-theme.ss b/src/jcode/ui/tui-theme.ss
index d0bec38..1c29809 100644
--- a/src/jcode/ui/tui-theme.ss
+++ b/src/jcode/ui/tui-theme.ss
@@ -9,7 +9,9 @@
   ;; Helpers
   rgb truecolor)
 
-(import :jcode/ui/tui-ffi)
+(import :jcode/ui/tui-ffi
+        :jerboa/core
+        :jerboa/runtime)
 
 ;; ---- Face struct ----
 
diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index f7934a3..fa94fff 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -30,7 +30,9 @@
         :jcode/tool/git
         :jcode/mcp/client
         :jcode/tool/lsp
-        :jcode/core/plugin)
+        :jcode/core/plugin
+        :jerboa/core
+        :jerboa/runtime)
 
 (def logger (make-logger "tui"))
 (def *version* "0.1.0")
@@ -46,6 +48,7 @@
 ;; app-state directly — Chez hash tables and lists are not thread-safe under
 ;; preemptive pthreads, so all state mutation must happen on the main thread.
 (def *main-thread* (make-parameter #f))
+(def *mcp-disabled* #f)
 
 (def (tui-log fmt . args)
   (let ((p (*tui-log-port*)))
@@ -438,6 +441,7 @@
                "  /clear      Start new session"
                "  /sessions   List sessions"
                "  /search <term>  Search session history"
+               "  /mcp        Toggle MCP tools on/off (for local models)"
                "  /theme      Cycle theme (Ctrl-T)"
                "  /sidebar    Toggle sidebar (Ctrl-B)"
                "  /quit       Exit"
@@ -485,6 +489,16 @@
       ((equal? cmd "theme")
        (cycle-theme!)
        (add-message! state (msg-block-system "Theme cycled.")))
+      ((equal? cmd "mcp")
+       (let* ((mcp-tools (filter (lambda (n) (string-prefix? "mcp_" n)) (list-tools)))
+              (hide? (not *mcp-disabled*)))
+         (set! *mcp-disabled* hide?)
+         (for-each (lambda (n) (set-tool-internal! n hide?)) mcp-tools)
+         (add-message! state
+           (msg-block-system
+             (format "MCP tools ~a (~a tools). Use /mcp to toggle."
+               (if hide? "disabled" "enabled") (length mcp-tools))))
+         (refresh-mcp-sidebar! state)))
       ((equal? cmd "sidebar")
        (app-state-sidebar-visible?-set! state (not (app-state-sidebar-visible? state)))
        (reflow-all! state))