mcp: accurate file_path errors + guard tool dispatch
ober
120039e31587b182de7c9ecf3f472467885efa5c
--- 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))