mcp-framework/README.md
2026-04-13 15:53:53 +02:00

6.3 KiB

mcp-framework

Bibliotheque Go pour construire des binaires MCP avec :

  • resolution de profils CLI
  • stockage JSON de configuration dans os.UserConfigDir()
  • stockage de secrets dans le wallet natif selon l'OS
  • lecture d'un manifeste mcp.toml a la racine du projet
  • pipeline d'auto-update via endpoint de release configurable

Le framework est volontairement petit. Il fournit des briques reutilisables, pas une application MCP complete.

Installation

go get gitea.lclr.dev/AI/mcp-framework

Packages

  • cli : helpers pour resoudre un profil, valider une URL et demander des valeurs a l'utilisateur.
  • config : lecture/ecriture atomique d'une config JSON versionnee dans os.UserConfigDir().
  • manifest : lecture de mcp.toml a la racine du projet et conversion vers update.ReleaseSource.
  • secretstore : lecture/ecriture de secrets dans le wallet natif.
  • update : telechargement et remplacement du binaire courant depuis un endpoint de release.

Usage Type

Le flux typique cote application est :

  1. Resoudre le profil actif avec cli.
  2. Charger la config versionnee avec config.
  3. Lire les secrets avec secretstore.
  4. Charger mcp.toml avec manifest.
  5. Executer l'auto-update avec update si necessaire.

Manifeste mcp.toml

Le package manifest cherche automatiquement mcp.toml dans le repertoire courant puis remonte les repertoires parents jusqu'a trouver le fichier.

Exemple minimal :

[update]
source_name = "Gitea releases"
base_url = "https://gitea.example.com"
latest_release_url = "https://gitea.example.com/api/v1/repos/org/repo/releases/latest"
token_header = "Authorization"
token_env_names = ["GITEA_TOKEN"]

Champs supportes dans [update] :

  • source_name : nom humain de la source de release, utilise dans certains messages d'erreur.
  • base_url : base de la forge ou du service de release.
  • latest_release_url : URL complete qui retourne la release la plus recente.
  • token_header : header HTTP a utiliser pour l'authentification.
  • token_env_names : liste de variables d'environnement candidates pour retrouver le token.

Exemple de chargement :

file, path, err := manifest.LoadDefault(".")
if err != nil {
	return err
}

fmt.Printf("manifest loaded from %s\n", path)
source := file.Update.ReleaseSource()

Config JSON

Le package config stocke une structure generique par profil dans un JSON prive pour l'utilisateur courant.

Exemple :

type Profile struct {
	BaseURL string `json:"base_url"`
	APIKey  string `json:"api_key"`
}

store := config.NewStore[Profile]("my-mcp")

cfg, path, err := store.LoadDefault()
if err != nil {
	return err
}

profileName := cli.ResolveProfileName(flagProfile, os.Getenv("MY_MCP_PROFILE"), cfg.CurrentProfile)
profile := cfg.Profiles[profileName]

profile.BaseURL = "https://api.example.com"
cfg.CurrentProfile = profileName
cfg.Profiles[profileName] = profile

_, err = store.SaveDefault(cfg)
if err != nil {
	return err
}

fmt.Printf("config saved to %s\n", path)

Notes :

  • le fichier est cree avec des permissions 0600
  • le repertoire parent est force en 0700
  • l'ecriture est atomique via un fichier temporaire puis rename
  • si le fichier n'existe pas, Load et LoadDefault retournent une config vide par defaut

Secrets

Le package secretstore s'appuie sur le wallet natif du systeme :

  • macOS : Keychain
  • Linux : Secret Service ou KWallet selon l'environnement
  • Windows : Credential Manager

Exemple :

store, err := secretstore.Open(secretstore.Options{
	ServiceName: "my-mcp",
})
if err != nil {
	return err
}

if err := store.SetSecret("api-token", "My MCP API token", token); err != nil {
	return err
}

token, err = store.GetSecret("api-token")
switch {
case err == nil:
	// secret found
case errors.Is(err, secretstore.ErrNotFound):
	// first run
default:
	return err
}

CLI Helpers

cli fournit des helpers simples pour les assistants interactifs :

profileName := cli.ResolveProfileName(flagProfile, os.Getenv("MY_MCP_PROFILE"), cfg.CurrentProfile)

baseURL, err := cli.PromptLine(reader, os.Stdout, "Base URL", profile.BaseURL)
if err != nil {
	return err
}
if err := cli.ValidateBaseURL(baseURL); err != nil {
	return err
}

token, err := cli.PromptSecret(os.Stdin, os.Stdout, "API token", hasStoredSecret, storedToken)
if err != nil {
	return err
}

Auto-Update

Le package update ne deduit pas la forge ni l'authentification. L'application cliente fournit l'URL de release, le header d'auth eventuel et, si besoin, les variables d'environnement a consulter.

Le format attendu pour la reponse latest release est actuellement :

{
  "tag_name": "v1.2.3",
  "assets": {
    "links": [
      {
        "name": "my-mcp-linux-amd64",
        "url": "https://example.com/downloads/my-mcp-linux-amd64"
      }
    ]
  }
}

Exemple :

file, _, err := manifest.LoadDefault(".")
if err != nil {
	return err
}

err = update.Run(ctx, update.Options{
	CurrentVersion: version,
	BinaryName:     "my-mcp",
	ReleaseSource:  file.Update.ReleaseSource(),
	Stdout:         os.Stdout,
})
if err != nil {
	return err
}

Contraintes actuelles :

  • le latest_release_url doit etre renseigne explicitement
  • les assets supportes sont darwin/amd64, darwin/arm64, linux/amd64 et windows/amd64
  • le remplacement du binaire n'est pas supporte sur Windows
  • le nom de l'asset est derive de BinaryName, GOOS et GOARCH

Exemple Minimal

type Profile struct {
	BaseURL string `json:"base_url"`
}

func run(ctx context.Context, flagProfile string) error {
	cfgStore := config.NewStore[Profile]("my-mcp")

	cfg, _, err := cfgStore.LoadDefault()
	if err != nil {
		return err
	}

	profileName := cli.ResolveProfileName(flagProfile, os.Getenv("MY_MCP_PROFILE"), cfg.CurrentProfile)
	profile := cfg.Profiles[profileName]

	manifestFile, _, err := manifest.LoadDefault(".")
	if err != nil {
		return err
	}

	err = update.Run(ctx, update.Options{
		CurrentVersion: version,
		BinaryName:     "my-mcp",
		ReleaseSource:  manifestFile.Update.ReleaseSource(),
	})
	if err != nil {
		return err
	}

	_ = profile
	return nil
}

Limites Actuelles

  • le manifeste gere uniquement la section [update]
  • le framework ne fournit pas encore d'interface unique de bootstrap
  • l'auto-update reste volontairement simple et ne supporte pas encore de scripts externes