From 560e8e3713f86b438ddacbb59681f22df20eefcf Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Thu, 27 Nov 2025 11:23:30 +0200 Subject: [PATCH 1/8] add mcp list-tools to list available tool calls --- cmd/src/mcp.go | 45 +++++++++++++++++++++++++++++++++++++++ internal/mcp/mcp_parse.go | 10 ++++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 cmd/src/mcp.go diff --git a/cmd/src/mcp.go b/cmd/src/mcp.go new file mode 100644 index 0000000000..cd96dd69cd --- /dev/null +++ b/cmd/src/mcp.go @@ -0,0 +1,45 @@ +package main + +import ( + "flag" + "fmt" +) + +func init() { + flagSet := flag.NewFlagSet("mcp", flag.ExitOnError) + handler := func(args []string) error { + return mcpMain(args) + } + + commands = append(commands, &command{ + flagSet: flagSet, + handler: handler, + }) +} +func mcpMain(args []string) error { + fmt.Println("NOTE: This command is still experimental") + tools, err := LoadMCPToolDefinitions(mcpToolListJSON) + if err != nil { + return err + } + + subcmd := args[0] + if subcmd == "list-tools" { + fmt.Println("Available tools") + for name := range tools { + fmt.Printf("- %s\n", name) + } + return nil + } + + tool, ok := tools[subcmd] + if !ok { + return fmt.Errorf("tool definition for %q not found - run src mcp list-tools to see a list of available tools", subcmd) + } + return handleMcpTool(tool, args[1:]) +} + +func handleMcpTool(tool *MCPToolDef, args []string) error { + fmt.Printf("handling tool %q args: %+v", tool.Name, args) + return nil +} diff --git a/internal/mcp/mcp_parse.go b/internal/mcp/mcp_parse.go index b5fb843804..b6e2250782 100644 --- a/internal/mcp/mcp_parse.go +++ b/internal/mcp/mcp_parse.go @@ -5,6 +5,7 @@ import ( _ "embed" "encoding/json" "fmt" + "strings" "github.com/sourcegraph/sourcegraph/lib/errors" ) @@ -85,7 +86,8 @@ func LoadToolDefinitions(data []byte) (map[string]*ToolDef, error) { parser := &parser{} for _, t := range defs.Tools { - tools[t.Name] = &ToolDef{ + name := normalizeToolName(t.Name) + tools[name] = &ToolDef{ Name: t.Name, Description: t.Description, InputSchema: parser.parseRootSchema(t.InputSchema), @@ -164,3 +166,9 @@ func (p *parser) parseProperties(props map[string]json.RawMessage) map[string]Sc } return res } + +// normalizeToolName takes mcp tool names like 'sg_keyword_search' and normalizes it to 'keyword-search" +func normalizeToolName(toolName string) string { + toolName, _ = strings.CutPrefix(toolName, "sg_") + return strings.ReplaceAll(toolName, "_", "-") +} From ef57b58a8bce7f8e5d573e4491af897c8b2b808c Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Fri, 28 Nov 2025 14:40:55 +0200 Subject: [PATCH 2/8] set variable name for embedded json --- internal/mcp/mcp_parse.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/mcp/mcp_parse.go b/internal/mcp/mcp_parse.go index b6e2250782..2999fcb3a0 100644 --- a/internal/mcp/mcp_parse.go +++ b/internal/mcp/mcp_parse.go @@ -11,7 +11,7 @@ import ( ) //go:embed mcp_tools.json -var _ []byte +var mcpToolListJSON []byte type ToolDef struct { Name string `json:"name"` From e027f838c60fe5ee390739e37d289ae24fe781b3 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Fri, 28 Nov 2025 14:42:54 +0200 Subject: [PATCH 3/8] fix usage of fmt.Errorf --- internal/mcp/mcp_parse.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/internal/mcp/mcp_parse.go b/internal/mcp/mcp_parse.go index 2999fcb3a0..e535e339ef 100644 --- a/internal/mcp/mcp_parse.go +++ b/internal/mcp/mcp_parse.go @@ -4,7 +4,6 @@ package mcp import ( _ "embed" "encoding/json" - "fmt" "strings" "github.com/sourcegraph/sourcegraph/lib/errors" @@ -159,7 +158,7 @@ func (p *parser) parseProperties(props map[string]json.RawMessage) map[string]Sc for name, raw := range props { var r RawSchema if err := json.Unmarshal(raw, &r); err != nil { - p.errors = append(p.errors, fmt.Errorf("failed to parse property %q: %w", name, err)) + p.errors = append(p.errors, errors.Newf("failed to parse property %q: %w", name, err)) continue } res[name] = p.parseSchema(&r) From e6ad31be5417701a95af6ff7a31c8f96a7553526 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Fri, 28 Nov 2025 14:46:42 +0200 Subject: [PATCH 4/8] fix test --- internal/mcp/mcp_parse_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/mcp/mcp_parse_test.go b/internal/mcp/mcp_parse_test.go index e29281e9a3..7ba98040c3 100644 --- a/internal/mcp/mcp_parse_test.go +++ b/internal/mcp/mcp_parse_test.go @@ -46,7 +46,8 @@ func TestLoadToolDefinitions(t *testing.T) { t.Fatalf("Expected 1 tool, got %d", len(tools)) } - tool := tools["test_tool"] + // Temporary: map keys have normalized names + tool := tools["test-tool"] if tool == nil { t.Fatal("Tool 'test_tool' not found") } From e128ffc0c1ef72af8a9f5cd9c2ad38eaf0a39006 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Fri, 28 Nov 2025 14:58:00 +0200 Subject: [PATCH 5/8] fix lint error --- cmd/src/mcp.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cmd/src/mcp.go b/cmd/src/mcp.go index cd96dd69cd..7c5c9cf35b 100644 --- a/cmd/src/mcp.go +++ b/cmd/src/mcp.go @@ -7,13 +7,9 @@ import ( func init() { flagSet := flag.NewFlagSet("mcp", flag.ExitOnError) - handler := func(args []string) error { - return mcpMain(args) - } - commands = append(commands, &command{ flagSet: flagSet, - handler: handler, + handler: mcpMain, }) } func mcpMain(args []string) error { From be474672241cad58e4f483c5afef1d99330b82c2 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Tue, 2 Dec 2025 11:38:55 +0200 Subject: [PATCH 6/8] make default LoadToolDefinitions and unexported loadToolDefinitions --- internal/mcp/mcp_parse.go | 6 +++++- internal/mcp/mcp_parse_test.go | 2 +- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/internal/mcp/mcp_parse.go b/internal/mcp/mcp_parse.go index e535e339ef..576ca54540 100644 --- a/internal/mcp/mcp_parse.go +++ b/internal/mcp/mcp_parse.go @@ -67,7 +67,11 @@ type parser struct { errors []error } -func LoadToolDefinitions(data []byte) (map[string]*ToolDef, error) { +func LoadToolDefinitions() (map[string]*ToolDef, error) { + return loadToolDefinitions(mcpToolListJSON) +} + +func loadToolDefinitions(data []byte) (map[string]*ToolDef, error) { defs := struct { Tools []struct { Name string `json:"name"` diff --git a/internal/mcp/mcp_parse_test.go b/internal/mcp/mcp_parse_test.go index 7ba98040c3..13adcadc03 100644 --- a/internal/mcp/mcp_parse_test.go +++ b/internal/mcp/mcp_parse_test.go @@ -37,7 +37,7 @@ func TestLoadToolDefinitions(t *testing.T) { ] }`) - tools, err := LoadToolDefinitions(toolJSON) + tools, err := loadToolDefinitions(toolJSON) if err != nil { t.Fatalf("Failed to load tool definitions: %v", err) } From 2414ed5967bd55ec4725fff8e4b81be61e12ddf0 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Tue, 2 Dec 2025 11:39:27 +0200 Subject: [PATCH 7/8] use internal/mcp --- cmd/src/mcp.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/src/mcp.go b/cmd/src/mcp.go index 7c5c9cf35b..8d35369a2b 100644 --- a/cmd/src/mcp.go +++ b/cmd/src/mcp.go @@ -3,6 +3,8 @@ package main import ( "flag" "fmt" + + "github.com/sourcegraph/src-cli/internal/mcp" ) func init() { @@ -14,7 +16,7 @@ func init() { } func mcpMain(args []string) error { fmt.Println("NOTE: This command is still experimental") - tools, err := LoadMCPToolDefinitions(mcpToolListJSON) + tools, err := mcp.LoadToolDefinitions() if err != nil { return err } @@ -35,7 +37,7 @@ func mcpMain(args []string) error { return handleMcpTool(tool, args[1:]) } -func handleMcpTool(tool *MCPToolDef, args []string) error { +func handleMcpTool(tool *mcp.MCPToolDef, args []string) error { fmt.Printf("handling tool %q args: %+v", tool.Name, args) return nil } From 824ee139277fd7e96456d54eae62dd3eb9f20e04 Mon Sep 17 00:00:00 2001 From: William Bezuidenhout Date: Tue, 2 Dec 2025 12:13:49 +0200 Subject: [PATCH 8/8] fixup after refactor --- cmd/src/mcp.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/src/mcp.go b/cmd/src/mcp.go index 8d35369a2b..ec5683e4f7 100644 --- a/cmd/src/mcp.go +++ b/cmd/src/mcp.go @@ -37,7 +37,7 @@ func mcpMain(args []string) error { return handleMcpTool(tool, args[1:]) } -func handleMcpTool(tool *mcp.MCPToolDef, args []string) error { +func handleMcpTool(tool *mcp.ToolDef, args []string) error { fmt.Printf("handling tool %q args: %+v", tool.Name, args) return nil }