diff --git a/cmd/docker-mcp/catalog/ls.go b/cmd/docker-mcp/catalog/ls.go index 685e6fe7..73f793a6 100644 --- a/cmd/docker-mcp/catalog/ls.go +++ b/cmd/docker-mcp/catalog/ls.go @@ -6,10 +6,13 @@ import ( "fmt" "time" + "github.com/docker/cli/cli/command" + + "github.com/docker/mcp-gateway/cmd/docker-mcp/hints" "github.com/docker/mcp-gateway/pkg/telemetry" ) -func Ls(ctx context.Context, format Format) error { +func Ls(ctx context.Context, dockerCli command.Cli, format Format) error { // Initialize telemetry telemetry.Init() @@ -32,13 +35,13 @@ func Ls(ctx context.Context, format Format) error { } fmt.Println(string(data)) } else { - humanPrintCatalog(*cfg) + humanPrintCatalog(dockerCli, *cfg) } return nil } -func humanPrintCatalog(cfg Config) { +func humanPrintCatalog(dockerCli command.Cli, cfg Config) { if len(cfg.Catalogs) == 0 { fmt.Println("No catalogs configured.") return @@ -47,4 +50,9 @@ func humanPrintCatalog(cfg Config) { for name, catalog := range cfg.Catalogs { fmt.Printf("%s: %s\n", name, catalog.DisplayName) } + if hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: To browse a catalog's servers, use ") + hints.TipCyanBoldItalic.Print("docker mcp catalog show ") + fmt.Println() + } } diff --git a/cmd/docker-mcp/catalog/show.go b/cmd/docker-mcp/catalog/show.go index 822ee975..0f5c273a 100644 --- a/cmd/docker-mcp/catalog/show.go +++ b/cmd/docker-mcp/catalog/show.go @@ -9,10 +9,12 @@ import ( "strings" "time" + "github.com/docker/cli/cli/command" "github.com/mikefarah/yq/v4/pkg/yqlib" "github.com/moby/term" "gopkg.in/yaml.v3" + "github.com/docker/mcp-gateway/cmd/docker-mcp/hints" "github.com/docker/mcp-gateway/pkg/yq" ) @@ -53,7 +55,7 @@ func SupportedFormats() string { return strings.Join(quoted, ", ") } -func Show(ctx context.Context, name string, format Format, mcpOAuthDcrEnabled bool) error { +func Show(ctx context.Context, dockerCli command.Cli, name string, format Format, mcpOAuthDcrEnabled bool) error { cfg, err := ReadConfigWithDefaultCatalog(ctx) if err != nil { return err @@ -153,6 +155,12 @@ func Show(ctx context.Context, name string, format Format, mcpOAuthDcrEnabled bo fmt.Printf(" %s\n", strings.Repeat("─", headerLineWidth)) fmt.Printf(" %d servers total\n", serverCount) fmt.Println() + if hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: To view server details, use ") + hints.TipCyanBoldItalic.Print("docker mcp server inspect ") + hints.TipCyan.Print(". To add servers, use ") + hints.TipCyanBoldItalic.Println("docker mcp server enable ") + } return nil } diff --git a/cmd/docker-mcp/client/connect.go b/cmd/docker-mcp/client/connect.go index 36f1390c..9eb918bc 100644 --- a/cmd/docker-mcp/client/connect.go +++ b/cmd/docker-mcp/client/connect.go @@ -3,9 +3,13 @@ package client import ( "context" "fmt" + + "github.com/docker/cli/cli/command" + + "github.com/docker/mcp-gateway/cmd/docker-mcp/hints" ) -func Connect(ctx context.Context, cwd string, config Config, vendor string, global, quiet bool) error { +func Connect(ctx context.Context, dockerCli command.Cli, cwd string, config Config, vendor string, global, quiet bool) error { if vendor == vendorCodex { if !global { return fmt.Errorf("codex only supports global configuration. Re-run with --global or -g") @@ -33,5 +37,11 @@ func Connect(ctx context.Context, cwd string, config Config, vendor string, glob return err } fmt.Printf("You might have to restart '%s'.\n", vendor) + if hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: Your client is now connected! Use ") + hints.TipCyanBoldItalic.Print("docker mcp tools ls") + hints.TipCyan.Println(" to see your available tools") + fmt.Println() + } return nil } diff --git a/cmd/docker-mcp/commands/catalog.go b/cmd/docker-mcp/commands/catalog.go index b550cfd8..eb3c20aa 100644 --- a/cmd/docker-mcp/commands/catalog.go +++ b/cmd/docker-mcp/commands/catalog.go @@ -23,7 +23,7 @@ func catalogCommand(dockerCli command.Cli) *cobra.Command { cmd.AddCommand(bootstrapCatalogCommand()) cmd.AddCommand(importCatalogCommand()) cmd.AddCommand(exportCatalogCommand()) - cmd.AddCommand(lsCatalogCommand()) + cmd.AddCommand(lsCatalogCommand(dockerCli)) cmd.AddCommand(rmCatalogCommand()) cmd.AddCommand(updateCatalogCommand(dockerCli)) cmd.AddCommand(showCatalogCommand(dockerCli)) @@ -86,7 +86,7 @@ cannot be exported as it is managed by Docker.`, } } -func lsCatalogCommand() *cobra.Command { +func lsCatalogCommand(dockerCli command.Cli) *cobra.Command { var opts struct { Format catalog.Format } @@ -101,7 +101,7 @@ func lsCatalogCommand() *cobra.Command { # List catalogs in JSON format docker mcp catalog ls --format=json`, RunE: func(cmd *cobra.Command, _ []string) error { - return catalog.Ls(cmd.Context(), opts.Format) + return catalog.Ls(cmd.Context(), dockerCli, opts.Format) }, } flags := cmd.Flags() @@ -165,7 +165,7 @@ If no name is provided, shows the Docker official catalog.`, } mcpOAuthDcrEnabled := isMcpOAuthDcrFeatureEnabled(dockerCli) - return catalog.Show(cmd.Context(), name, opts.Format, mcpOAuthDcrEnabled) + return catalog.Show(cmd.Context(), dockerCli, name, opts.Format, mcpOAuthDcrEnabled) }, } flags := cmd.Flags() diff --git a/cmd/docker-mcp/commands/client.go b/cmd/docker-mcp/commands/client.go index 27d540f7..5cdf07b4 100644 --- a/cmd/docker-mcp/commands/client.go +++ b/cmd/docker-mcp/commands/client.go @@ -5,21 +5,21 @@ import ( "fmt" "strings" - "github.com/spf13/pflag" - + "github.com/docker/cli/cli/command" "github.com/spf13/cobra" + "github.com/spf13/pflag" "github.com/docker/mcp-gateway/cmd/docker-mcp/client" ) -func clientCommand(cwd string) *cobra.Command { +func clientCommand(dockerCli command.Cli, cwd string) *cobra.Command { cfg := client.ReadConfig() cmd := &cobra.Command{ Use: fmt.Sprintf("client (Supported: %s)", strings.Join(client.GetSupportedMCPClients(*cfg), ", ")), Short: "Manage MCP clients", } cmd.AddCommand(listClientCommand(cwd, *cfg)) - cmd.AddCommand(connectClientCommand(cwd, *cfg)) + cmd.AddCommand(connectClientCommand(dockerCli, cwd, *cfg)) cmd.AddCommand(disconnectClientCommand(cwd, *cfg)) cmd.AddCommand(manualClientCommand()) return cmd @@ -44,7 +44,7 @@ func listClientCommand(cwd string, cfg client.Config) *cobra.Command { return cmd } -func connectClientCommand(cwd string, cfg client.Config) *cobra.Command { +func connectClientCommand(dockerCli command.Cli, cwd string, cfg client.Config) *cobra.Command { var opts struct { Global bool Quiet bool @@ -54,7 +54,7 @@ func connectClientCommand(cwd string, cfg client.Config) *cobra.Command { Short: fmt.Sprintf("Connect the Docker MCP Toolkit to a client. Supported clients: %s", strings.Join(client.GetSupportedMCPClients(cfg), " ")), Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return client.Connect(cmd.Context(), cwd, cfg, args[0], opts.Global, opts.Quiet) + return client.Connect(cmd.Context(), dockerCli, cwd, cfg, args[0], opts.Global, opts.Quiet) }, } flags := cmd.Flags() diff --git a/cmd/docker-mcp/commands/root.go b/cmd/docker-mcp/commands/root.go index 615a01e3..7ef4e53f 100644 --- a/cmd/docker-mcp/commands/root.go +++ b/cmd/docker-mcp/commands/root.go @@ -71,7 +71,7 @@ func Root(ctx context.Context, cwd string, dockerCli command.Cli) *cobra.Command dockerClient := docker.NewClient(dockerCli) cmd.AddCommand(catalogCommand(dockerCli)) - cmd.AddCommand(clientCommand(cwd)) + cmd.AddCommand(clientCommand(dockerCli, cwd)) cmd.AddCommand(configCommand(dockerClient)) cmd.AddCommand(featureCommand(dockerCli)) cmd.AddCommand(gatewayCommand(dockerClient, dockerCli)) @@ -80,7 +80,7 @@ func Root(ctx context.Context, cwd string, dockerCli command.Cli) *cobra.Command cmd.AddCommand(registryCommand()) cmd.AddCommand(secretCommand(dockerClient)) cmd.AddCommand(serverCommand(dockerClient, dockerCli)) - cmd.AddCommand(toolsCommand(dockerClient)) + cmd.AddCommand(toolsCommand(dockerClient, dockerCli)) cmd.AddCommand(versionCommand()) if os.Getenv("DOCKER_MCP_SHOW_HIDDEN") == "1" { diff --git a/cmd/docker-mcp/commands/server.go b/cmd/docker-mcp/commands/server.go index 34a04012..40b2d9f6 100644 --- a/cmd/docker-mcp/commands/server.go +++ b/cmd/docker-mcp/commands/server.go @@ -8,6 +8,7 @@ import ( "github.com/docker/cli/cli/command" "github.com/spf13/cobra" + "github.com/docker/mcp-gateway/cmd/docker-mcp/hints" "github.com/docker/mcp-gateway/cmd/docker-mcp/server" "github.com/docker/mcp-gateway/pkg/config" "github.com/docker/mcp-gateway/pkg/docker" @@ -42,6 +43,11 @@ func serverCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command { fmt.Fprintln(cmd.OutOrStdout(), "No server is enabled") } else { fmt.Fprintln(cmd.OutOrStdout(), strings.Join(list, ", ")) + if hints.Enabled(dockerCli) { + hints.TipCyan.Fprint(cmd.OutOrStdout(), "Tip: To use these servers, connect to a client (IE: claude/cursor) with ") + hints.TipCyanBoldItalic.Fprintln(cmd.OutOrStdout(), "docker mcp client connect ") + fmt.Fprintln(cmd.OutOrStdout(), "") + } } return nil @@ -57,7 +63,7 @@ func serverCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command { Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { mcpOAuthDcrEnabled := isMcpOAuthDcrFeatureEnabled(dockerCli) - return server.Enable(cmd.Context(), docker, args, mcpOAuthDcrEnabled) + return server.Enable(cmd.Context(), docker, dockerCli, args, mcpOAuthDcrEnabled) }, }) @@ -68,7 +74,7 @@ func serverCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command { Args: cobra.MinimumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { mcpOAuthDcrEnabled := isMcpOAuthDcrFeatureEnabled(dockerCli) - return server.Disable(cmd.Context(), docker, args, mcpOAuthDcrEnabled) + return server.Disable(cmd.Context(), docker, dockerCli, args, mcpOAuthDcrEnabled) }, }) diff --git a/cmd/docker-mcp/commands/tools.go b/cmd/docker-mcp/commands/tools.go index e4793667..bf44de6b 100644 --- a/cmd/docker-mcp/commands/tools.go +++ b/cmd/docker-mcp/commands/tools.go @@ -1,13 +1,14 @@ package commands import ( + "github.com/docker/cli/cli/command" "github.com/spf13/cobra" "github.com/docker/mcp-gateway/cmd/docker-mcp/tools" "github.com/docker/mcp-gateway/pkg/docker" ) -func toolsCommand(docker docker.Client) *cobra.Command { +func toolsCommand(docker docker.Client, dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "tools", Short: "Manage tools", @@ -30,7 +31,7 @@ func toolsCommand(docker docker.Client) *cobra.Command { Short: "List tools", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { - return tools.List(cmd.Context(), version, gatewayArgs, verbose, "list", "", format) + return tools.List(cmd.Context(), dockerCli, version, gatewayArgs, verbose, "list", "", format) }, }) @@ -39,7 +40,7 @@ func toolsCommand(docker docker.Client) *cobra.Command { Short: "Count tools", Args: cobra.NoArgs, RunE: func(cmd *cobra.Command, _ []string) error { - return tools.List(cmd.Context(), version, gatewayArgs, verbose, "count", "", format) + return tools.List(cmd.Context(), dockerCli, version, gatewayArgs, verbose, "count", "", format) }, }) @@ -48,7 +49,7 @@ func toolsCommand(docker docker.Client) *cobra.Command { Short: "Inspect a tool", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { - return tools.List(cmd.Context(), version, gatewayArgs, verbose, "inspect", args[0], format) + return tools.List(cmd.Context(), dockerCli, version, gatewayArgs, verbose, "inspect", args[0], format) }, }) cmd.AddCommand(&cobra.Command{ diff --git a/cmd/docker-mcp/hints/hints.go b/cmd/docker-mcp/hints/hints.go new file mode 100644 index 00000000..0e57766d --- /dev/null +++ b/cmd/docker-mcp/hints/hints.go @@ -0,0 +1,31 @@ +package hints + +import ( + "github.com/docker/cli/cli/command" + "github.com/fatih/color" +) + +func Enabled(dockerCli command.Cli) bool { + configFile := dockerCli.ConfigFile() + if configFile == nil || configFile.Plugins == nil { + return true + } + + pluginConfig, ok := configFile.Plugins["-x-cli-hints"] + if !ok { + return true + } + + enabledValue, exists := pluginConfig["enabled"] + if !exists { + return true + } + + return enabledValue == "true" +} + +var ( + TipCyan = color.New(color.FgCyan) + TipCyanBoldItalic = color.New(color.FgCyan, color.Bold, color.Italic) + TipGreen = color.New(color.FgGreen) +) diff --git a/cmd/docker-mcp/server/enable.go b/cmd/docker-mcp/server/enable.go index ca7a84ea..df5b4795 100644 --- a/cmd/docker-mcp/server/enable.go +++ b/cmd/docker-mcp/server/enable.go @@ -5,23 +5,25 @@ import ( "context" "fmt" + "github.com/docker/cli/cli/command" "gopkg.in/yaml.v3" + "github.com/docker/mcp-gateway/cmd/docker-mcp/hints" "github.com/docker/mcp-gateway/pkg/catalog" "github.com/docker/mcp-gateway/pkg/config" "github.com/docker/mcp-gateway/pkg/docker" "github.com/docker/mcp-gateway/pkg/oauth" ) -func Disable(ctx context.Context, docker docker.Client, serverNames []string, mcpOAuthDcrEnabled bool) error { - return update(ctx, docker, nil, serverNames, mcpOAuthDcrEnabled) +func Disable(ctx context.Context, docker docker.Client, dockerCli command.Cli, serverNames []string, mcpOAuthDcrEnabled bool) error { + return update(ctx, docker, dockerCli, nil, serverNames, mcpOAuthDcrEnabled) } -func Enable(ctx context.Context, docker docker.Client, serverNames []string, mcpOAuthDcrEnabled bool) error { - return update(ctx, docker, serverNames, nil, mcpOAuthDcrEnabled) +func Enable(ctx context.Context, docker docker.Client, dockerCli command.Cli, serverNames []string, mcpOAuthDcrEnabled bool) error { + return update(ctx, docker, dockerCli, serverNames, nil, mcpOAuthDcrEnabled) } -func update(ctx context.Context, docker docker.Client, add []string, remove []string, mcpOAuthDcrEnabled bool) error { +func update(ctx context.Context, docker docker.Client, dockerCli command.Cli, add []string, remove []string, mcpOAuthDcrEnabled bool) error { // Read registry.yaml that contains which servers are enabled. registryYAML, err := config.ReadRegistry(ctx, docker) if err != nil { @@ -94,5 +96,21 @@ func update(ctx context.Context, docker docker.Client, add []string, remove []st return fmt.Errorf("writing registry config: %w", err) } + if len(add) > 0 && hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: ") + hints.TipGreen.Print("✓") + hints.TipCyan.Print(" Server enabled. To view all enabled servers, use ") + hints.TipCyanBoldItalic.Println("docker mcp server ls") + fmt.Println() + } + + if len(remove) > 0 && hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: ") + hints.TipGreen.Print("✓") + hints.TipCyan.Print(" Server disabled. To see remaining enabled servers, use ") + hints.TipCyanBoldItalic.Println("docker mcp server ls") + fmt.Println() + } + return nil } diff --git a/cmd/docker-mcp/server/server_test.go b/cmd/docker-mcp/server/server_test.go index 6447fa36..7dd9aae3 100644 --- a/cmd/docker-mcp/server/server_test.go +++ b/cmd/docker-mcp/server/server_test.go @@ -10,6 +10,8 @@ import ( "strconv" "testing" + "github.com/docker/cli/cli/command" + "github.com/docker/cli/cli/config/configfile" "github.com/docker/docker/api/types/volume" "github.com/docker/docker/errdefs" "github.com/stretchr/testify/assert" @@ -20,7 +22,7 @@ import ( ) func TestListVolumeNotFound(t *testing.T) { - ctx, home, docker := setup(t, withoutPromptsVolume()) + ctx, home, docker := setupForList(t, withoutPromptsVolume()) enabled, err := List(ctx, docker) require.NoError(t, err) @@ -30,7 +32,7 @@ func TestListVolumeNotFound(t *testing.T) { } func TestListEmptyVolume(t *testing.T) { - ctx, home, docker := setup(t, withEmptyPromptsVolume()) + ctx, home, docker := setupForList(t, withEmptyPromptsVolume()) enabled, err := List(ctx, docker) require.NoError(t, err) @@ -40,7 +42,7 @@ func TestListEmptyVolume(t *testing.T) { } func TestListImportVolume(t *testing.T) { - ctx, home, docker := setup(t, withRegistryYamlInPromptsVolume("registry:\n github-official:\n ref: \"\"")) + ctx, home, docker := setupForList(t, withRegistryYamlInPromptsVolume("registry:\n github-official:\n ref: \"\"")) enabled, err := List(ctx, docker) require.NoError(t, err) @@ -50,7 +52,7 @@ func TestListImportVolume(t *testing.T) { } func TestListEmpty(t *testing.T) { - ctx, _, docker := setup(t, withEmptyRegistryYaml()) + ctx, _, docker := setupForList(t, withEmptyRegistryYaml()) enabled, err := List(ctx, docker) require.NoError(t, err) @@ -58,7 +60,7 @@ func TestListEmpty(t *testing.T) { } func TestList(t *testing.T) { - ctx, _, docker := setup(t, withRegistryYaml("registry:\n git:\n ref: \"\"")) + ctx, _, docker := setupForList(t, withRegistryYaml("registry:\n git:\n ref: \"\"")) enabled, err := List(ctx, docker) require.NoError(t, err) @@ -66,16 +68,16 @@ func TestList(t *testing.T) { } func TestEnableNotFound(t *testing.T) { - ctx, _, docker := setup(t, withEmptyRegistryYaml(), withEmptyCatalog()) + ctx, _, docker, dockerCli := setup(t, withEmptyRegistryYaml(), withEmptyCatalog()) - err := Enable(ctx, docker, []string{"duckduckgo"}, false) + err := Enable(ctx, docker, dockerCli, []string{"duckduckgo"}, false) require.ErrorContains(t, err, "server duckduckgo not found in catalog") } func TestEnable(t *testing.T) { - ctx, _, docker := setup(t, withEmptyRegistryYaml(), withCatalog("registry:\n duckduckgo:\n")) + ctx, _, docker, dockerCli := setup(t, withEmptyRegistryYaml(), withCatalog("registry:\n duckduckgo:\n")) - err := Enable(ctx, docker, []string{"duckduckgo"}, false) + err := Enable(ctx, docker, dockerCli, []string{"duckduckgo"}, false) require.NoError(t, err) enabled, err := List(ctx, docker) @@ -84,9 +86,9 @@ func TestEnable(t *testing.T) { } func TestDisable(t *testing.T) { - ctx, _, docker := setup(t, withRegistryYaml("registry:\n duckduckgo:\n ref: \"\"\n git:\n ref: \"\""), withCatalog("registry:\n git:\n duckduckgo:\n")) + ctx, _, docker, dockerCli := setup(t, withRegistryYaml("registry:\n duckduckgo:\n ref: \"\"\n git:\n ref: \"\""), withCatalog("registry:\n git:\n duckduckgo:\n")) - err := Disable(ctx, docker, []string{"duckduckgo"}, false) + err := Disable(ctx, docker, dockerCli, []string{"duckduckgo"}, false) require.NoError(t, err) enabled, err := List(ctx, docker) @@ -95,9 +97,9 @@ func TestDisable(t *testing.T) { } func TestDisableUnknown(t *testing.T) { - ctx, _, docker := setup(t, withRegistryYaml("registry:\n duckduckgo:\n ref: \"\""), withCatalog("registry:\n duckduckgo:\n")) + ctx, _, docker, dockerCli := setup(t, withRegistryYaml("registry:\n duckduckgo:\n ref: \"\""), withCatalog("registry:\n duckduckgo:\n")) - err := Disable(ctx, docker, []string{"unknown"}, false) + err := Disable(ctx, docker, dockerCli, []string{"unknown"}, false) require.NoError(t, err) enabled, err := List(ctx, docker) @@ -106,9 +108,9 @@ func TestDisableUnknown(t *testing.T) { } func TestRemoveOutdatedServerOnEnable(t *testing.T) { - ctx, _, docker := setup(t, withRegistryYaml("registry:\n outdated:\n ref: \"\""), withCatalog("registry:\n git:\n")) + ctx, _, docker, dockerCli := setup(t, withRegistryYaml("registry:\n outdated:\n ref: \"\""), withCatalog("registry:\n git:\n")) - err := Enable(ctx, docker, []string{"git"}, false) + err := Enable(ctx, docker, dockerCli, []string{"git"}, false) require.NoError(t, err) enabled, err := List(ctx, docker) @@ -117,9 +119,9 @@ func TestRemoveOutdatedServerOnEnable(t *testing.T) { } func TestRemoveOutdatedServerOnDisable(t *testing.T) { - ctx, _, docker := setup(t, withRegistryYaml("registry:\n outdated:\n ref: \"\""), withEmptyCatalog()) + ctx, _, docker, dockerCli := setup(t, withRegistryYaml("registry:\n outdated:\n ref: \"\""), withEmptyCatalog()) - err := Disable(ctx, docker, []string{"git"}, false) + err := Disable(ctx, docker, dockerCli, []string{"git"}, false) require.NoError(t, err) enabled, err := List(ctx, docker) @@ -129,7 +131,7 @@ func TestRemoveOutdatedServerOnDisable(t *testing.T) { // Fixtures -func setup(t *testing.T, options ...option) (context.Context, string, docker.Client) { +func setup(t *testing.T, options ...option) (context.Context, string, docker.Client, command.Cli) { t.Helper() // Mock for Docker API @@ -147,7 +149,13 @@ func setup(t *testing.T, options ...option) (context.Context, string, docker.Cli o(t, home, docker) } - return t.Context(), home, docker + return t.Context(), home, docker, &fakeCli{} +} + +func setupForList(t *testing.T, options ...option) (context.Context, string, docker.Client) { + t.Helper() + ctx, home, docker, _ := setup(t, options...) + return ctx, home, docker } func writeFile(t *testing.T, path string, content []byte) { @@ -168,6 +176,14 @@ func (f *fakeDocker) InspectVolume(context.Context, string) (volume.Volume, erro return f.volume, f.inspectErr } +type fakeCli struct { + command.Cli +} + +func (fakeCli) ConfigFile() *configfile.ConfigFile { + return &configfile.ConfigFile{} +} + type exitCodeErr struct { exitCode int } diff --git a/cmd/docker-mcp/tools/list.go b/cmd/docker-mcp/tools/list.go index d6fb4de4..0e393050 100644 --- a/cmd/docker-mcp/tools/list.go +++ b/cmd/docker-mcp/tools/list.go @@ -6,13 +6,16 @@ import ( "fmt" "strings" + "github.com/docker/cli/cli/command" "github.com/modelcontextprotocol/go-sdk/mcp" "go.opentelemetry.io/otel" "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/metric" + + "github.com/docker/mcp-gateway/cmd/docker-mcp/hints" ) -func List(ctx context.Context, version string, gatewayArgs []string, debug bool, show, tool, format string) error { +func List(ctx context.Context, dockerCli command.Cli, version string, gatewayArgs []string, debug bool, show, tool, format string) error { // Initialize telemetry for CLI operations meter := otel.GetMeterProvider().Meter("github.com/docker/mcp-gateway") toolsDiscoveredGauge, _ := meter.Int64Gauge("mcp.cli.tools.discovered", @@ -50,12 +53,24 @@ func List(ctx context.Context, version string, gatewayArgs []string, debug bool, for _, tool := range response.Tools { fmt.Println(" -", tool.Name, "-", toolDescription(tool)) } + if hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: For tool details, use ") + hints.TipCyanBoldItalic.Print("docker mcp tools inspect ") + hints.TipCyan.Print(". To test the tool, use ") + hints.TipCyanBoldItalic.Println("docker mcp tools call ") + fmt.Println() + } } case "count": if format == "json" { fmt.Printf("{\"count\": %d}\n", len(response.Tools)) } else { fmt.Println(len(response.Tools), "tools") + if hints.Enabled(dockerCli) { + hints.TipCyan.Print("Tip: To see all available tools, use ") + hints.TipCyanBoldItalic.Println("docker mcp tools ls") + fmt.Println() + } } case "inspect": var found *mcp.Tool