Skip to content

Commit 57e636d

Browse files
committed
Support mcp connect/logout
1 parent b140768 commit 57e636d

6 files changed

Lines changed: 120 additions & 29 deletions

File tree

CHANGELOG.md

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

55
- Clarify Task tool prompt for task ordering, task granularity, concurrent starts, and clearing finished task lists.
6+
- Improve MCP server auth: stop auto-opening browser on OAuth, add `mcp/connectServer` request for client-driven auth and `mcp/logoutServer` notification for clearing credentials.
67
- Replace MCP Java SDK with plumcp for MCP client communication. SSE transport is no longer supported (deprecated in MCP spec 2025-03-26).
78
- Fix crash on invalid YAML frontmatter in plugin skill files.
89
- Show "Waiting subagent" instead of "Calling tool" in progress messages during subagent execution.

docs/protocol.md

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1953,7 +1953,7 @@ interface MCPServerUpdatedParams {
19531953
/**
19541954
* The status of the server.
19551955
*/
1956-
status: 'running' | 'starting' | 'stopped' | 'failed' | 'disabled';
1956+
status: 'running' | 'starting' | 'stopped' | 'failed' | 'disabled' | 'requires-auth';
19571957

19581958
/**
19591959
* The tools supported by this mcp server if not disabled.
@@ -2022,6 +2022,46 @@ interface MCPStartServerParams {
20222022
}
20232023
```
20242024

2025+
### Connect MCP server (➡️)
2026+
2027+
A client notification to initiate OAuth authorization for an MCP server that has `requires-auth` status.
2028+
The server starts a local OAuth callback server and opens the authorization URL in the browser.
2029+
On successful authorization, the server completes the OAuth flow and sends `tool/serverUpdated` with status `running`.
2030+
2031+
_Notification:_
2032+
2033+
* method: `mcp/connectServer`
2034+
* params: `MCPConnectServerParams` defined as follows:
2035+
2036+
```typescript
2037+
interface MCPConnectServerParams {
2038+
/**
2039+
* The MCP server name.
2040+
*/
2041+
name: string;
2042+
}
2043+
```
2044+
2045+
### Logout MCP server (➡️)
2046+
2047+
A client notification to logout from an MCP server, clearing its stored OAuth credentials
2048+
and restarting the server. The server will re-detect auth requirements and send
2049+
`tool/serverUpdated` with status `requires-auth`.
2050+
2051+
_Notification:_
2052+
2053+
* method: `mcp/logoutServer`
2054+
* params: `MCPLogoutServerParams` defined as follows:
2055+
2056+
```typescript
2057+
interface MCPLogoutServerParams {
2058+
/**
2059+
* The MCP server name.
2060+
*/
2061+
name: string;
2062+
}
2063+
```
2064+
20252065
### Add MCP (↩️)
20262066

20272067
Soon

src/eca/features/tools.clj

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,24 @@
289289
metrics
290290
{:on-server-updated (partial notify-server-updated metrics messenger tool-status-fn)})))
291291

292+
(defn connect-server! [name db* messenger config metrics]
293+
(let [tool-status-fn (make-tool-status-fn config nil)]
294+
(f.mcp/connect-server!
295+
name
296+
db*
297+
config
298+
metrics
299+
{:on-server-updated (partial notify-server-updated metrics messenger tool-status-fn)})))
300+
301+
(defn logout-server! [name db* messenger config metrics]
302+
(let [tool-status-fn (make-tool-status-fn config nil)]
303+
(f.mcp/logout-server!
304+
name
305+
db*
306+
config
307+
metrics
308+
{:on-server-updated (partial notify-server-updated metrics messenger tool-status-fn)})))
309+
292310
(defn tool-call-summary [all-tools full-name args config db]
293311
(when-let [summary-fn (:summary-fn (first (filter #(= full-name (:full-name %))
294312
all-tools)))]

src/eca/features/tools/mcp.clj

Lines changed: 46 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -176,28 +176,14 @@
176176
[]))
177177

178178
(defn ^:private initialize-mcp-oauth
179-
[{:keys [authorization-endpoint callback-port] :as oauth-info}
179+
[oauth-info
180180
server-name
181181
db*
182182
server-config
183-
{:keys [on-success on-error on-server-updated]}]
184-
(oauth/start-oauth-server!
185-
{:port callback-port
186-
:on-success (fn [{:keys [code]}]
187-
(let [{:keys [access-token refresh-token expires-at]} (oauth/authorize-token! oauth-info code)]
188-
(swap! db* assoc-in [:mcp-auth server-name] {:type :auth/oauth
189-
:url (:url server-config)
190-
:refresh-token refresh-token
191-
:access-token access-token
192-
:expires-at expires-at}))
193-
(oauth/stop-oauth-server! callback-port)
194-
(on-success))
195-
:on-error (fn [error]
196-
(oauth/stop-oauth-server! callback-port)
197-
(on-error error))})
198-
(logger/info logger-tag (format "Authenticating MCP server '%s' at '%s'" server-name authorization-endpoint))
199-
(swap! db* assoc-in [:mcp-clients server-name] {:status :requires-auth})
200-
(browse/browse-url authorization-endpoint)
183+
{:keys [on-server-updated]}]
184+
(logger/info logger-tag (format "MCP server '%s' requires authentication" server-name))
185+
(swap! db* assoc-in [:mcp-clients server-name] {:status :requires-auth
186+
:oauth-info oauth-info})
201187
(on-server-updated (->server server-name server-config :requires-auth @db*)))
202188

203189
(defn ^:private token-expired?
@@ -266,15 +252,7 @@
266252
name
267253
db*
268254
server-config
269-
{:on-server-updated on-server-updated
270-
:on-success (fn []
271-
;; :mcp-auth exists now
272-
(db/update-global-cache! @db* metrics)
273-
(initialize-server! name db* config metrics on-server-updated))
274-
:on-error (fn [error]
275-
(logger/error logger-tag error)
276-
(swap! db* assoc-in [:mcp-clients name :status] :failed)
277-
(on-server-updated (->server name server-config :failed db)))})
255+
{:on-server-updated on-server-updated})
278256
(let [init-timeout (:mcpTimeoutSeconds config)
279257
on-tools-change (fn [tools]
280258
(let [tools (mapv tool->internal tools)]
@@ -354,6 +332,46 @@
354332
(logger/info logger-tag (format "Starting MCP server %s from manual request despite :disabled=true" name)))
355333
(initialize-server! name db* config metrics on-server-updated)))
356334

335+
(defn connect-server!
336+
"Initiate OAuth authorization for an MCP server that requires auth.
337+
Starts the local OAuth callback server and returns the authorization URL."
338+
[name db* config metrics {:keys [on-server-updated]}]
339+
(let [server-config (get-in config [:mcpServers name])
340+
{:keys [oauth-info]} (get-in @db* [:mcp-clients name])]
341+
(when (and server-config oauth-info)
342+
(let [{:keys [authorization-endpoint callback-port]} oauth-info]
343+
(swap! db* assoc-in [:mcp-clients name :status] :starting)
344+
(on-server-updated (->server name server-config :starting @db*))
345+
(oauth/start-oauth-server!
346+
{:port callback-port
347+
:on-success (fn [{:keys [code]}]
348+
(let [{:keys [access-token refresh-token expires-at]} (oauth/authorize-token! oauth-info code)]
349+
(swap! db* assoc-in [:mcp-auth name] {:type :auth/oauth
350+
:url (:url server-config)
351+
:refresh-token refresh-token
352+
:access-token access-token
353+
:expires-at expires-at}))
354+
(oauth/stop-oauth-server! callback-port)
355+
(db/update-global-cache! @db* metrics)
356+
(initialize-server! name db* config metrics on-server-updated))
357+
:on-error (fn [error]
358+
(oauth/stop-oauth-server! callback-port)
359+
(logger/error logger-tag error)
360+
(swap! db* assoc-in [:mcp-clients name :status] :failed)
361+
(on-server-updated (->server name server-config :failed @db*)))})
362+
(browse/browse-url authorization-endpoint)))))
363+
364+
(defn logout-server!
365+
"Logout from an MCP server by clearing stored OAuth credentials and restarting it."
366+
[name db* config metrics {:keys [on-server-updated]}]
367+
(when (get-in config [:mcpServers name])
368+
(swap! db* update :mcp-auth dissoc name)
369+
(db/update-global-cache! @db* metrics)
370+
(when (get-in @db* [:mcp-clients name :client])
371+
(stop-server! name db* config {:on-server-updated on-server-updated}))
372+
(future
373+
(initialize-server! name db* config metrics on-server-updated))))
374+
357375
(defn all-tools [db]
358376
(into []
359377
(mapcat (fn [[name {:keys [tools version]}]]

src/eca/handlers.clj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,14 @@
203203
(metrics/task metrics :eca/mcp-start-server
204204
(f.tools/start-server! (:name params) db* messenger config metrics)))
205205

206+
(defn mcp-connect-server [{:keys [db* messenger metrics config]} params]
207+
(metrics/task metrics :eca/mcp-connect-server
208+
(f.tools/connect-server! (:name params) db* messenger config metrics)))
209+
210+
(defn mcp-logout-server [{:keys [db* messenger metrics config]} params]
211+
(metrics/task metrics :eca/mcp-logout-server
212+
(f.tools/logout-server! (:name params) db* messenger config metrics)))
213+
206214
(defn ^:private update-agent-model-and-variants!
207215
"Updates the selected model and variants based on agent configuration."
208216
[agent-config config messenger db*]

src/eca/server.clj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@
8383
(defmethod jsonrpc.server/receive-notification "mcp/startServer" [_ components params]
8484
(handlers/mcp-start-server (with-config components) params))
8585

86+
(defmethod jsonrpc.server/receive-notification "mcp/connectServer" [_ components params]
87+
(handlers/mcp-connect-server (with-config components) params))
88+
89+
(defmethod jsonrpc.server/receive-notification "mcp/logoutServer" [_ components params]
90+
(handlers/mcp-logout-server (with-config components) params))
91+
8692
(defmethod jsonrpc.server/receive-notification "chat/selectedAgentChanged" [_ components params]
8793
(handlers/chat-selected-agent-changed (with-config components) params))
8894

0 commit comments

Comments
 (0)