# Helpers CLI `cli` fournit des helpers simples pour les assistants interactifs : ```go 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 } _ = token ``` Pour décrire un setup complet sans réécrire la boucle interactive : ```go result, err := cli.RunSetup(cli.SetupOptions{ Stdin: os.Stdin, Stdout: os.Stdout, Fields: []cli.SetupField{ { Name: "base_url", Label: "Base URL", Type: cli.SetupFieldURL, Required: true, }, { Name: "api_token", Label: "API token", Type: cli.SetupFieldSecret, Required: true, ExistingSecret: storedToken, }, { Name: "enabled", Label: "Enable integration", Type: cli.SetupFieldBool, Default: "true", }, { Name: "scopes", Label: "Scopes", Type: cli.SetupFieldList, Default: "read,write", Normalize: func(value string) string { return strings.TrimSpace(strings.ToLower(value)) }, }, }, }) if err != nil { return err } baseURL, _ := result.Get("base_url") apiToken, _ := result.Get("api_token") enabled, _ := result.Get("enabled") scopes, _ := result.Get("scopes") if apiToken.KeptStoredSecret { fmt.Println("Stored token kept.") } _ = baseURL _ = enabled _ = scopes ``` Pour persister un secret de setup avec write+read-back et messages homogènes : ```go if err := cli.WriteSetupSecretVerified(cli.SetupSecretWriteOptions{ Store: store, SecretName: "api-token", SecretLabel: "API token", TokenEnv: "MY_MCP_API_TOKEN", Value: apiToken, }); err != nil { return err } ``` Chaque champ peut déclarer ses propres hooks de validation (`Validate`, `ValidateBool`, `ValidateList`). Les validations sont appliquées de manière cohérente en TTY et en stdin non interactif. Pour standardiser la résolution `flag > env > config > secret` avec provenance : ```go store, err := secretstore.OpenFromManifest(secretstore.OpenFromManifestOptions{ ServiceName: "my-mcp", }) if err != nil { return err } lookup := cli.ResolveLookup(cli.ResolveLookupOptions{ Flag: cli.MapLookup(flagValues), Env: cli.EnvLookup(os.LookupEnv), Config: cli.ConfigMap(configValues), Secret: cli.SecretStore(store), }) resolution, err := cli.ResolveFields(cli.ResolveOptions{ Fields: []cli.FieldSpec{ {Name: "base_url", Required: true, FlagKey: "base-url", EnvKey: "MY_MCP_BASE_URL"}, {Name: "api_token", Required: true, EnvKey: "MY_MCP_API_TOKEN", SecretKey: "my-mcp-api-token"}, {Name: "timeout", DefaultValue: "30s"}, }, Lookup: lookup, }) if err != nil { return err } if err := cli.RenderResolutionProvenance(os.Stdout, resolution); err != nil { return err } ``` `ResolveOptions.Order` permet de changer la priorité globale si nécessaire, et `FieldSpec.Sources` permet de définir un ordre spécifique pour un champ. Le package fournit un socle réutilisable pour une commande `doctor`. Pour les cas standards (config, secret store, connectivité), préférer `bootstrap.StandardConfigTestHandler` qui câble `RunDoctor` sans boilerplate. Pour un contrôle fin ou un config test impératif, utiliser `RunDoctor` directement : ```go report := cli.RunDoctor(context.Background(), cli.DoctorOptions{ ConfigCheck: cli.NewConfigCheck(store), SecretStoreCheck: cli.SecretStoreAvailabilityCheck(func() (secretstore.Store, error) { return secretstore.OpenFromManifest(secretstore.OpenFromManifestOptions{ ServiceName: "my-mcp", }) }), ConnectivityCheck: func(context.Context) cli.DoctorResult { if err := pingBackend(); err != nil { return cli.DoctorResult{ Name: "connectivity", Status: cli.DoctorStatusFail, Summary: "backend inaccessible", Detail: err.Error(), } } return cli.DoctorResult{ Name: "connectivity", Status: cli.DoctorStatusOK, Summary: "backend accessible", } }, }) if err := cli.RenderDoctorReport(os.Stdout, report); err != nil { return err } if report.HasFailures() { os.Exit(1) } ``` `ManifestDir` est optionnel. Quand il est fourni, `RunDoctor` inclut un `ManifestCheck` qui vérifie la présence et la validité de `mcp.toml` dans ce répertoire. Ne l'inclure que si ce check est pertinent pour l'application. Quand `SecretBackendPolicy` vaut `bitwarden-cli`, le check Bitwarden est ajouté automatiquement. Pour le désactiver explicitement : ```go report := cli.RunDoctor(context.Background(), cli.DoctorOptions{ DisableAutoBitwardenCheck: true, }) ``` Pour éviter des checks custom répétitifs sur les profils, `doctor` expose aussi un helper basé sur `FieldSpec` : ```go report := cli.RunDoctor(context.Background(), cli.DoctorOptions{ ExtraChecks: []cli.DoctorCheck{ cli.RequiredResolvedFieldsCheck(cli.ResolveOptions{ Fields: []cli.FieldSpec{ {Name: "base_url", Required: true, EnvKey: "MY_MCP_BASE_URL"}, { Name: "api_token", Required: true, EnvKey: "MY_MCP_API_TOKEN", SecretKey: "my-mcp-api-token", Sources: []cli.ValueSource{cli.SourceEnv, cli.SourceSecret}, }, }, Lookup: cli.ResolveLookup(cli.ResolveLookupOptions{ Env: cli.EnvLookup(os.LookupEnv), Config: cli.ConfigMap(configValues), Secret: cli.SecretStore(secretStore), }), }), }, }) ``` En cas d'échec de résolution, tu peux aussi réutiliser le formatteur `cli.FormatResolveFieldsError(err)` dans un check custom pour garder des messages homogènes.