diff --git a/cmd/src/mcp.go b/cmd/src/mcp.go new file mode 100644 index 0000000000..ec5683e4f7 --- /dev/null +++ b/cmd/src/mcp.go @@ -0,0 +1,43 @@ +package main + +import ( + "flag" + "fmt" + + "github.com/sourcegraph/src-cli/internal/mcp" +) + +func init() { + flagSet := flag.NewFlagSet("mcp", flag.ExitOnError) + commands = append(commands, &command{ + flagSet: flagSet, + handler: mcpMain, + }) +} +func mcpMain(args []string) error { + fmt.Println("NOTE: This command is still experimental") + tools, err := mcp.LoadToolDefinitions() + 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 *mcp.ToolDef, 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..576ca54540 100644 --- a/internal/mcp/mcp_parse.go +++ b/internal/mcp/mcp_parse.go @@ -4,13 +4,13 @@ package mcp import ( _ "embed" "encoding/json" - "fmt" + "strings" "github.com/sourcegraph/sourcegraph/lib/errors" ) //go:embed mcp_tools.json -var _ []byte +var mcpToolListJSON []byte type ToolDef struct { Name string `json:"name"` @@ -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"` @@ -85,7 +89,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), @@ -157,10 +162,16 @@ 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) } 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, "_", "-") +} diff --git a/internal/mcp/mcp_parse_test.go b/internal/mcp/mcp_parse_test.go index e29281e9a3..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) } @@ -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") }