package manifest import ( "errors" "fmt" "os" "path/filepath" "strings" "github.com/BurntSushi/toml" "gitea.lclr.dev/AI/mcp-framework/update" ) const DefaultFile = "mcp.toml" type File struct { Update Update `toml:"update"` } type Update struct { SourceName string `toml:"source_name"` BaseURL string `toml:"base_url"` LatestReleaseURL string `toml:"latest_release_url"` TokenHeader string `toml:"token_header"` TokenEnvNames []string `toml:"token_env_names"` } 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) } var file File if err := toml.Unmarshal(data, &file); err != nil { return File{}, fmt.Errorf("parse manifest %s: %w", path, 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.Update.normalize() } func (u *Update) normalize() { u.SourceName = strings.TrimSpace(u.SourceName) u.BaseURL = strings.TrimRight(strings.TrimSpace(u.BaseURL), "/") u.LatestReleaseURL = strings.TrimSpace(u.LatestReleaseURL) u.TokenHeader = strings.TrimSpace(u.TokenHeader) envNames := u.TokenEnvNames[:0] for _, envName := range u.TokenEnvNames { if trimmed := strings.TrimSpace(envName); trimmed != "" { envNames = append(envNames, trimmed) } } u.TokenEnvNames = envNames } func (u Update) ReleaseSource() update.ReleaseSource { u.normalize() return update.ReleaseSource{ Name: u.SourceName, BaseURL: u.BaseURL, LatestReleaseURL: u.LatestReleaseURL, TokenHeader: u.TokenHeader, TokenEnvNames: append([]string(nil), u.TokenEnvNames...), } }