All checks were successful
CI / test (push) Successful in 12s
Documenter BitwardenLoginHandler, StandardConfigTestHandler et le comportement opt-in de ManifestCheck dans RunDoctor. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
208 lines
5.6 KiB
Markdown
208 lines
5.6 KiB
Markdown
# 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.
|