system-manager-wakasu
1;;; mcp-hub.el --- manager mcp server -*- lexical-binding: t; -*-
2
3;; Copyright (C) 2025 lizqwer scott
4
5;; Author: lizqwer scott <lizqwerscott@gmail.com>
6;; Keywords: ai, mcp
7
8;; This program is free software; you can redistribute it and/or modify
9;; it under the terms of the GNU General Public License as published by
10;; the Free Software Foundation, either version 3 of the License, or
11;; (at your option) any later version.
12
13;; This program is distributed in the hope that it will be useful,
14;; but WITHOUT ANY WARRANTY; without even the implied warranty of
15;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16;; GNU General Public License for more details.
17
18;; You should have received a copy of the GNU General Public License
19;; along with this program. If not, see <https://www.gnu.org/licenses/>.
20
21;;; Commentary:
22
23;;
24
25;;; Code:
26
27(require 'mcp)
28
29(defcustom mcp-hub-servers nil
30 "Configuration for MCP servers.
31Each server configuration is a list of the form
32 (NAME . (:command COMMAND :args ARGS)) or (NAME . (:url URL)), where:
33- NAME is a string identifying the server.
34- COMMAND is the command to start the server.
35- ARGS is a list of arguments passed to the command.
36- URL is a string arguments to connect sse mcp server."
37 :group 'mcp-hub
38 :type '(list (cons string (list symbol string))))
39
40(defun mcp-hub--start-server (server &optional inited-callback)
41 "Start an MCP server with the given configuration.
42SERVER should be a cons cell of the form (NAME . CONFIG) where:
43- NAME is a string identifying the server
44- CONFIG is a plist containing either:
45 - :command and :args for local servers
46 - :url for remote servers
47
48Optional argument INITED-CALLBACK is a function called when the server
49has successfully initialized and tools are available. The callback
50receives no arguments."
51 (apply #'mcp-connect-server
52 (append (list (car server))
53 (cdr server)
54 (list :initial-callback
55 #'(lambda (_)
56 (mcp-hub-update))
57 :tools-callback
58 #'(lambda (_ _)
59 (mcp-hub-update)
60 (when inited-callback
61 (funcall inited-callback)))
62 :prompts-callback
63 #'(lambda (_ _)
64 (mcp-hub-update))
65 :resources-callback
66 #'(lambda (_ _)
67 (mcp-hub-update))
68 :error-callback
69 #'(lambda (_ _)
70 (mcp-hub-update))))))
71
72;;;###autoload
73(cl-defun mcp-hub-get-all-tool (&key asyncp categoryp)
74 "Retrieve all available tools from connected MCP servers.
75This function collects all tools from currently connected MCP servers,
76filtering out any invalid entries. Each tool is created as a text tool
77that can be used for interaction.
78
79When ASYNCP is non-nil, the tools will be created asynchronously.
80
81When CATEGORYP is non-nil, the tools will be add to a category.
82
83Returns a list of text tools created from all valid tools across all
84connected servers. The list excludes any tools that couldn't be created
85due to missing or invalid names.
86
87Example:
88 (mcp-hub-get-all-tool) ; Get all tools synchronously
89 (mcp-hub-get-all-tool t) ; Get all tools asynchronously"
90 (let ((res ))
91 (maphash #'(lambda (name server)
92 (when (and server
93 (equal (mcp--status server)
94 'connected))
95 (when-let* ((tools (mcp--tools server))
96 (tool-names (mapcar #'(lambda (tool) (plist-get tool :name)) tools)))
97 (dolist (tool-name tool-names)
98 (push (let ((tool (mcp-make-text-tool name tool-name asyncp)))
99 (if categoryp
100 (plist-put
101 tool
102 :category
103 (format "mcp-%s"
104 name))
105 tool))
106 res)))))
107 mcp-server-connections)
108 (nreverse res)))
109
110;;;###autoload
111(defun mcp-hub-start-all-server (&optional callback servers)
112 "Start all configured MCP servers.
113This function will attempt to start each server listed in `mcp-hub-servers'
114if it's not already running.
115
116Optional argument CALLBACK is a function to be called when all servers have
117either started successfully or failed to start.The callback receives no
118arguments.
119
120Optional argument SERVERS is a list of server names (strings) to filter which
121servers should be started. When nil, all configured servers are considered."
122 (interactive)
123 (let* ((servers-to-start (cl-remove-if (lambda (server)
124 (or (not (cl-find (car server) servers :test #'string=))
125 (gethash (car server) mcp-server-connections)))
126 mcp-hub-servers))
127 (total (length servers-to-start))
128 (started 0))
129 (if (zerop total)
130 (progn
131 (message "All MCP servers already running")
132 (when callback (funcall callback)))
133 (message "Starting %d MCP server(s)..." total)
134 (dolist (server servers-to-start)
135 (condition-case err
136 (mcp-hub--start-server
137 server
138 (lambda ()
139 (cl-incf started)
140 (message "Started server %s (%d/%d)" (car server) started total)
141 (when (and callback (>= started total))
142 (funcall callback))))
143 (error
144 (message "Failed to start server %s: %s" (car server) err)
145 (cl-incf started)
146 (when (and callback (>= started total))
147 (funcall callback))))))))
148
149;;;###autoload
150(defun mcp-hub-close-all-server ()
151 "Stop all running MCP servers.
152This function will attempt to stop each server listed in `mcp-hub-servers'
153that is currently running."
154 (interactive)
155 (dolist (server mcp-hub-servers)
156 (when (gethash (car server)
157 mcp-server-connections)
158 (mcp-stop-server (car server))))
159 (mcp-hub-update))
160
161;;;###autoload
162(defun mcp-hub-restart-all-server ()
163 "Restart all configured MCP servers.
164This function first stops all running servers, then starts them again.
165It's useful for applying configuration changes or recovering from errors."
166 (interactive)
167 (mcp-hub-close-all-server)
168 (mcp-hub-start-all-server))
169
170(defun mcp-hub-get-servers ()
171 "Retrieve status information for all configured servers.
172Returns a list of server statuses, where each status is a plist containing:
173- :name - The server's name
174- :status - Either `connected' or `stop'
175- :tools - Available tools (if connected)
176- :resources - Available resources (if connected)
177- :prompts - Available prompts (if connected)"
178 (mapcar #'(lambda (server)
179 (let ((name (car server)))
180 (if-let* ((connection (gethash name mcp-server-connections)))
181 (list :name name
182 :type (mcp--connection-type connection)
183 :status (mcp--status connection)
184 :tools (mcp--tools connection)
185 :resources (mcp--resources connection)
186 :prompts (mcp--prompts connection))
187 (list :name name :status 'stop))))
188 mcp-hub-servers))
189
190(defun mcp-hub-update ()
191 "Update the MCP Hub display with current server status.
192If called interactively, ARG is the prefix argument.
193When SILENT is non-nil, suppress any status messages.
194This function refreshes the *Mcp-Hub* buffer with the latest server information,
195including connection status, available tools, resources, and prompts."
196 (interactive "P")
197 (when-let* ((server-list (mcp-hub-get-servers))
198 (server-show (mapcar #'(lambda (server)
199 (let* ((name (plist-get server :name))
200 (status (plist-get server :status)))
201 (append (list name
202 (symbol-name (plist-get server :type))
203 (pcase status
204 ('connected
205 (propertize (symbol-name status)
206 'face 'success))
207 ('error
208 (propertize (symbol-name status)
209 'face 'error))
210 (_
211 (symbol-name status))))
212 (if (equal status 'connected)
213 (mapcar #'(lambda (x)
214 (format "%d"
215 (length x)))
216 (list (plist-get server :tools)
217 (plist-get server :resources)
218 (plist-get server :prompts)))
219 (list "nil" "nil" "nil")))))
220 server-list)))
221 (with-current-buffer (get-buffer-create "*Mcp-Hub*")
222 (setq tabulated-list-entries
223 (cl-mapcar #'(lambda (statu index)
224 (list (format "%d" index)
225 (vconcat statu)))
226 server-show
227 (number-sequence 1 (length server-list))))
228 (tabulated-list-print t))))
229
230;;;###autoload
231(defun mcp-hub ()
232 "View mcp hub server."
233 (interactive)
234 ;; start all server
235 (when (and mcp-hub-servers
236 (= (hash-table-count mcp-server-connections)
237 0))
238 (mcp-hub-start-all-server))
239 ;; show buffer
240 (pop-to-buffer "*Mcp-Hub*" nil)
241 (mcp-hub-mode))
242
243;;;###autoload
244(defun mcp-hub-start-server ()
245 "Start the currently selected MCP server.
246This function starts the server that is currently highlighted in the *Mcp-Hub*
247buffer. It sets up callbacks for connection status, tools, prompts, and
248resources updates, and refreshes the hub view after starting the server."
249 (interactive)
250 (when-let* ((server (tabulated-list-get-entry))
251 (name (elt server 0))
252 (server-arg (cl-find name mcp-hub-servers :key #'car :test #'equal)))
253 (mcp-hub--start-server server-arg)
254 (mcp-hub-update)))
255
256;;;###autoload
257(defun mcp-hub-close-server ()
258 "Stop the currently selected MCP server.
259This function stops the server that is currently highlighted in the *Mcp-Hub*
260buffer and updates the hub view to reflect the change in status."
261 (interactive)
262 (when-let* ((server (tabulated-list-get-entry))
263 (name (elt server 0)))
264 (mcp-stop-server name)
265 (mcp-hub-update)))
266
267;;;###autoload
268(defun mcp-hub-restart-server ()
269 "Restart the currently selected MCP server.
270This function stops and then starts the server that is currently highlighted
271in the *Mcp-Hub* buffer. It's useful for applying configuration changes or
272recovering from errors."
273 (interactive)
274 (mcp-hub-close-server)
275 (mcp-hub-start-server))
276
277;;;###autoload
278(defun mcp-hub-view-log ()
279 "View the event log for the currently selected MCP server.
280This function opens a buffer showing the event log for the server that is
281currently highlighted in the *Mcp-Hub* buffer."
282 (interactive)
283 (when-let* ((server (tabulated-list-get-entry))
284 (name (elt server 0)))
285 (switch-to-buffer (format "*%s events*"
286 name))))
287
288(define-derived-mode mcp-hub-mode tabulated-list-mode "Mcp Hub"
289 "A major mode for viewing a list of mcp server."
290 (setq-local revert-buffer-function #'mcp-hub-update)
291 (setq tabulated-list-format
292 [("Name" 18 t)
293 ("Type" 10 t)
294 ("Status" 15 t)
295 ("Tools" 10 t)
296 ("Resources" 10 t)
297 ("Prompts" 10 t)])
298 (setq tabulated-list-padding 2)
299 (setq tabulated-list-sort-key '("Name" . nil))
300 (tabulated-list-init-header)
301
302 (keymap-set mcp-hub-mode-map "l" #'mcp-hub-view-log)
303 (keymap-set mcp-hub-mode-map "s" #'mcp-hub-start-server)
304 (keymap-set mcp-hub-mode-map "k" #'mcp-hub-close-server)
305 (keymap-set mcp-hub-mode-map "r" #'mcp-hub-restart-server)
306 (keymap-set mcp-hub-mode-map "S" #'mcp-hub-start-all-server)
307 (keymap-set mcp-hub-mode-map "R" #'mcp-hub-restart-all-server)
308 (keymap-set mcp-hub-mode-map "K" #'mcp-hub-close-all-server)
309
310 (mcp-hub-update))
311
312(provide 'mcp-hub)
313;;; mcp-hub.el ends here