Skip to content

Commit d3a0327

Browse files
committed
Improve error handling when MCP promtps fail on server side
1 parent 1c36aa0 commit d3a0327

5 files changed

Lines changed: 73 additions & 22 deletions

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
- Consider Anthropic internal server error as a retriable error.
66
- Improve MCP error logging to show error code, message, and data instead of null.
7+
- Improve error handling when MCP promtps fail on server side.
78

89
## 0.115.1
910

src/eca/features/chat.clj

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -669,12 +669,23 @@
669669
(let [{:keys [arguments]} (first (filter #(= prompt (:name %)) (f.mcp/all-prompts @db*)))
670670
args-vals (zipmap (map :name arguments) args)
671671
{:keys [messages error-message]} (f.prompt/get-prompt! prompt args-vals @db*)]
672-
(if error-message
673-
(lifecycle/send-content! chat-ctx
674-
:system
675-
{:type :text
676-
:text error-message})
677-
(prompt-messages! messages :mcp-prompt chat-ctx))))
672+
(cond
673+
error-message
674+
(do (lifecycle/send-content! chat-ctx
675+
:system
676+
{:type :text
677+
:text error-message})
678+
(lifecycle/finish-chat-prompt! :idle chat-ctx))
679+
680+
(seq messages)
681+
(prompt-messages! messages :mcp-prompt chat-ctx)
682+
683+
:else
684+
(do (lifecycle/send-content! chat-ctx
685+
:system
686+
{:type :text
687+
:text (format "No response from prompt '%s'." prompt)})
688+
(lifecycle/finish-chat-prompt! :idle chat-ctx)))))
678689

679690
(defn ^:private handle-command! [{:keys [command args]} chat-ctx]
680691
(try

src/eca/features/tools/mcp.clj

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -653,19 +653,25 @@
653653
(:mcp-clients db)))
654654

655655
(defn get-prompt! [name arguments db]
656-
(when-let [mcp-client (->> (vals (:mcp-clients db))
657-
(keep (fn [{:keys [client prompts]}]
658-
(when (some #(= name (:name %)) prompts)
659-
client)))
660-
first)]
661-
(when-let [prompt (->> {:on-error (fn [_id jsonrpc-error]
662-
(logger/warn logger-tag "Error getting prompt:" (format-jsonrpc-error jsonrpc-error)))}
663-
(pmc/get-prompt mcp-client name arguments))]
664-
{:description (:description prompt)
665-
:messages (mapv (fn [each-message]
666-
{:role (string/lower-case (:role each-message))
667-
:content [(->content (:content each-message))]})
668-
(:messages prompt))})))
656+
(if-let [mcp-client (->> (vals (:mcp-clients db))
657+
(keep (fn [{:keys [client prompts]}]
658+
(when (some #(= name (:name %)) prompts)
659+
client)))
660+
first)]
661+
(let [error* (atom nil)
662+
prompt (->> {:on-error (fn [_id jsonrpc-error]
663+
(logger/warn logger-tag "Error getting prompt:" (format-jsonrpc-error jsonrpc-error))
664+
(reset! error* (format-jsonrpc-error jsonrpc-error)))}
665+
(pmc/get-prompt mcp-client name arguments))]
666+
(if-let [error @error*]
667+
{:error-message (str "MCP error getting prompt: " error)}
668+
(when prompt
669+
{:description (:description prompt)
670+
:messages (mapv (fn [each-message]
671+
{:role (string/lower-case (:role each-message))
672+
:content [(->content (:content each-message))]})
673+
(:messages prompt))})))
674+
{:error-message (format "Prompt '%s' not found in any connected MCP server" name)}))
669675

670676
(defn get-resource! [uri db]
671677
(when-let [mcp-client (->> (vals (:mcp-clients db))

test/eca/features/chat_test.clj

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
[clojure.string :as string]
55
[clojure.test :refer [deftest is testing]]
66
[eca.features.chat :as f.chat]
7+
[eca.features.chat.lifecycle :as lifecycle]
78
[eca.features.prompt :as f.prompt]
89
[eca.features.tools :as f.tools]
910
[eca.features.tools.mcp :as f.mcp]
@@ -588,7 +589,40 @@
588589
{"foo" 42 "bar" "yo"}))
589590
(is (match?
590591
@invoked?
591-
[[{:role :user :content "test"}] :mcp-prompt test-chat-ctx]))))))
592+
[[{:role :user :content "test"}] :mcp-prompt test-chat-ctx])))))
593+
594+
(testing "shows error message and finishes chat when get-prompt! returns error-message"
595+
(let [test-chat-ctx {:db* (atom {})}
596+
sent-content (atom nil)
597+
finished-status (atom nil)]
598+
(with-redefs [f.mcp/all-prompts (fn [_]
599+
[{:name "failing-prompt" :arguments [{:name "arg1"}]}])
600+
f.prompt/get-prompt! (fn [_ _ _]
601+
{:error-message "MCP error getting prompt: code=-32603 message=Invalid required argument: arg1"})
602+
lifecycle/send-content! (fn [_ctx _role content]
603+
(reset! sent-content content))
604+
lifecycle/finish-chat-prompt! (fn [status _ctx]
605+
(reset! finished-status status))]
606+
(#'f.chat/send-mcp-prompt! {:prompt "failing-prompt" :args ["val1"]} test-chat-ctx)
607+
(is (= :text (:type @sent-content)))
608+
(is (string/includes? (:text @sent-content) "MCP error getting prompt"))
609+
(is (= :idle @finished-status)))))
610+
611+
(testing "shows error message and finishes chat when get-prompt! returns nil"
612+
(let [test-chat-ctx {:db* (atom {})}
613+
sent-content (atom nil)
614+
finished-status (atom nil)]
615+
(with-redefs [f.mcp/all-prompts (fn [_]
616+
[{:name "nil-prompt" :arguments []}])
617+
f.prompt/get-prompt! (fn [_ _ _] nil)
618+
lifecycle/send-content! (fn [_ctx _role content]
619+
(reset! sent-content content))
620+
lifecycle/finish-chat-prompt! (fn [status _ctx]
621+
(reset! finished-status status))]
622+
(#'f.chat/send-mcp-prompt! {:prompt "nil-prompt" :args []} test-chat-ctx)
623+
(is (= :text (:type @sent-content)))
624+
(is (string/includes? (:text @sent-content) "No response from prompt"))
625+
(is (= :idle @finished-status))))))
592626

593627
(deftest message->decision-test
594628
(testing "plain prompt message"

test/eca/oauth_test.clj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
[clojure.test :refer [deftest is testing]]
55
[eca.oauth :as oauth]
66
[hato.client :as http]
7-
[matcher-combinators.test :refer [match?]]
87
[ring.util.codec :as ring.util]))
98

109
(deftest generate-pkce-test
@@ -33,7 +32,7 @@
3332
{:status 404}
3433
(make-auth-response
3534
"Bearer realm=\"test\", resource_metadata=\"https://example.com/prm\"")))
36-
http/get (fn [url opts]
35+
http/get (fn [url _opts]
3736
(swap! get-urls conj url)
3837
(cond
3938
(= url "https://example.com/prm")

0 commit comments

Comments
 (0)