From e59188aea6131743d80868c3499e77802d6bff2d Mon Sep 17 00:00:00 2001 From: Jon Gallant <2163001+jongio@users.noreply.github.com> Date: Fri, 24 Jul 2026 14:28:28 -0700 Subject: [PATCH] mcp: filter tool inventory Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 340b4dd3-b4b3-4f1a-9163-66b16d96fa81 --- cmd/mcp.go | 22 ++++++++++++++++- cmd/mcp_test.go | 65 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 cmd/mcp_test.go diff --git a/cmd/mcp.go b/cmd/mcp.go index 2ddd26ef..dfe912b0 100644 --- a/cmd/mcp.go +++ b/cmd/mcp.go @@ -6,6 +6,7 @@ import ( "fmt" "os" "os/signal" + "strings" "github.com/jongio/grut/internal/config" "github.com/jongio/grut/internal/git" @@ -76,12 +77,13 @@ const mcpToolsUse = "tools" func newMCPToolsCmd() *cobra.Command { var jsonOut bool + var filter string cmd := &cobra.Command{ Use: mcpToolsUse, Short: "List grut MCP tools", RunE: func(cmd *cobra.Command, args []string) error { - tools := grut_mcp.ToolInventory() + tools := filterMCPTools(grut_mcp.ToolInventory(), filter) w := cmd.OutOrStdout() if jsonOut { enc := json.NewEncoder(w) @@ -96,5 +98,23 @@ func newMCPToolsCmd() *cobra.Command { }, } cmd.Flags().BoolVar(&jsonOut, "json", false, "Print tools as JSON") + cmd.Flags().StringVar(&filter, "filter", "", "Only show tools matching this text") return cmd } + +func filterMCPTools(tools []grut_mcp.ToolInfo, filter string) []grut_mcp.ToolInfo { + query := strings.TrimSpace(strings.ToLower(filter)) + if query == "" { + return tools + } + + out := []grut_mcp.ToolInfo{} + for _, tool := range tools { + if strings.Contains(strings.ToLower(tool.Name), query) || + strings.Contains(strings.ToLower(tool.Category), query) || + strings.Contains(strings.ToLower(tool.Description), query) { + out = append(out, tool) + } + } + return out +} diff --git a/cmd/mcp_test.go b/cmd/mcp_test.go new file mode 100644 index 00000000..ca985d9b --- /dev/null +++ b/cmd/mcp_test.go @@ -0,0 +1,65 @@ +package cmd + +import ( + "bytes" + "encoding/json" + "strings" + "testing" + + grut_mcp "github.com/jongio/grut/internal/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestMCPToolsCommandFiltersTextOutput(t *testing.T) { + cmd := newMCPToolsCmd() + cmd.SetArgs([]string{"--filter", "read"}) + var out bytes.Buffer + cmd.SetOut(&out) + + err := cmd.Execute() + + require.NoError(t, err) + assert.Contains(t, out.String(), "file_read") + assert.NotContains(t, out.String(), "file_write") +} + +func TestMCPToolsCommandFiltersJSONOutput(t *testing.T) { + cmd := newMCPToolsCmd() + cmd.SetArgs([]string{"--filter", "write", "--json"}) + var out bytes.Buffer + cmd.SetOut(&out) + + err := cmd.Execute() + + require.NoError(t, err) + var tools []grut_mcp.ToolInfo + require.NoError(t, json.Unmarshal(out.Bytes(), &tools)) + require.NotEmpty(t, tools) + for _, tool := range tools { + haystack := strings.ToLower(tool.Name + tool.Category + tool.Description) + assert.Contains(t, haystack, "write") + } +} + +func TestMCPToolsCommandFilterNoMatches(t *testing.T) { + cmd := newMCPToolsCmd() + cmd.SetArgs([]string{"--filter", "no-such-tool", "--json"}) + var out bytes.Buffer + cmd.SetOut(&out) + + err := cmd.Execute() + + require.NoError(t, err) + assert.Equal(t, "[]\n", out.String()) +} + +func TestRootRegistersMCPToolsCommand(t *testing.T) { + root, cleanup := newRootCommand() + defer cleanup() + + toolsCmd, _, err := root.Find([]string{"mcp", mcpToolsUse}) + require.NoError(t, err) + require.NotNil(t, toolsCmd) + assert.Equal(t, mcpToolsUse, toolsCmd.Name()) +}