package config import ( "fmt" "os" "strings" toml "github.com/pelletier/go-toml/v2" ) // CLIConfig is the configuration for the mcp CLI binary. type CLIConfig struct { Services ServicesConfig `toml:"services"` Build BuildConfig `toml:"build"` MCIAS MCIASConfig `toml:"mcias"` Auth AuthConfig `toml:"auth"` Nodes []NodeConfig `toml:"nodes"` } // BuildConfig holds settings for building container images. type BuildConfig struct { Workspace string `toml:"workspace"` } // ServicesConfig defines where service definition files live. type ServicesConfig struct { Dir string `toml:"dir"` } // MCIASConfig holds MCIAS connection settings, shared by CLI and agent. type MCIASConfig struct { ServerURL string `toml:"server_url"` CACert string `toml:"ca_cert"` ServiceName string `toml:"service_name"` } // AuthConfig holds authentication settings for the CLI. type AuthConfig struct { TokenPath string `toml:"token_path"` Username string `toml:"username"` // optional, for unattended operation PasswordFile string `toml:"password_file"` // optional, for unattended operation } // NodeConfig defines a managed node that the CLI connects to. type NodeConfig struct { Name string `toml:"name"` Address string `toml:"address"` } // LoadCLIConfig reads and validates a CLI configuration file. // Environment variables override file values for select fields. func LoadCLIConfig(path string) (*CLIConfig, error) { data, err := os.ReadFile(path) //nolint:gosec // config path from trusted CLI flag if err != nil { return nil, fmt.Errorf("read config %q: %w", path, err) } var cfg CLIConfig if err := toml.Unmarshal(data, &cfg); err != nil { return nil, fmt.Errorf("parse config %q: %w", path, err) } applyCLIEnvOverrides(&cfg) if err := validateCLIConfig(&cfg); err != nil { return nil, fmt.Errorf("validate config: %w", err) } return &cfg, nil } func applyCLIEnvOverrides(cfg *CLIConfig) { if v := os.Getenv("MCP_SERVICES_DIR"); v != "" { cfg.Services.Dir = v } if v := os.Getenv("MCP_BUILD_WORKSPACE"); v != "" { cfg.Build.Workspace = v } if v := os.Getenv("MCP_MCIAS_SERVER_URL"); v != "" { cfg.MCIAS.ServerURL = v } if v := os.Getenv("MCP_MCIAS_CA_CERT"); v != "" { cfg.MCIAS.CACert = v } if v := os.Getenv("MCP_MCIAS_SERVICE_NAME"); v != "" { cfg.MCIAS.ServiceName = v } if v := os.Getenv("MCP_AUTH_TOKEN_PATH"); v != "" { cfg.Auth.TokenPath = v } } func validateCLIConfig(cfg *CLIConfig) error { if cfg.Services.Dir == "" { return fmt.Errorf("services.dir is required") } if cfg.MCIAS.ServerURL == "" { return fmt.Errorf("mcias.server_url is required") } if cfg.MCIAS.ServiceName == "" { return fmt.Errorf("mcias.service_name is required") } if cfg.Auth.TokenPath == "" { return fmt.Errorf("auth.token_path is required") } // Expand ~ in workspace path. if strings.HasPrefix(cfg.Build.Workspace, "~/") { home, err := os.UserHomeDir() if err != nil { return fmt.Errorf("expand workspace path: %w", err) } cfg.Build.Workspace = home + cfg.Build.Workspace[1:] } return nil }