feat(bootstrap): expose doctor alias in help and options

This commit is contained in:
thibaud-lclr 2026-04-14 17:51:28 +02:00
parent 3d8a7dc84d
commit 0e5bfb2d39
3 changed files with 186 additions and 12 deletions

View file

@ -70,9 +70,13 @@ Exemple minimal :
```go
func main() {
err := bootstrap.Run(context.Background(), bootstrap.Options{
BinaryName: "my-mcp",
Description: "Client MCP",
Version: version,
BinaryName: "my-mcp",
Description: "Client MCP",
Version: version,
EnableDoctorAlias: true, // expose `doctor` comme alias de `config test`
AliasDescriptions: map[string]string{
"doctor": "Diagnostiquer la configuration locale.",
},
Hooks: bootstrap.Hooks{
Setup: func(ctx context.Context, inv bootstrap.Invocation) error {
return runSetup(ctx, inv.Args)
@ -102,6 +106,7 @@ automatiquement `Options.Version`.
Sans arguments, `bootstrap.Run` affiche l'aide globale (pas de lancement implicite de `mcp`).
La commande `config` impose une sous-commande (`show`, `test`, et optionnellement `delete`).
Les alias déclarés (ou `EnableDoctorAlias`) sont affichés dans l'aide globale.
## Manifeste `mcp.toml`

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io"
"os"
"sort"
"strings"
)
@ -15,6 +16,7 @@ const (
CommandConfig = "config"
CommandUpdate = "update"
CommandVersion = "version"
CommandDoctor = "doctor"
ConfigSubcommandShow = "show"
ConfigSubcommandTest = "test"
@ -44,15 +46,17 @@ type Hooks struct {
}
type Options struct {
BinaryName string
Description string
Version string
Aliases map[string][]string
Args []string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Hooks Hooks
BinaryName string
Description string
Version string
Aliases map[string][]string
AliasDescriptions map[string]string
EnableDoctorAlias bool
Args []string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
Hooks Hooks
}
type Invocation struct {
@ -165,9 +169,62 @@ func normalize(opts Options) Options {
if opts.Args == nil {
opts.Args = os.Args[1:]
}
opts.Aliases = normalizeAliases(opts.Aliases, opts.EnableDoctorAlias)
opts.AliasDescriptions = normalizeAliasDescriptions(opts.AliasDescriptions, opts.Aliases, opts.EnableDoctorAlias)
return opts
}
func normalizeAliases(aliases map[string][]string, enableDoctorAlias bool) map[string][]string {
normalized := make(map[string][]string, len(aliases)+1)
for name, target := range aliases {
trimmedName := strings.TrimSpace(name)
trimmedTarget := trimArgs(target)
if trimmedName == "" || len(trimmedTarget) == 0 {
continue
}
normalized[trimmedName] = trimmedTarget
}
if enableDoctorAlias {
if _, ok := normalized[CommandDoctor]; !ok {
normalized[CommandDoctor] = []string{CommandConfig, ConfigSubcommandTest}
}
}
if len(normalized) == 0 {
return nil
}
return normalized
}
func normalizeAliasDescriptions(descriptions map[string]string, aliases map[string][]string, enableDoctorAlias bool) map[string]string {
normalized := make(map[string]string, len(descriptions)+1)
for name, description := range descriptions {
trimmedName := strings.TrimSpace(name)
trimmedDescription := strings.TrimSpace(description)
if trimmedName == "" || trimmedDescription == "" {
continue
}
if _, ok := aliases[trimmedName]; !ok {
continue
}
normalized[trimmedName] = trimmedDescription
}
if enableDoctorAlias {
if _, ok := aliases[CommandDoctor]; ok {
if _, defined := normalized[CommandDoctor]; !defined {
normalized[CommandDoctor] = "Diagnostiquer la configuration locale."
}
}
}
if len(normalized) == 0 {
return nil
}
return normalized
}
func parseArgs(args []string) (command string, commandArgs []string, showHelp bool) {
args = trimArgs(args)
if len(args) == 0 {
@ -435,6 +492,34 @@ func printGlobalHelp(opts Options) error {
}
}
if len(opts.Aliases) > 0 {
if _, err := fmt.Fprintln(opts.Stdout, "\nAlias:"); err != nil {
return err
}
names := make([]string, 0, len(opts.Aliases))
for name := range opts.Aliases {
names = append(names, name)
}
sort.Strings(names)
for _, name := range names {
target := strings.Join(opts.Aliases[name], " ")
description := aliasDescription(opts.AliasDescriptions, name, target)
if _, err := fmt.Fprintf(opts.Stdout, " %-7s %s\n", name, description); err != nil {
return err
}
}
}
_, err := fmt.Fprintf(opts.Stdout, "\nAide detaillee: %s help <command>\n", opts.BinaryName)
return err
}
func aliasDescription(descriptions map[string]string, name, target string) string {
description := strings.TrimSpace(descriptions[name])
if description == "" {
return fmt.Sprintf("Alias de %q.", target)
}
return fmt.Sprintf("%s (alias de %q).", description, target)
}

View file

@ -398,3 +398,87 @@ func TestRunPrintsAliasHelpFromTrailingHelpFlag(t *testing.T) {
t.Fatalf("command help output = %q", text)
}
}
func TestRunPrintsAliasesInGlobalHelp(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
err := Run(context.Background(), Options{
BinaryName: "my-mcp",
Aliases: map[string][]string{
"doctor": {CommandConfig, ConfigSubcommandTest},
},
AliasDescriptions: map[string]string{
"doctor": "Diagnostiquer la configuration locale.",
},
Args: []string{"help"},
Stdout: &stdout,
Stderr: &stderr,
})
if err != nil {
t.Fatalf("Run error = %v", err)
}
text := stdout.String()
if !strings.Contains(text, "Alias:") {
t.Fatalf("global help output missing alias section: %q", text)
}
if !strings.Contains(text, `doctor Diagnostiquer la configuration locale. (alias de "config test").`) {
t.Fatalf("global help output missing doctor alias details: %q", text)
}
}
func TestRunRoutesDoctorAliasWhenEnabled(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
var got Invocation
err := Run(context.Background(), Options{
BinaryName: "my-mcp",
EnableDoctorAlias: true,
Args: []string{"doctor", "--profile", "prod"},
Stdout: &stdout,
Stderr: &stderr,
Hooks: Hooks{
ConfigTest: func(_ context.Context, inv Invocation) error {
got = inv
return nil
},
},
})
if err != nil {
t.Fatalf("Run error = %v", err)
}
if got.Command != "config test" {
t.Fatalf("invocation command = %q, want %q", got.Command, "config test")
}
wantArgs := []string{"--profile", "prod"}
if !slices.Equal(got.Args, wantArgs) {
t.Fatalf("invocation args = %v, want %v", got.Args, wantArgs)
}
}
func TestRunPrintsDoctorAliasInGlobalHelpWhenEnabled(t *testing.T) {
var stdout bytes.Buffer
var stderr bytes.Buffer
err := Run(context.Background(), Options{
BinaryName: "my-mcp",
EnableDoctorAlias: true,
Args: []string{"help"},
Stdout: &stdout,
Stderr: &stderr,
})
if err != nil {
t.Fatalf("Run error = %v", err)
}
text := stdout.String()
if !strings.Contains(text, "Alias:") {
t.Fatalf("global help output missing alias section: %q", text)
}
if !strings.Contains(text, `doctor Diagnostiquer la configuration locale. (alias de "config test").`) {
t.Fatalf("global help output missing default doctor alias details: %q", text)
}
}