9
9
ct "github.com/stacklok/toolhive/pkg/container"
10
10
rt "github.com/stacklok/toolhive/pkg/container/runtime"
11
11
"github.com/stacklok/toolhive/pkg/core"
12
+ "github.com/stacklok/toolhive/pkg/groups"
12
13
"github.com/stacklok/toolhive/pkg/labels"
13
14
"github.com/stacklok/toolhive/pkg/logger"
14
15
)
@@ -30,10 +31,15 @@ type Manager interface {
30
31
RegisterClients (clients []Client , workloads []core.Workload ) error
31
32
// UnregisterClients unregisters multiple clients from ToolHive.
32
33
UnregisterClients (ctx context.Context , clients []Client ) error
34
+ // AddServerToClients adds an MCP server to the appropriate client configurations.
35
+ AddServerToClients (ctx context.Context , serverName , serverURL , transportType , group string ) error
36
+ // RemoveServerFromClients removes an MCP server from the appropriate client configurations.
37
+ RemoveServerFromClients (ctx context.Context , serverName , group string ) error
33
38
}
34
39
35
40
type defaultManager struct {
36
- runtime rt.Runtime
41
+ runtime rt.Runtime
42
+ groupManager groups.Manager
37
43
}
38
44
39
45
// NewManager creates a new client manager instance.
@@ -43,8 +49,14 @@ func NewManager(ctx context.Context) (Manager, error) {
43
49
return nil , err
44
50
}
45
51
52
+ groupManager , err := groups .NewManager ()
53
+ if err != nil {
54
+ return nil , err
55
+ }
56
+
46
57
return & defaultManager {
47
- runtime : runtime ,
58
+ runtime : runtime ,
59
+ groupManager : groupManager ,
48
60
}, nil
49
61
}
50
62
@@ -96,6 +108,49 @@ func (m *defaultManager) UnregisterClients(ctx context.Context, clients []Client
96
108
return nil
97
109
}
98
110
111
+ // AddServerToClients adds an MCP server to the appropriate client configurations.
112
+ // If the workload belongs to a group, only clients registered with that group are updated.
113
+ // If the workload has no group, all registered clients are updated (backward compatibility).
114
+ func (m * defaultManager ) AddServerToClients (
115
+ ctx context.Context , serverName , serverURL , transportType , group string ,
116
+ ) error {
117
+ targetClients := m .getTargetClients (ctx , serverName , group )
118
+
119
+ if len (targetClients ) == 0 {
120
+ logger .Infof ("No target clients found for server %s" , serverName )
121
+ return nil
122
+ }
123
+
124
+ // Add the server to each target client
125
+ for _ , clientName := range targetClients {
126
+ if err := m .updateClientWithServer (clientName , serverName , serverURL , transportType ); err != nil {
127
+ logger .Warnf ("Warning: Failed to update client %s: %v" , clientName , err )
128
+ }
129
+ }
130
+ return nil
131
+ }
132
+
133
+ // RemoveServerFromClients removes an MCP server from the appropriate client configurations.
134
+ // If the server belongs to a group, only clients registered with that group are updated.
135
+ // If the server has no group, all registered clients are updated (backward compatibility).
136
+ func (m * defaultManager ) RemoveServerFromClients (ctx context.Context , serverName , group string ) error {
137
+ targetClients := m .getTargetClients (ctx , serverName , group )
138
+
139
+ if len (targetClients ) == 0 {
140
+ logger .Infof ("No target clients found for server %s" , serverName )
141
+ return nil
142
+ }
143
+
144
+ // Remove the server from each target client
145
+ for _ , clientName := range targetClients {
146
+ if err := m .removeServerFromClient (MCPClient (clientName ), serverName ); err != nil {
147
+ logger .Warnf ("Warning: Failed to remove server from client %s: %v" , clientName , err )
148
+ }
149
+ }
150
+
151
+ return nil
152
+ }
153
+
99
154
// removeMCPsFromClient removes currently running MCP servers from the specified client's configuration
100
155
func (m * defaultManager ) removeMCPsFromClient (ctx context.Context , clientType MCPClient ) error {
101
156
// List workloads
@@ -117,12 +172,6 @@ func (m *defaultManager) removeMCPsFromClient(ctx context.Context, clientType MC
117
172
return nil
118
173
}
119
174
120
- // Find the client configuration for the specified client
121
- clientConfig , err := FindClientConfig (clientType )
122
- if err != nil {
123
- return fmt .Errorf ("failed to find client configurations: %w" , err )
124
- }
125
-
126
175
// For each running container, remove it from the client configuration
127
176
for _ , c := range runningContainers {
128
177
// Get container name from labels
@@ -139,34 +188,17 @@ func (m *defaultManager) removeMCPsFromClient(ctx context.Context, clientType MC
139
188
continue
140
189
}
141
190
142
- // Remove the MCP server configuration with locking
143
- if err := clientConfig .ConfigUpdater .Remove (name ); err != nil {
144
- logger .Warnf ("Warning: Failed to remove MCP server configuration from %s: %v" , clientConfig .Path , err )
191
+ if err := m .removeServerFromClient (clientType , name ); err != nil {
192
+ logger .Warnf ("Warning: %v" , err )
145
193
continue
146
194
}
147
-
148
- logger .Infof ("Removed MCP server %s from client %s\n " , name , clientType )
149
195
}
150
196
151
197
return nil
152
198
}
153
199
154
200
// addWorkloadsToClient adds the specified workloads to the client's configuration
155
- func (* defaultManager ) addWorkloadsToClient (clientType MCPClient , workloads []core.Workload ) error {
156
- // Find the client configuration for the specified client
157
- clientConfig , err := FindClientConfig (clientType )
158
- if err != nil {
159
- if errors .Is (err , ErrConfigFileNotFound ) {
160
- // Create a new client configuration if it doesn't exist
161
- clientConfig , err = CreateClientConfig (clientType )
162
- if err != nil {
163
- return fmt .Errorf ("failed to create client configuration for %s: %w" , clientType , err )
164
- }
165
- } else {
166
- return fmt .Errorf ("failed to find client configuration: %w" , err )
167
- }
168
- }
169
-
201
+ func (m * defaultManager ) addWorkloadsToClient (clientType MCPClient , workloads []core.Workload ) error {
170
202
if len (workloads ) == 0 {
171
203
// No workloads to add, nothing more to do
172
204
return nil
@@ -178,14 +210,87 @@ func (*defaultManager) addWorkloadsToClient(clientType MCPClient, workloads []co
178
210
continue
179
211
}
180
212
181
- // Update the MCP server configuration with locking
182
- if err := Upsert (* clientConfig , workload .Name , workload .URL , string (workload .TransportType )); err != nil {
183
- logger .Warnf ("Warning: Failed to update MCP server configuration in %s: %v" , clientConfig .Path , err )
184
- continue
213
+ // Use the common update function (creates config if needed)
214
+ err := m .updateClientWithServer (
215
+ string (clientType ), workload .Name , workload .URL , string (workload .TransportType ),
216
+ )
217
+ if err != nil {
218
+ return fmt .Errorf ("failed to add workload %s to client %s: %v" , workload .Name , clientType , err )
185
219
}
186
220
187
221
logger .Infof ("Added MCP server %s to client %s\n " , workload .Name , clientType )
188
222
}
189
223
190
224
return nil
191
225
}
226
+
227
+ // removeServerFromClient removes an MCP server from a single client configuration
228
+ func (* defaultManager ) removeServerFromClient (clientName MCPClient , serverName string ) error {
229
+ clientConfig , err := FindClientConfig (clientName )
230
+ if err != nil {
231
+ return fmt .Errorf ("failed to find client configurations: %w" , err )
232
+ }
233
+
234
+ // Remove the MCP server configuration with locking
235
+ if err := clientConfig .ConfigUpdater .Remove (serverName ); err != nil {
236
+ return fmt .Errorf ("failed to remove MCP server configuration from %s: %v" , clientConfig .Path , err )
237
+ }
238
+
239
+ logger .Infof ("Removed MCP server %s from client %s\n " , serverName , clientName )
240
+ return nil
241
+ }
242
+
243
+ // updateClientWithServer updates a single client with an MCP server configuration, creating config if needed
244
+ func (* defaultManager ) updateClientWithServer (clientName , serverName , serverURL , transportType string ) error {
245
+ clientConfig , err := FindClientConfig (MCPClient (clientName ))
246
+ if err != nil {
247
+ if errors .Is (err , ErrConfigFileNotFound ) {
248
+ // Create a new client configuration if it doesn't exist
249
+ clientConfig , err = CreateClientConfig (MCPClient (clientName ))
250
+ if err != nil {
251
+ return fmt .Errorf ("failed to create client configuration for %s: %w" , clientName , err )
252
+ }
253
+ } else {
254
+ return fmt .Errorf ("failed to find client configuration: %w" , err )
255
+ }
256
+ }
257
+
258
+ logger .Infof ("Updating client configuration: %s" , clientConfig .Path )
259
+
260
+ if err := Upsert (* clientConfig , serverName , serverURL , transportType ); err != nil {
261
+ return fmt .Errorf ("failed to update MCP server configuration in %s: %v" , clientConfig .Path , err )
262
+ }
263
+
264
+ logger .Infof ("Successfully updated client configuration: %s" , clientConfig .Path )
265
+ return nil
266
+ }
267
+
268
+ // getTargetClients determines which clients should be updated based on workload group
269
+ func (m * defaultManager ) getTargetClients (ctx context.Context , serverName , groupName string ) []string {
270
+ // Server belongs to a group - only update clients registered with that group
271
+ if groupName != "" {
272
+ group , err := m .groupManager .Get (ctx , groupName )
273
+ if err != nil {
274
+ logger .Warnf (
275
+ "Warning: Failed to get group %s for server %s, skipping client config updates: %v" ,
276
+ group , serverName , err ,
277
+ )
278
+ return nil
279
+ }
280
+
281
+ logger .Infof (
282
+ "Server %s belongs to group %s, updating %d registered client(s)" ,
283
+ serverName , group , len (group .RegisteredClients ),
284
+ )
285
+ return group .RegisteredClients
286
+ }
287
+
288
+ // Server has no group - use backward compatible behavior (update all registered clients)
289
+ appConfig := config .GetConfig ()
290
+ targetClients := appConfig .Clients .RegisteredClients
291
+ logger .Infof (
292
+ "Server %s has no group, updating %d globally registered client(s) for backward compatibility" ,
293
+ serverName , len (targetClients ),
294
+ )
295
+ return targetClients
296
+ }
0 commit comments