@@ -10,6 +10,7 @@ import (
10
10
11
11
"github.com/stacklok/toolhive/pkg/client"
12
12
"github.com/stacklok/toolhive/pkg/config"
13
+ "github.com/stacklok/toolhive/pkg/core"
13
14
"github.com/stacklok/toolhive/pkg/groups"
14
15
"github.com/stacklok/toolhive/pkg/logger"
15
16
"github.com/stacklok/toolhive/pkg/workloads"
@@ -38,6 +39,7 @@ func ClientRouter(
38
39
r .Get ("/" , routes .listClients )
39
40
r .Post ("/" , routes .registerClient )
40
41
r .Delete ("/{name}" , routes .unregisterClient )
42
+ r .Delete ("/{name}/groups/{group}" , routes .unregisterClientFromGroup )
41
43
r .Post ("/register" , routes .registerClientsBulk )
42
44
r .Post ("/unregister" , routes .unregisterClientsBulk )
43
45
return r
@@ -129,9 +131,7 @@ func (c *ClientRoutes) unregisterClient(w http.ResponseWriter, r *http.Request)
129
131
return
130
132
}
131
133
132
- err := c .clientManager .UnregisterClients (r .Context (), []client.Client {
133
- {Name : client .MCPClient (clientName )},
134
- })
134
+ err := c .removeClient (r .Context (), []client.Client {{Name : client .MCPClient (clientName )}}, nil )
135
135
if err != nil {
136
136
logger .Errorf ("Failed to unregister client: %v" , err )
137
137
http .Error (w , "Failed to unregister client" , http .StatusInternalServerError )
@@ -141,6 +141,41 @@ func (c *ClientRoutes) unregisterClient(w http.ResponseWriter, r *http.Request)
141
141
w .WriteHeader (http .StatusNoContent )
142
142
}
143
143
144
+ // unregisterClientFromGroup
145
+ //
146
+ // @Summary Unregister a client from a specific group
147
+ // @Description Unregister a client from a specific group in ToolHive
148
+ // @Tags clients
149
+ // @Param name path string true "Client name to unregister"
150
+ // @Param group path string true "Group name to remove client from"
151
+ // @Success 204
152
+ // @Failure 400 {string} string "Invalid request"
153
+ // @Failure 404 {string} string "Client or group not found"
154
+ // @Router /api/v1beta/clients/{name}/groups/{group} [delete]
155
+ func (c * ClientRoutes ) unregisterClientFromGroup (w http.ResponseWriter , r * http.Request ) {
156
+ clientName := chi .URLParam (r , "name" )
157
+ if clientName == "" {
158
+ http .Error (w , "Client name is required" , http .StatusBadRequest )
159
+ return
160
+ }
161
+
162
+ groupName := chi .URLParam (r , "group" )
163
+ if groupName == "" {
164
+ http .Error (w , "Group name is required" , http .StatusBadRequest )
165
+ return
166
+ }
167
+
168
+ // Remove client from the specific group
169
+ err := c .removeClient (r .Context (), []client.Client {{Name : client .MCPClient (clientName )}}, []string {groupName })
170
+ if err != nil {
171
+ logger .Errorf ("Failed to unregister client from group: %v" , err )
172
+ http .Error (w , "Failed to unregister client from group" , http .StatusInternalServerError )
173
+ return
174
+ }
175
+
176
+ w .WriteHeader (http .StatusNoContent )
177
+ }
178
+
144
179
// registerClientsBulk
145
180
//
146
181
// @Summary Register multiple clients
@@ -220,7 +255,7 @@ func (c *ClientRoutes) unregisterClientsBulk(w http.ResponseWriter, r *http.Requ
220
255
clients [i ] = client.Client {Name : name }
221
256
}
222
257
223
- err = c .clientManager . UnregisterClients (r .Context (), clients )
258
+ err = c .removeClient (r .Context (), clients , req . Groups )
224
259
if err != nil {
225
260
logger .Errorf ("Failed to unregister clients: %v" , err )
226
261
http .Error (w , "Failed to unregister clients" , http .StatusInternalServerError )
@@ -310,3 +345,104 @@ func (c *ClientRoutes) performClientRegistration(ctx context.Context, clients []
310
345
311
346
return nil
312
347
}
348
+
349
+ func (c * ClientRoutes ) removeClient (ctx context.Context , clients []client.Client , groupNames []string ) error {
350
+ runningWorkloads , err := c .workloadManager .ListWorkloads (ctx , false )
351
+ if err != nil {
352
+ return fmt .Errorf ("failed to list running workloads: %w" , err )
353
+ }
354
+
355
+ if len (groupNames ) > 0 {
356
+ return c .removeClientFromGroup (ctx , clients , groupNames , runningWorkloads )
357
+ }
358
+
359
+ return c .removeClientGlobally (ctx , clients , runningWorkloads )
360
+ }
361
+
362
+ func (c * ClientRoutes ) removeClientFromGroup (
363
+ ctx context.Context ,
364
+ clients []client.Client ,
365
+ groupNames []string ,
366
+ runningWorkloads []core.Workload ,
367
+ ) error {
368
+ // Remove clients from specific groups only
369
+ filteredWorkloads , err := workloads .FilterByGroups (runningWorkloads , groupNames )
370
+ if err != nil {
371
+ return fmt .Errorf ("failed to filter workloads by groups: %w" , err )
372
+ }
373
+
374
+ // Remove the workloads from the client's configuration file
375
+ err = c .clientManager .UnregisterClients (ctx , clients , filteredWorkloads )
376
+ if err != nil {
377
+ return fmt .Errorf ("failed to unregister clients: %w" , err )
378
+ }
379
+
380
+ // Extract client names for group management
381
+ clientNames := make ([]string , len (clients ))
382
+ for i , clientToRemove := range clients {
383
+ clientNames [i ] = string (clientToRemove .Name )
384
+ }
385
+
386
+ // Remove the clients from the groups
387
+ err = c .groupManager .UnregisterClients (ctx , groupNames , clientNames )
388
+ if err != nil {
389
+ return fmt .Errorf ("failed to unregister clients from groups: %w" , err )
390
+ }
391
+
392
+ return nil
393
+ }
394
+
395
+ func (c * ClientRoutes ) removeClientGlobally (
396
+ ctx context.Context ,
397
+ clients []client.Client ,
398
+ runningWorkloads []core.Workload ,
399
+ ) error {
400
+ // Remove the workloads from the client's configuration file
401
+ err := c .clientManager .UnregisterClients (ctx , clients , runningWorkloads )
402
+ if err != nil {
403
+ return fmt .Errorf ("failed to unregister clients: %w" , err )
404
+ }
405
+
406
+ // Remove clients from all groups and global registry
407
+ allGroups , err := c .groupManager .List (ctx )
408
+ if err != nil {
409
+ return fmt .Errorf ("failed to list groups: %w" , err )
410
+ }
411
+
412
+ if len (allGroups ) > 0 {
413
+ clientNames := make ([]string , len (clients ))
414
+ for i , clientToRemove := range clients {
415
+ clientNames [i ] = string (clientToRemove .Name )
416
+ }
417
+
418
+ allGroupNames := make ([]string , len (allGroups ))
419
+ for i , group := range allGroups {
420
+ allGroupNames [i ] = group .Name
421
+ }
422
+
423
+ err = c .groupManager .UnregisterClients (ctx , allGroupNames , clientNames )
424
+ if err != nil {
425
+ return fmt .Errorf ("failed to unregister clients from groups: %w" , err )
426
+ }
427
+ }
428
+
429
+ // Remove clients from global registered clients list
430
+ for _ , clientToRemove := range clients {
431
+ err := config .UpdateConfig (func (c * config.Config ) {
432
+ for i , registeredClient := range c .Clients .RegisteredClients {
433
+ if registeredClient == string (clientToRemove .Name ) {
434
+ // Remove client from slice
435
+ c .Clients .RegisteredClients = append (c .Clients .RegisteredClients [:i ], c .Clients .RegisteredClients [i + 1 :]... )
436
+ logger .Infof ("Successfully unregistered client: %s\n " , clientToRemove .Name )
437
+ return
438
+ }
439
+ }
440
+ logger .Warnf ("Client %s was not found in registered clients list" , clientToRemove .Name )
441
+ })
442
+ if err != nil {
443
+ return fmt .Errorf ("failed to update configuration for client %s: %w" , clientToRemove .Name , err )
444
+ }
445
+ }
446
+
447
+ return nil
448
+ }
0 commit comments