TUI: fix blank-screen hang on startup from blocking MCP query

ober

47e454dd05a12b32f5ef993d8c420fced79a7cb8

diff --git a/src/jcode/mcp/client.ss b/src/jcode/mcp/client.ss
index b810c08..1d66a9a 100644
--- a/src/jcode/mcp/client.ss
+++ b/src/jcode/mcp/client.ss
@@ -29,6 +29,10 @@
 (defstruct mcp-conn (name to-stdin from-stdout from-stderr pid next-id))
 
 (def *mcp-servers* '())
+;; Cached tool count per server name. Populated by register-mcp-tools at init.
+;; mcp-active-servers reads from here instead of doing a blocking JSON-RPC
+;; round-trip, which could hang forever if a server is slow or dead.
+(def *mcp-tool-counts* (make-hash-table))
 
 ;; --- subprocess management ---
 
@@ -57,13 +61,17 @@
 
 (def (mcp-stop-all!)
   (for-each mcp-stop! *mcp-servers*)
-  (set! *mcp-servers* '()))
+  (set! *mcp-servers* '())
+  (set! *mcp-tool-counts* (make-hash-table)))
 
 (def (mcp-active-servers)
-  "Return list of (name . tool-count) for active MCP servers."
+  "Return list of (name . tool-count) for active MCP servers.
+   Reads the cached count populated at init time — never does a
+   blocking JSON-RPC call, so it's safe to call from the TUI hot
+   path without risk of hanging on a dead server."
   (map (lambda (conn)
-         (cons (mcp-conn-name conn)
-               (length (try (mcp-list-tools conn) (catch (e) '())))))
+         (let ((name (mcp-conn-name conn)))
+           (cons name (or (hash-get *mcp-tool-counts* name) 0))))
        *mcp-servers*))
 
 ;; --- JSON-RPC 2.0 protocol ---
@@ -189,6 +197,9 @@
               (lambda (args)
                 (mcp-call-tool conn name args))))))
       tools)
+    ;; Cache the count so mcp-active-servers can return it without
+    ;; doing a blocking JSON-RPC call later.
+    (hash-put! *mcp-tool-counts* (mcp-conn-name conn) (length tools))
     (length tools)))
 
 ;; --- config and init ---
diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index c32e44d..042efb3 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -196,14 +196,20 @@
            (session (session-create "New session")))
       (tui-log "tui-main: terminal ~ax~a, session=~a" w h (session-id session))
       (app-state-session-id-set! state (session-id session))
-      ;; Populate MCP status on sidebar
+      ;; Populate MCP status on sidebar (best-effort, time-limited)
+      (tui-log "tui-main: refresh-mcp-sidebar! (begin)")
       (refresh-mcp-sidebar! state)
+      (tui-log "tui-main: refresh-mcp-sidebar! (done)")
       ;; Welcome message
       (app-state-messages-set! state
         (list (msg-block-system (format "jcode ~a — Type your message, /help for commands" *version*))))
+      (tui-log "tui-main: reflow-all! (begin)")
       (reflow-all! state)
+      (tui-log "tui-main: draw-all! (begin)")
       (draw-all! state)
+      (tui-log "tui-main: tb-present! (begin)")
       (tb-present!)
+      (tui-log "tui-main: tb-present! (done)")
       (*main-thread* (current-thread))
       (tui-log "tui-main: entering event loop, main-thread=~a" (*main-thread*))
       (event-loop state)