package manifest import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/BurntSushi/toml" "forge.lclr.dev/AI/mcp-framework/update" ) const DefaultFile = "mcp.toml" const EmbeddedSource = "embedded:mcp.toml" type File struct { BinaryName string `toml:"binary_name"` DocsURL string `toml:"docs_url"` Update Update `toml:"update"` Environment Environment `toml:"environment"` SecretStore SecretStore `toml:"secret_store"` Profiles Profiles `toml:"profiles"` Bootstrap Bootstrap `toml:"bootstrap"` Config Config `toml:"config"` } type Update struct { SourceName string `toml:"source_name"` Driver string `toml:"driver"` Repository string `toml:"repository"` BaseURL string `toml:"base_url"` LatestReleaseURL string `toml:"latest_release_url"` AssetNameTemplate string `toml:"asset_name_template"` ChecksumAssetName string `toml:"checksum_asset_name"` ChecksumRequired bool `toml:"checksum_required"` SignatureAssetName string `toml:"signature_asset_name"` SignatureRequired bool `toml:"signature_required"` SignaturePublicKey string `toml:"signature_public_key"` SignaturePublicKeyEnvNames []string `toml:"signature_public_key_env_names"` TokenHeader string `toml:"token_header"` TokenPrefix string `toml:"token_prefix"` TokenEnvNames []string `toml:"token_env_names"` } type Environment struct { Known []string `toml:"known"` } type SecretStore struct { BackendPolicy string `toml:"backend_policy"` BitwardenCache *bool `toml:"bitwarden_cache"` } type Profiles struct { Default string `toml:"default"` Known []string `toml:"known"` } type Bootstrap struct { Description string `toml:"description"` } type Config struct { Fields []ConfigField `toml:"fields"` } type ConfigField struct { Name string `toml:"name"` Flag string `toml:"flag"` Env string `toml:"env"` ConfigKey string `toml:"config_key"` SecretKeyTemplate string `toml:"secret_key_template"` Type string `toml:"type"` Label string `toml:"label"` Default string `toml:"default"` Required bool `toml:"required"` Sources []string `toml:"sources"` } type BootstrapMetadata struct { BinaryName string Description string DocsURL string DefaultProfile string Profiles []string } type ScaffoldMetadata struct { BinaryName string DocsURL string KnownEnvironmentVariables []string SecretStorePolicy string DefaultProfile string Profiles []string } func Find(startDir string) (string, error) { dir := strings.TrimSpace(startDir) if dir == "" { wd, err := os.Getwd() if err != nil { return "", fmt.Errorf("resolve working directory: %w", err) } dir = wd } absDir, err := filepath.Abs(dir) if err != nil { return "", fmt.Errorf("resolve manifest search path %q: %w", dir, err) } for { path := filepath.Join(absDir, DefaultFile) info, err := os.Stat(path) switch { case err == nil: if info.IsDir() { return "", fmt.Errorf("manifest path %q is a directory", path) } return path, nil case !errors.Is(err, os.ErrNotExist): return "", fmt.Errorf("stat manifest %q: %w", path, err) } parent := filepath.Dir(absDir) if parent == absDir { return "", fmt.Errorf("find manifest %q from %q: %w", DefaultFile, dir, os.ErrNotExist) } absDir = parent } } func Load(path string) (File, error) { data, err := os.ReadFile(path) if err != nil { return File{}, fmt.Errorf("read manifest %s: %w", path, err) } return parse(data, path) } func LoadEmbedded(content string) (File, string, error) { trimmed := strings.TrimSpace(content) if trimmed == "" { return File{}, "", os.ErrNotExist } file, err := parse([]byte(trimmed), EmbeddedSource) if err != nil { return File{}, "", err } return file, EmbeddedSource, nil } func LoadDefaultOrEmbedded(startDir, embeddedContent string) (File, string, error) { file, path, err := LoadDefault(startDir) if err == nil { return file, path, nil } if !errors.Is(err, os.ErrNotExist) { return File{}, "", err } return LoadEmbedded(embeddedContent) } func parse(data []byte, source string) (File, error) { var file File if err := toml.Unmarshal(data, &file); err != nil { return File{}, fmt.Errorf("parse manifest %s: %w", source, err) } file.normalize() return file, nil } func LoadDefault(startDir string) (File, string, error) { path, err := Find(startDir) if err != nil { return File{}, "", err } file, err := Load(path) if err != nil { return File{}, "", err } return file, path, nil } func (f *File) normalize() { f.BinaryName = strings.TrimSpace(f.BinaryName) f.DocsURL = strings.TrimSpace(f.DocsURL) f.Update.normalize() f.Environment.normalize() f.SecretStore.normalize() f.Profiles.normalize() f.Bootstrap.normalize() f.Config.normalize() } func (u *Update) normalize() { u.SourceName = strings.TrimSpace(u.SourceName) u.Driver = strings.ToLower(strings.TrimSpace(u.Driver)) u.Repository = strings.Trim(strings.TrimSpace(u.Repository), "/") u.BaseURL = strings.TrimRight(strings.TrimSpace(u.BaseURL), "/") u.LatestReleaseURL = strings.TrimSpace(u.LatestReleaseURL) u.AssetNameTemplate = strings.TrimSpace(u.AssetNameTemplate) u.ChecksumAssetName = strings.TrimSpace(u.ChecksumAssetName) u.SignatureAssetName = strings.TrimSpace(u.SignatureAssetName) u.SignaturePublicKey = strings.TrimSpace(u.SignaturePublicKey) u.SignaturePublicKeyEnvNames = normalizeStringList(u.SignaturePublicKeyEnvNames) u.TokenHeader = strings.TrimSpace(u.TokenHeader) u.TokenPrefix = strings.TrimSpace(u.TokenPrefix) u.TokenEnvNames = normalizeStringList(u.TokenEnvNames) } func (e *Environment) normalize() { e.Known = normalizeStringList(e.Known) } func (s *SecretStore) normalize() { s.BackendPolicy = strings.TrimSpace(s.BackendPolicy) } func (p *Profiles) normalize() { p.Default = strings.TrimSpace(p.Default) p.Known = normalizeStringList(p.Known) } func (b *Bootstrap) normalize() { b.Description = strings.TrimSpace(b.Description) } func (c *Config) normalize() { for i := range c.Fields { c.Fields[i].normalize() } } func (f *ConfigField) normalize() { f.Name = strings.TrimSpace(f.Name) f.Flag = strings.TrimSpace(f.Flag) f.Env = strings.TrimSpace(f.Env) f.ConfigKey = strings.TrimSpace(f.ConfigKey) f.SecretKeyTemplate = strings.TrimSpace(f.SecretKeyTemplate) f.Type = strings.ToLower(strings.TrimSpace(f.Type)) f.Label = strings.TrimSpace(f.Label) f.Default = strings.TrimSpace(f.Default) f.Sources = normalizeStringList(f.Sources) } func (u Update) ReleaseSource() update.ReleaseSource { u.normalize() return update.ReleaseSource{ Name: u.SourceName, Driver: u.Driver, Repository: u.Repository, BaseURL: u.BaseURL, LatestReleaseURL: u.LatestReleaseURL, AssetNameTemplate: u.AssetNameTemplate, ChecksumAssetName: u.ChecksumAssetName, ChecksumRequired: u.ChecksumRequired, SignatureAssetName: u.SignatureAssetName, SignatureRequired: u.SignatureRequired, SignaturePublicKey: u.SignaturePublicKey, SignaturePublicKeyEnvNames: append([]string(nil), u.SignaturePublicKeyEnvNames...), TokenHeader: u.TokenHeader, TokenPrefix: u.TokenPrefix, TokenEnvNames: append([]string(nil), u.TokenEnvNames...), } } func (f File) BootstrapInfo() BootstrapMetadata { f.normalize() return BootstrapMetadata{ BinaryName: f.BinaryName, Description: f.Bootstrap.Description, DocsURL: f.DocsURL, DefaultProfile: f.Profiles.Default, Profiles: append([]string(nil), f.Profiles.Known...), } } func (f File) ScaffoldInfo() ScaffoldMetadata { f.normalize() return ScaffoldMetadata{ BinaryName: f.BinaryName, DocsURL: f.DocsURL, KnownEnvironmentVariables: append([]string(nil), f.Environment.Known...), SecretStorePolicy: f.SecretStore.BackendPolicy, DefaultProfile: f.Profiles.Default, Profiles: append([]string(nil), f.Profiles.Known...), } } func normalizeStringList(values []string) []string { normalized := values[:0] for _, value := range values { if trimmed := strings.TrimSpace(value); trimmed != "" { normalized = append(normalized, trimmed) } } return normalized }