|
82 | 82 | (when (string/includes? url "/sse") |
83 | 83 | (logger/warn logger-tag (format "SSE transport is no longer supported for server '%s'. Using Streamable HTTP instead. Consider updating the URL." server-name))) |
84 | 84 | (logger/info logger-tag (format "Creating HTTP transport for server '%s' at %s" server-name url)) |
85 | | - {:transport (phct/make-streamable-http-transport |
86 | | - hc |
87 | | - :on-other-response (fn [response] |
88 | | - (let [status (long (:status response)) |
89 | | - read-body (fn [] |
90 | | - (try |
91 | | - (let [body* (atom nil)] |
92 | | - (when-let [on-msg (:on-msg response)] |
93 | | - (on-msg (fn [text] (reset! body* text)))) |
94 | | - (when-let [body @body*] |
95 | | - (subs body 0 (min (count body) 1000)))) |
96 | | - (catch Exception _ nil)))] |
97 | | - (cond |
98 | | - (= 202 status) nil |
99 | | - |
100 | | - (or (<= 400 status 499) |
101 | | - (<= 500 status)) |
102 | | - (let [body (read-body) |
103 | | - ;; 405 with body mentioning GET means server doesn't support |
104 | | - ;; SSE streaming, which is fine — it still works via POST. |
105 | | - sse-unsupported? (and (= 405 status) |
106 | | - body |
107 | | - (string/includes? body "GET"))] |
108 | | - (if sse-unsupported? |
109 | | - (logger/info logger-tag |
110 | | - (format "MCP server '%s' does not support SSE streaming, continuing with POST only" |
111 | | - server-name)) |
112 | | - (do (logger/warn logger-tag |
113 | | - (format "MCP server '%s' returned %d, re-initializing.%s" |
114 | | - server-name status |
115 | | - (if body (str " Response: " body) ""))) |
116 | | - (reset! needs-reinit?* true)))) |
117 | | - |
118 | | - :else |
119 | | - (logger/warn logger-tag (format "Unexpected HTTP response from MCP server '%s': %d" |
120 | | - server-name status)))))) |
| 85 | + {:transport (phct/make-streamable-http-transport hc) |
121 | 86 | :needs-reinit?* needs-reinit?*}) |
122 | 87 |
|
123 | 88 | ;; STDIO transport |
|
240 | 205 | [jsonrpc-error] |
241 | 206 | (let [code (:code jsonrpc-error) |
242 | 207 | message (:message jsonrpc-error) |
243 | | - data (:data jsonrpc-error)] |
| 208 | + data (:data jsonrpc-error) |
| 209 | + http-status (:plumcp.core/http-status jsonrpc-error)] |
244 | 210 | (cond-> "" |
| 211 | + http-status (str "http=" http-status " ") |
245 | 212 | code (str "code=" code) |
246 | 213 | message (str (when code " ") "message=" message) |
247 | 214 | data (str " data=" (pr-str data)) |
|
590 | 557 | (:mcp-clients db))) |
591 | 558 |
|
592 | 559 | (defn ^:private reinitialize-server! |
593 | | - "Re-initialize an MCP server after a transport error (HTTP 404/5xx). |
| 560 | + "Re-initialize an MCP server after a transport error (HTTP 401/403/5xx). |
594 | 561 | Stops the old transport without attempting disconnect (the session is already |
595 | 562 | gone server-side), then runs a fresh initialize-server! cycle." |
596 | 563 | [server-name old-client db* config metrics] |
|
605 | 572 | {:error true |
606 | 573 | :contents [{:type :text :text msg}]}) |
607 | 574 |
|
| 575 | +(defn ^:private reinit-worthy-http-status? |
| 576 | + "HTTP status codes that indicate the session/auth is broken and the server |
| 577 | + connection should be re-initialized (e.g. expired token, server error)." |
| 578 | + [status] |
| 579 | + (or (= 401 (long status)) |
| 580 | + (= 403 (long status)) |
| 581 | + (<= 500 (long status)))) |
| 582 | + |
608 | 583 | (defn ^:private do-call-tool |
609 | 584 | "Execute a tool call. Delegates timeout handling to plumcp via :timeout-millis. |
610 | | - HTTP 400/404/500 errors are returned as JSON-RPC error responses by plumcp, |
611 | | - so no polling loop is needed." |
612 | | - [mcp-client name arguments] |
| 585 | + All non-200 HTTP errors are returned as JSON-RPC errors by plumcp with |
| 586 | + :plumcp.core/http-status on the error map." |
| 587 | + [mcp-client name arguments needs-reinit?*] |
613 | 588 | (locking mcp-client |
614 | 589 | (let [error-msg* (atom nil) |
615 | 590 | call-opts {:timeout-millis tool-call-timeout-ms |
616 | 591 | :on-error (fn [_id jsonrpc-error] |
617 | 592 | (let [msg (or (:message jsonrpc-error) "Unknown JSON-RPC error")] |
618 | 593 | (logger/warn logger-tag "Error calling tool:" (format-jsonrpc-error jsonrpc-error)) |
619 | | - (reset! error-msg* msg)) |
| 594 | + (reset! error-msg* msg) |
| 595 | + (when-let [http-status (:plumcp.core/http-status jsonrpc-error)] |
| 596 | + (when (and needs-reinit?* (reinit-worthy-http-status? http-status)) |
| 597 | + (logger/warn logger-tag (format "HTTP %d error, flagging for re-initialization" http-status)) |
| 598 | + (reset! needs-reinit?* true)))) |
620 | 599 | nil)} |
621 | 600 | result (try |
622 | 601 | (pmc/call-tool mcp-client name arguments call-opts) |
|
643 | 622 | (defn ^:private reinit-and-call-tool! [server-name mcp-client db* config metrics name arguments] |
644 | 623 | (reinitialize-server! server-name mcp-client db* config metrics) |
645 | 624 | (if-let [new-client (get-in @db* [:mcp-clients server-name :client])] |
646 | | - (do-call-tool new-client name arguments) |
| 625 | + (do-call-tool new-client name arguments nil) |
647 | 626 | (tool-call-error (format "Failed to re-initialize MCP server '%s'" server-name)))) |
648 | 627 |
|
649 | 628 | (defn call-tool! [name arguments {:keys [db db* config metrics]}] |
|
654 | 633 | [sn client needs-reinit?*]))) |
655 | 634 | first)] |
656 | 635 | (if (and needs-reinit?* @needs-reinit?* db* config metrics) |
657 | | - ;; Already flagged (e.g. GET stream 5xx) — reinit before attempting the call |
| 636 | + ;; Already flagged — reinit before attempting the call |
658 | 637 | (reinit-and-call-tool! server-name mcp-client db* config metrics name arguments) |
659 | | - (let [result (do-call-tool mcp-client name arguments)] |
| 638 | + (let [result (do-call-tool mcp-client name arguments needs-reinit?*)] |
660 | 639 | (cond |
661 | 640 | ;; nil = transient transport error, retry once |
662 | 641 | (nil? result) |
663 | | - (do-call-tool mcp-client name arguments) |
| 642 | + (do-call-tool mcp-client name arguments needs-reinit?*) |
664 | 643 |
|
665 | | - ;; Flagged during the call (e.g. GET stream 404/5xx) — reinit and retry |
| 644 | + ;; Flagged during the call (e.g. HTTP 401/403/5xx) — reinit and retry |
666 | 645 | (and (:error result) needs-reinit?* @needs-reinit?* db* config metrics) |
667 | 646 | (reinit-and-call-tool! server-name mcp-client db* config metrics name arguments) |
668 | 647 |
|
|
0 commit comments