Skip to content

Commit fcfe91d

Browse files
committed
MCP server Oauth guardails
1 parent f9286af commit fcfe91d

2 files changed

Lines changed: 23 additions & 12 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
## Unreleased
44

5+
- Fix MCP server initialization crash (`String cannot be cast to IPersistentCollection`) when OAuth metadata endpoint returns a non-JSON or error response.
6+
57
## 0.110.3
68

79
- Fix `outputTruncation.sizeKb` not being honored

src/eca/oauth.clj

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@
110110
:error-message error}))
111111
(response/content-type "text/html"))))))
112112

113+
(defn ^:private successful-json-body
114+
"Extracts :body from an HTTP response only when status is 2xx and body is a map.
115+
Hato with {:as :json :coerce :unexceptional} leaves :body as a raw String for
116+
non-2xx responses, which would break callers expecting a map."
117+
[response]
118+
(when (and response (<= 200 (:status response) 299))
119+
(let [body (:body response)]
120+
(when (map? body) body))))
121+
113122
(defn ^:private valid-auth-challenge?
114123
"A 401 is only a valid OAuth challenge if it includes a www-authenticate header.
115124
Some proxies (e.g. istio-envoy) return 401 for unsupported methods like HEAD
@@ -144,13 +153,13 @@
144153
redirect-uri (format "http://localhost:%s/auth/callback" callback-port)
145154
www-authenticate (some-> (get headers "www-authenticate") parse-www-authenticate)
146155
;; Step 1: Fetch resource metadata (PRM) or auth server metadata directly
147-
first-meta (some-> (http/get
148-
(or (:resource_metadata www-authenticate)
149-
(str base-url "/.well-known/oauth-authorization-server"))
150-
{:timeout 10000
151-
:throw-exceptions? false
152-
:as :json})
153-
:body)
156+
first-meta (successful-json-body
157+
(http/get
158+
(or (:resource_metadata www-authenticate)
159+
(str base-url "/.well-known/oauth-authorization-server"))
160+
{:timeout 10000
161+
:throw-exceptions? false
162+
:as :json}))
154163
auth-server (first (:authorization_servers first-meta))
155164
;; Step 2: If PRM returned authorization_servers but no token_endpoint,
156165
;; fetch the actual Authorization Server Metadata (RFC 8414)
@@ -159,11 +168,11 @@
159168
well-known-url (str (.getScheme uri) "://" (.getAuthority uri)
160169
"/.well-known/oauth-authorization-server"
161170
(.getPath uri))]
162-
(some-> (http/get well-known-url
163-
{:timeout 10000
164-
:throw-exceptions? false
165-
:as :json})
166-
:body)))
171+
(successful-json-body
172+
(http/get well-known-url
173+
{:timeout 10000
174+
:throw-exceptions? false
175+
:as :json}))))
167176
;; Merge: auth server metadata takes precedence, PRM as fallback
168177
meta (merge first-meta auth-server-meta)
169178
base-auth-endpoint (or (:authorization_endpoint meta)

0 commit comments

Comments
 (0)