3
3
package client
4
4
5
5
import (
6
+ "errors"
6
7
"fmt"
7
8
"os"
8
9
"path/filepath"
@@ -59,6 +60,11 @@ type mcpClientConfig struct {
59
60
IsTransportTypeFieldSupported bool
60
61
}
61
62
63
+ var (
64
+ // ErrConfigFileNotFound is returned when a client configuration file is not found
65
+ ErrConfigFileNotFound = fmt .Errorf ("client config file not found" )
66
+ )
67
+
62
68
var supportedClientIntegrations = []mcpClientConfig {
63
69
{
64
70
ClientType : RooCode ,
@@ -190,18 +196,22 @@ type MCPServerConfig struct {
190
196
191
197
// FindClientConfig returns the client configuration file for a given client type.
192
198
func FindClientConfig (clientType MCPClient ) (* ConfigFile , error ) {
193
- configFiles , err := FindClientConfigs ()
199
+ // retrieve the metadata of the config files
200
+ configFile , err := retrieveConfigFileMetadata (clientType )
194
201
if err != nil {
195
- return nil , fmt .Errorf ("failed to fetch client configurations: %w" , err )
196
- }
197
-
198
- for _ , cf := range configFiles {
199
- if cf .ClientType == clientType {
200
- return & cf , nil
202
+ if errors .Is (err , ErrConfigFileNotFound ) {
203
+ // Propagate the error if the file is not found
204
+ return nil , fmt .Errorf ("%w: for client %s" , ErrConfigFileNotFound , clientType )
201
205
}
206
+ return nil , err
202
207
}
203
208
204
- return nil , fmt .Errorf ("client configuration for %s not found" , clientType )
209
+ // validate the format of the config files
210
+ err = validateConfigFileFormat (configFile )
211
+ if err != nil {
212
+ return nil , fmt .Errorf ("failed to validate config file format: %w" , err )
213
+ }
214
+ return configFile , nil
205
215
}
206
216
207
217
// FindClientConfigs searches for client configuration files in standard locations
@@ -211,25 +221,58 @@ func FindClientConfigs() ([]ConfigFile, error) {
211
221
return nil , fmt .Errorf ("failed to get client status: %w" , err )
212
222
}
213
223
214
- notInstalledClients := make ( map [ string ] bool )
224
+ var configFiles [] ConfigFile
215
225
for _ , clientStatus := range clientStatuses {
216
226
if ! clientStatus .Installed {
217
- notInstalledClients [ string ( clientStatus . ClientType )] = true
227
+ continue
218
228
}
229
+ cf , err := FindClientConfig (clientStatus .ClientType )
230
+ if err != nil {
231
+ return nil , fmt .Errorf ("failed to find client config for %s: %w" , clientStatus .ClientType , err )
232
+ }
233
+ configFiles = append (configFiles , * cf )
219
234
}
220
235
221
- // retrieve the metadata of the config files
222
- configFiles , err := retrieveConfigFilesMetadata (notInstalledClients )
236
+ return configFiles , nil
237
+ }
238
+
239
+ // CreateClientConfig creates a new client configuration file for a given client type.
240
+ func CreateClientConfig (clientType MCPClient ) (* ConfigFile , error ) {
241
+ // Get home directory
242
+ home , err := os .UserHomeDir ()
223
243
if err != nil {
224
- return nil , fmt .Errorf ("failed to retrieve client config metadata : %w" , err )
244
+ return nil , fmt .Errorf ("failed to get home directory : %w" , err )
225
245
}
226
246
227
- // validate the format of the config files
228
- err = validateConfigFilesFormat (configFiles )
247
+ // Find the configuration for the requested client type
248
+ var clientCfg * mcpClientConfig
249
+ for _ , cfg := range supportedClientIntegrations {
250
+ if cfg .ClientType == clientType {
251
+ clientCfg = & cfg
252
+ break
253
+ }
254
+ }
255
+
256
+ if clientCfg == nil {
257
+ return nil , fmt .Errorf ("unsupported client type: %s" , clientType )
258
+ }
259
+
260
+ // Build the path to the configuration file
261
+ path := buildConfigFilePath (clientCfg .SettingsFile , clientCfg .RelPath , clientCfg .PlatformPrefix , []string {home })
262
+
263
+ // Validate that the file does not already exist
264
+ if _ , err := os .Stat (path ); ! os .IsNotExist (err ) {
265
+ return nil , fmt .Errorf ("client config file already exists at %s" , path )
266
+ }
267
+
268
+ // Create the file if it does not exist
269
+ logger .Infof ("Creating new client config file at %s" , path )
270
+ err = os .WriteFile (path , []byte ("{}" ), 0600 )
229
271
if err != nil {
230
- return nil , fmt .Errorf ("failed to validate config file format : %w" , err )
272
+ return nil , fmt .Errorf ("failed to create client config file: %w" , err )
231
273
}
232
- return configFiles , nil
274
+
275
+ return FindClientConfig (clientType )
233
276
}
234
277
235
278
// Upsert updates/inserts an MCP server in a client configuration file
@@ -265,44 +308,48 @@ func GenerateMCPServerURL(transportType string, host string, port int, container
265
308
return ""
266
309
}
267
310
268
- // retrieveConfigFilesMetadata retrieves the metadata for client configuration files.
269
- // It returns a list of ConfigFile objects, which contain metadata about the file that
270
- // can be used when performing operations on the file.
271
- func retrieveConfigFilesMetadata (filters map [string ]bool ) ([]ConfigFile , error ) {
272
- var configFiles []ConfigFile
273
-
311
+ // retrieveConfigFileMetadata retrieves the metadata for client configuration files for a given client type.
312
+ func retrieveConfigFileMetadata (clientType MCPClient ) (* ConfigFile , error ) {
274
313
// Get home directory
275
314
home , err := os .UserHomeDir ()
276
315
if err != nil {
277
316
return nil , fmt .Errorf ("failed to get home directory: %w" , err )
278
317
}
279
318
319
+ // Find the configuration for the requested client type
320
+ var clientCfg * mcpClientConfig
280
321
for _ , cfg := range supportedClientIntegrations {
281
- if filters [string (cfg .ClientType )] {
282
- continue
322
+ if cfg .ClientType == clientType {
323
+ clientCfg = & cfg
324
+ break
283
325
}
326
+ }
284
327
285
- path := buildConfigFilePath (cfg .SettingsFile , cfg .RelPath , cfg .PlatformPrefix , []string {home })
286
-
287
- err := validateConfigFileExists (path )
288
- if err != nil {
289
- logger .Warnf ("failed to validate config file: %w" , err )
290
- continue
291
- }
328
+ if clientCfg == nil {
329
+ return nil , fmt .Errorf ("unsupported client type: %s" , clientType )
330
+ }
292
331
293
- configUpdater := & JSONConfigUpdater {Path : path , MCPServersPathPrefix : cfg .MCPServersPathPrefix }
332
+ // Build the path to the configuration file
333
+ path := buildConfigFilePath (clientCfg .SettingsFile , clientCfg .RelPath , clientCfg .PlatformPrefix , []string {home })
294
334
295
- clientConfig := ConfigFile {
296
- Path : path ,
297
- ConfigUpdater : configUpdater ,
298
- ClientType : cfg .ClientType ,
299
- Extension : cfg .Extension ,
300
- }
335
+ // Validate that the file exists
336
+ if err := validateConfigFileExists (path ); err != nil {
337
+ return nil , err
338
+ }
301
339
302
- configFiles = append (configFiles , clientConfig )
340
+ // Create a config updater for this file
341
+ configUpdater := & JSONConfigUpdater {
342
+ Path : path ,
343
+ MCPServersPathPrefix : clientCfg .MCPServersPathPrefix ,
303
344
}
304
345
305
- return configFiles , nil
346
+ // Return the configuration file metadata
347
+ return & ConfigFile {
348
+ Path : path ,
349
+ ConfigUpdater : configUpdater ,
350
+ ClientType : clientCfg .ClientType ,
351
+ Extension : clientCfg .Extension ,
352
+ }, nil
306
353
}
307
354
308
355
func buildConfigFilePath (settingsFile string , relPath []string , platformPrefix map [string ][]string , path []string ) string {
@@ -317,27 +364,22 @@ func buildConfigFilePath(settingsFile string, relPath []string, platformPrefix m
317
364
// validateConfigFileExists validates that a client configuration file exists.
318
365
func validateConfigFileExists (path string ) error {
319
366
if _ , err := os .Stat (path ); os .IsNotExist (err ) {
320
- return fmt . Errorf ( "file does not exist: %s" , path )
367
+ return ErrConfigFileNotFound
321
368
}
322
369
return nil
323
370
}
324
371
325
- // validateConfigFileFormat validates the format of a client configuration file
326
- // It returns an error if the file is not valid JSON.
327
- func validateConfigFilesFormat (configFiles []ConfigFile ) error {
328
- for _ , cf := range configFiles {
329
- data , err := os .ReadFile (cf .Path )
330
- if err != nil {
331
- return fmt .Errorf ("failed to read file %s: %w" , cf .Path , err )
332
- }
333
-
334
- // Default to JSON
335
- // we don't care about the contents of the file, we just want to validate that it's valid JSON
336
- _ , err = hujson .Parse (data )
337
- if err != nil {
338
- return fmt .Errorf ("failed to parse JSON for file %s: %w" , cf .Path , err )
339
- }
372
+ func validateConfigFileFormat (cf * ConfigFile ) error {
373
+ data , err := os .ReadFile (cf .Path )
374
+ if err != nil {
375
+ return fmt .Errorf ("failed to read file %s: %w" , cf .Path , err )
340
376
}
341
377
378
+ // Default to JSON
379
+ // we don't care about the contents of the file, we just want to validate that it's valid JSON
380
+ _ , err = hujson .Parse (data )
381
+ if err != nil {
382
+ return fmt .Errorf ("failed to parse JSON for file %s: %w" , cf .Path , err )
383
+ }
342
384
return nil
343
385
}
0 commit comments