mcp: accurate file_path errors + guard tool dispatch

ober

120039e31587b182de7c9ecf3f472467885efa5c

diff --git a/mcp/server.ss b/mcp/server.ss
index 9bb48ca..ab84291 100644
--- a/mcp/server.ss
+++ b/mcp/server.ss
@@ -1066,8 +1066,19 @@
   (def code (hash-get* args "code" #f))
   (def file (hash-get* args "file_path" #f))
   (cond
-    [code (cons "code" code)]
-    [file (guard (e [else #f]) (cons file (read-file-string file)))]
+    ;; An empty/whitespace code string is treated as absent so it doesn't
+    ;; shadow a real file_path — passing code:"" alongside file_path used to
+    ;; summarize the empty string ("Summary: code") instead of the file.
+    [(and code (> (string-length (string-trim code)) 0)) (cons "code" code)]
+    ;; Report WHY a file_path can't be read (missing/unreadable) rather than
+    ;; collapsing to #f, which made every caller emit the misleading
+    ;; "file_path or code is required." — sending models into a retry/
+    ;; escalation loop chasing a nonexistent path. The dispatcher guard turns
+    ;; this into a clean isError tool result carrying the real reason.
+    [file (guard (e [else (error 'read-tool-source
+                            (string-append "cannot read file_path " file
+                                           " (no such file or not readable)"))])
+            (cons file (read-file-string file)))]
     [else #f]))
 
 (def (definition-line defn)
@@ -4260,7 +4271,14 @@
   (cond
     [(not name) (json-rpc-error id -32602 "tools/call requires params.name")]
     [(not tool) (json-rpc-error id -32602 (string-append "Unknown tool: " name))]
-    [else (json-rpc-result id ((hash-ref tool 'handler) args))]))
+    ;; Convert any handler exception into an isError tool result (not a
+    ;; protocol-level json-rpc-error) so the caller sees the real reason and
+    ;; the JSON-RPC id stays correlated. Without this a raised tool error fell
+    ;; through to the serve-loop guard and returned id=#f.
+    [else (json-rpc-result id
+            (guard (e [else (text-result
+                              (string-append "Tool error: " (error-message e)) #t)])
+              ((hash-ref tool 'handler) args)))]))
 
 (def (handle-message msg)
   (def id (hash-get* msg "id" #f))