mcp-framework/cmd/mcp-framework/main.go

269 lines
8.1 KiB
Go

package main
import (
"errors"
"flag"
"fmt"
"io"
"os"
"path/filepath"
"strings"
generatepkg "gitea.lclr.dev/AI/mcp-framework/generate"
scaffoldpkg "gitea.lclr.dev/AI/mcp-framework/scaffold"
)
const toolName = "mcp-framework"
func main() {
if err := run(os.Args[1:], os.Stdout, os.Stderr); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}
func run(args []string, stdout, stderr io.Writer) error {
if stdout == nil {
stdout = io.Discard
}
if stderr == nil {
stderr = io.Discard
}
if len(args) == 0 || isHelpArg(args[0]) {
printGlobalHelp(stdout)
return nil
}
switch args[0] {
case "generate":
return runGenerate(args[1:], stdout, stderr)
case "scaffold":
return runScaffold(args[1:], stdout, stderr)
default:
return fmt.Errorf("unknown command %q", args[0])
}
}
func runGenerate(args []string, stdout, stderr io.Writer) error {
if shouldShowHelp(args) {
printGenerateHelp(stdout)
return nil
}
fs := flag.NewFlagSet("generate", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var manifestPath string
var packageDir string
var packageName string
var check bool
fs.StringVar(&manifestPath, "manifest", "", "Chemin du mcp.toml à lire (défaut: ./mcp.toml)")
fs.StringVar(&packageDir, "package-dir", "mcpgen", "Répertoire du package Go généré")
fs.StringVar(&packageName, "package-name", "", "Nom du package Go généré (défaut: dérivé du dossier)")
fs.BoolVar(&check, "check", false, "Échoue si les fichiers générés ne sont pas à jour")
if err := fs.Parse(args); err != nil {
_ = stderr
return fmt.Errorf("parse generate flags: %w", err)
}
if fs.NArg() > 0 {
return fmt.Errorf("unexpected argument(s): %s", strings.Join(fs.Args(), ", "))
}
result, err := generatepkg.Generate(generatepkg.Options{
ManifestPath: manifestPath,
PackageDir: packageDir,
PackageName: packageName,
Check: check,
})
if err != nil {
return err
}
if check {
if _, err := fmt.Fprintln(stdout, "Generated files are up to date"); err != nil {
return err
}
return nil
}
for _, file := range result.Files {
if _, err := fmt.Fprintf(stdout, "Generated %s\n", filepath.ToSlash(file)); err != nil {
return err
}
}
return nil
}
func runScaffold(args []string, stdout, stderr io.Writer) error {
if len(args) == 0 || isHelpArg(args[0]) {
printScaffoldHelp(stdout)
return nil
}
switch args[0] {
case "init":
return runScaffoldInit(args[1:], stdout, stderr)
default:
return fmt.Errorf("unknown scaffold subcommand %q", args[0])
}
}
func runScaffoldInit(args []string, stdout, stderr io.Writer) error {
if shouldShowHelp(args) {
printScaffoldInitHelp(stdout)
return nil
}
fs := flag.NewFlagSet("scaffold init", flag.ContinueOnError)
fs.SetOutput(io.Discard)
var target string
var modulePath string
var binaryName string
var description string
var docsURL string
var defaultProfile string
var profiles string
var knownEnv string
var secretStorePolicy string
var releaseDriver string
var releaseBaseURL string
var releaseRepository string
var releaseTokenEnv string
var releasePublicKeyEnv string
var overwrite bool
fs.StringVar(&target, "target", "", "Répertoire cible du nouveau projet (requis)")
fs.StringVar(&modulePath, "module", "", "Chemin de module Go du projet généré")
fs.StringVar(&binaryName, "binary", "", "Nom du binaire généré")
fs.StringVar(&description, "description", "", "Description bootstrap du binaire")
fs.StringVar(&docsURL, "docs-url", "", "URL de documentation du projet")
fs.StringVar(&defaultProfile, "default-profile", "", "Profil par défaut")
fs.StringVar(&profiles, "profiles", "", "Liste CSV de profils connus")
fs.StringVar(&knownEnv, "known-env", "", "Liste CSV de variables d'environnement connues")
fs.StringVar(&secretStorePolicy, "secret-store-policy", "", "Politique secret store (auto, keyring-any, kwallet-only, env-only)")
fs.StringVar(&releaseDriver, "release-driver", "", "Driver de release (gitea, gitlab, github)")
fs.StringVar(&releaseBaseURL, "release-base-url", "", "Base URL de la forge release")
fs.StringVar(&releaseRepository, "release-repository", "", "Repository release (org/repo)")
fs.StringVar(&releaseTokenEnv, "release-token-env", "", "Nom de variable d'environnement pour le token release")
fs.StringVar(&releasePublicKeyEnv, "release-pubkey-env", "", "Nom de variable d'environnement pour la cle publique Ed25519 de signature")
fs.BoolVar(&overwrite, "overwrite", false, "Autorise l'écrasement des fichiers existants")
if err := fs.Parse(args); err != nil {
_ = stderr
return fmt.Errorf("parse scaffold init flags: %w", err)
}
if fs.NArg() > 0 {
return fmt.Errorf("unexpected argument(s): %s", strings.Join(fs.Args(), ", "))
}
if strings.TrimSpace(target) == "" {
return errors.New("--target is required")
}
result, err := scaffoldpkg.Generate(scaffoldpkg.Options{
TargetDir: target,
ModulePath: modulePath,
BinaryName: binaryName,
Description: description,
DocsURL: docsURL,
DefaultProfile: defaultProfile,
Profiles: parseCSV(profiles),
KnownEnvironmentVariables: parseCSV(knownEnv),
SecretStorePolicy: secretStorePolicy,
ReleaseDriver: releaseDriver,
ReleaseBaseURL: releaseBaseURL,
ReleaseRepository: releaseRepository,
ReleaseTokenEnv: releaseTokenEnv,
ReleasePublicKeyEnv: releasePublicKeyEnv,
Overwrite: overwrite,
})
if err != nil {
return err
}
if _, err := fmt.Fprintf(stdout, "Scaffold generated in %s\n", result.Root); err != nil {
return err
}
for _, file := range result.Files {
if _, err := fmt.Fprintf(stdout, "- %s\n", file); err != nil {
return err
}
}
return nil
}
func printGlobalHelp(w io.Writer) {
fmt.Fprintf(
w,
"Usage:\n %s <command> [options]\n\nCommands:\n generate Génère la glue Go depuis mcp.toml\n scaffold init Génère un nouveau projet MCP\n\nUse `%s help` for this message.\n",
toolName,
toolName,
)
}
func printGenerateHelp(w io.Writer) {
fmt.Fprintf(
w,
"Usage:\n %s generate [flags]\n\nFlags:\n --manifest Chemin du mcp.toml à lire\n --package-dir Répertoire du package Go généré (défaut: mcpgen)\n --package-name Nom du package Go généré (défaut: dérivé du dossier)\n --check Vérifie que les fichiers générés sont à jour\n",
toolName,
)
}
func printScaffoldHelp(w io.Writer) {
fmt.Fprintf(
w,
"Usage:\n %s scaffold init [flags]\n\nSubcommands:\n init Génère un nouveau squelette MCP\n",
toolName,
)
}
func printScaffoldInitHelp(w io.Writer) {
fmt.Fprintf(
w,
"Usage:\n %s scaffold init --target <dir> [flags]\n\nFlags:\n --target Répertoire cible (requis)\n --module Module Go (ex: example.com/my-mcp)\n --binary Nom du binaire\n --description Description bootstrap\n --docs-url URL de documentation\n --default-profile Profil par défaut\n --profiles CSV des profils connus\n --known-env CSV des variables d'environnement connues\n --secret-store-policy auto|keyring-any|kwallet-only|env-only\n --release-driver gitea|gitlab|github\n --release-base-url URL de base de la forge\n --release-repository Dépôt release (org/repo)\n --release-token-env Variable token release\n --release-pubkey-env Variable cle publique Ed25519 release\n --overwrite Écraser les fichiers existants\n",
toolName,
)
}
func shouldShowHelp(args []string) bool {
for _, arg := range args {
if isHelpArg(arg) {
return true
}
}
return false
}
func isHelpArg(arg string) bool {
switch strings.TrimSpace(arg) {
case "-h", "--help", "help":
return true
default:
return false
}
}
func parseCSV(value string) []string {
if strings.TrimSpace(value) == "" {
return nil
}
parts := strings.Split(value, ",")
result := make([]string, 0, len(parts))
for _, part := range parts {
trimmed := strings.TrimSpace(part)
if trimmed == "" {
continue
}
result = append(result, trimmed)
}
return result
}