mcp-framework/manifest/manifest.go
2026-04-13 15:52:00 +02:00

123 lines
2.7 KiB
Go

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...),
}
}