diff --git a/README.md b/README.md index bdd32fc..9a57f07 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ Each tool supports various options for customizing output, such as including lin This codebase makes use of edited code from [gopls](https://go.googlesource.com/tools/+/refs/heads/master/gopls/internal/protocol) to handle LSP communication. See ATTRIBUTION for details. -[mcp-golang](https://github.com/metoro-io/mcp-golang) is used for MCP communication. +[mcp-go](https://github.com/mark3labs/mcp-go) is used for MCP communication. ## Prerequisites @@ -161,7 +161,6 @@ Include ``` env: { "LOG_LEVEL": "DEBUG", - "LOG_COMPONENT_LEVELS": "wire:DEBUG" } ``` @@ -180,4 +179,3 @@ The following features are on my radar: - [ ] Add LSP server configuration options and presets for common languages - [ ] Make a more consistent and scalable API for tools (pagination, etc.) - [ ] Create tools at a higher level of abstraction, combining diagnostics, code lens, hover, and code actions when reading definitions or references. - diff --git a/go.mod b/go.mod index f1f1356..53e0c09 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.24.0 require ( github.com/fsnotify/fsnotify v1.9.0 - github.com/metoro-io/mcp-golang v0.6.0 + github.com/mark3labs/mcp-go v0.21.1 github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 github.com/stretchr/testify v1.10.0 golang.org/x/text v0.24.0 @@ -14,22 +14,14 @@ replace github.com/metoro-io/mcp-golang => github.com/isaacphi/mcp-golang v0.0.0 require ( github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c // indirect - github.com/bahlo/generic-list-go v0.2.0 // indirect - github.com/buger/jsonparser v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/go-cmp v0.7.0 // indirect - github.com/invopop/jsonschema v0.13.0 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/kisielk/errcheck v1.9.0 // indirect github.com/kr/pretty v0.3.1 // indirect - github.com/mailru/easyjson v0.9.0 // indirect - github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/rogpeppe/go-internal v1.14.1 // indirect - github.com/tidwall/gjson v1.18.0 // indirect - github.com/tidwall/match v1.1.1 // indirect - github.com/tidwall/pretty v1.2.1 // indirect - github.com/tidwall/sjson v1.2.5 // indirect - github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac // indirect golang.org/x/mod v0.24.0 // indirect golang.org/x/sync v0.13.0 // indirect diff --git a/go.sum b/go.sum index 3cacb49..20661e7 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,5 @@ github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c h1:pxW6RcqyfI9/kWtOwnv/G+AzdKuy2ZrqINhenH4HyNs= github.com/BurntSushi/toml v1.4.1-0.20240526193622-a339e1f7089c/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= -github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= -github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= -github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= -github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -16,10 +12,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= -github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= -github.com/isaacphi/mcp-golang v0.0.0-20250314121746-948e874f9887 h1:mwj41iKcwcR67wBt/fszrEJtGwuGSmSHcjC6u4ZEHdE= -github.com/isaacphi/mcp-golang v0.0.0-20250314121746-948e874f9887/go.mod h1:ifLP9ZzKpN1UqFWNTpAHOqSvNkMK6b7d1FSZ5Lu0lN0= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/kisielk/errcheck v1.9.0 h1:9xt1zI9EBfcYBvdU1nVrzMzzUPUtPKs9bVSIM3TAb3M= github.com/kisielk/errcheck v1.9.0/go.mod h1:kQxWMMVZgIkDq7U8xtG/n2juOjbLgZtedi0D+/VL/i8= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -29,11 +23,9 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mark3labs/mcp-go v0.21.1 h1:7Ek6KPIIbMhEYHRiRIg6K6UAgNZCJaHKQp926MNr6V0= +github.com/mark3labs/mcp-go v0.21.1/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= @@ -45,18 +37,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY= -github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= -github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= -github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= -github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= -github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= -github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= -github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= -github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= -github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac h1:TSSpLIG4v+p0rPv1pNOQtl1I8knsO4S9trOxNMOLVP4= golang.org/x/exp/typeparams v0.0.0-20250210185358-939b2ce775ac/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= diff --git a/main.go b/main.go index 052bd06..f6f3ed5 100644 --- a/main.go +++ b/main.go @@ -14,8 +14,7 @@ import ( "github.com/isaacphi/mcp-language-server/internal/logging" "github.com/isaacphi/mcp-language-server/internal/lsp" "github.com/isaacphi/mcp-language-server/internal/watcher" - "github.com/metoro-io/mcp-golang" - "github.com/metoro-io/mcp-golang/transport/stdio" + "github.com/mark3labs/mcp-go/server" ) // Create a logger for the core component @@ -27,10 +26,10 @@ type config struct { lspArgs []string } -type server struct { +type mcpServer struct { config config lspClient *lsp.Client - mcpServer *mcp_golang.Server + mcpServer *server.MCPServer ctx context.Context cancelFunc context.CancelFunc workspaceWatcher *watcher.WorkspaceWatcher @@ -72,16 +71,16 @@ func parseConfig() (*config, error) { return cfg, nil } -func newServer(config *config) (*server, error) { +func newServer(config *config) (*mcpServer, error) { ctx, cancel := context.WithCancel(context.Background()) - return &server{ + return &mcpServer{ config: *config, ctx: ctx, cancelFunc: cancel, }, nil } -func (s *server) initializeLSP() error { +func (s *mcpServer) initializeLSP() error { if err := os.Chdir(s.config.workspaceDir); err != nil { return fmt.Errorf("failed to change to workspace directory: %v", err) } @@ -104,18 +103,24 @@ func (s *server) initializeLSP() error { return client.WaitForServerReady(s.ctx) } -func (s *server) start() error { +func (s *mcpServer) start() error { if err := s.initializeLSP(); err != nil { return err } - s.mcpServer = mcp_golang.NewServer(stdio.NewStdioServerTransport()) + s.mcpServer = server.NewMCPServer( + "MCP Language Server", + "v0.0.2", + server.WithLogging(), + server.WithRecovery(), + ) + err := s.registerTools() if err != nil { return fmt.Errorf("tool registration failed: %v", err) } - return s.mcpServer.Serve() + return server.ServeStdio(s.mcpServer) } func main() { @@ -185,7 +190,7 @@ func main() { os.Exit(0) } -func cleanup(s *server, done chan struct{}) { +func cleanup(s *mcpServer, done chan struct{}) { coreLogger.Info("Cleanup initiated for PID: %d", os.Getpid()) // Create a context with timeout for shutdown operations diff --git a/tools.go b/tools.go index f74e531..913a39b 100644 --- a/tools.go +++ b/tools.go @@ -1,10 +1,11 @@ package main import ( + "context" "fmt" "github.com/isaacphi/mcp-language-server/internal/tools" - "github.com/metoro-io/mcp-golang" + "github.com/mark3labs/mcp-go/mcp" ) type ReadDefinitionArgs struct { @@ -37,107 +38,245 @@ type ExecuteCodeLensArgs struct { Index int `json:"index" jsonschema:"required,description=The index of the code lens to execute (from get_codelens output), 1 indexed"` } -func (s *server) registerTools() error { +func (s *mcpServer) registerTools() error { coreLogger.Debug("Registering MCP tools") - err := s.mcpServer.RegisterTool( - "apply_text_edit", - "Apply multiple text edits to a file.", - func(args ApplyTextEditArgs) (*mcp_golang.ToolResponse, error) { - coreLogger.Debug("Executing apply_text_edit for file: %s", args.FilePath) - response, err := tools.ApplyTextEdits(s.ctx, s.lspClient, args.FilePath, args.Edits) - if err != nil { - coreLogger.Error("Failed to apply edits: %v", err) - return nil, fmt.Errorf("failed to apply edits: %v", err) - } - return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(response)), nil - }) - if err != nil { - return fmt.Errorf("failed to register tool: %v", err) - } - - err = s.mcpServer.RegisterTool( - "read_definition", - "Read the source code definition of a symbol (function, type, constant, etc.) from the codebase. Returns the complete implementation code where the symbol is defined.", - func(args ReadDefinitionArgs) (*mcp_golang.ToolResponse, error) { - coreLogger.Debug("Executing read_definition for symbol: %s", args.SymbolName) - text, err := tools.ReadDefinition(s.ctx, s.lspClient, args.SymbolName, args.ShowLineNumbers) - if err != nil { - coreLogger.Error("Failed to get definition: %v", err) - return nil, fmt.Errorf("failed to get definition: %v", err) + applyTextEditTool := mcp.NewTool("apply_text_edit", + mcp.WithDescription("Apply multiple text edits to a file."), + mcp.WithObject("edits", + mcp.Required(), + mcp.Description("List of edits to apply"), + ), + mcp.WithString("filePath", + mcp.Required(), + mcp.Description("Path to the file to edit"), + ), + ) + + s.mcpServer.AddTool(applyTextEditTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + filePath, ok := request.Params.Arguments["filePath"].(string) + if !ok { + return mcp.NewToolResultError("filePath must be a string"), nil + } + + // Extract edits array + editsArg, ok := request.Params.Arguments["edits"] + if !ok { + return mcp.NewToolResultError("edits is required"), nil + } + + // Type assert and convert the edits + editsArray, ok := editsArg.([]interface{}) + if !ok { + return mcp.NewToolResultError("edits must be an array"), nil + } + + var edits []tools.TextEdit + for _, editItem := range editsArray { + editMap, ok := editItem.(map[string]interface{}) + if !ok { + return mcp.NewToolResultError("each edit must be an object"), nil } - return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(text)), nil - }) - if err != nil { - return fmt.Errorf("failed to register tool: %v", err) - } - - err = s.mcpServer.RegisterTool( - "find_references", - "Find all usages and references of a symbol throughout the codebase. Returns a list of all files and locations where the symbol appears.", - func(args FindReferencesArgs) (*mcp_golang.ToolResponse, error) { - coreLogger.Debug("Executing find_references for symbol: %s", args.SymbolName) - text, err := tools.FindReferences(s.ctx, s.lspClient, args.SymbolName, args.ShowLineNumbers) - if err != nil { - coreLogger.Error("Failed to find references: %v", err) - return nil, fmt.Errorf("failed to find references: %v", err) + + startLine, ok := editMap["startLine"].(float64) + if !ok { + return mcp.NewToolResultError("startLine must be a number"), nil } - return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(text)), nil - }) - if err != nil { - return fmt.Errorf("failed to register tool: %v", err) - } - - err = s.mcpServer.RegisterTool( - "get_diagnostics", - "Get diagnostic information for a specific file from the language server.", - func(args GetDiagnosticsArgs) (*mcp_golang.ToolResponse, error) { - coreLogger.Debug("Executing get_diagnostics for file: %s", args.FilePath) - text, err := tools.GetDiagnosticsForFile(s.ctx, s.lspClient, args.FilePath, args.IncludeContext, args.ShowLineNumbers) - if err != nil { - coreLogger.Error("Failed to get diagnostics: %v", err) - return nil, fmt.Errorf("failed to get diagnostics: %v", err) + + endLine, ok := editMap["endLine"].(float64) + if !ok { + return mcp.NewToolResultError("endLine must be a number"), nil } - return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(text)), nil - }, + + newText, _ := editMap["newText"].(string) // newText can be empty + + edits = append(edits, tools.TextEdit{ + StartLine: int(startLine), + EndLine: int(endLine), + NewText: newText, + }) + } + + coreLogger.Debug("Executing apply_text_edit for file: %s", filePath) + response, err := tools.ApplyTextEdits(s.ctx, s.lspClient, filePath, edits) + if err != nil { + coreLogger.Error("Failed to apply edits: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("failed to apply edits: %v", err)), nil + } + return mcp.NewToolResultText(response), nil + }) + + readDefinitionTool := mcp.NewTool("read_definition", + mcp.WithDescription("Read the source code definition of a symbol (function, type, constant, etc.) from the codebase. Returns the complete implementation code where the symbol is defined."), + mcp.WithString("symbolName", + mcp.Required(), + mcp.Description("The name of the symbol whose definition you want to find (e.g. 'mypackage.MyFunction', 'MyType.MyMethod')"), + ), + mcp.WithBoolean("showLineNumbers", + mcp.Description("Include line numbers in the returned source code"), + mcp.DefaultBool(true), + ), ) - if err != nil { - return fmt.Errorf("failed to register tool: %v", err) - } - - err = s.mcpServer.RegisterTool( - "get_codelens", - "Get code lens hints for a given file from the language server.", - func(args GetCodeLensArgs) (*mcp_golang.ToolResponse, error) { - coreLogger.Debug("Executing get_codelens for file: %s", args.FilePath) - text, err := tools.GetCodeLens(s.ctx, s.lspClient, args.FilePath) - if err != nil { - coreLogger.Error("Failed to get code lens: %v", err) - return nil, fmt.Errorf("failed to get code lens: %v", err) - } - return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(text)), nil - }, + + s.mcpServer.AddTool(readDefinitionTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + symbolName, ok := request.Params.Arguments["symbolName"].(string) + if !ok { + return mcp.NewToolResultError("symbolName must be a string"), nil + } + + showLineNumbers := true // default value + if showLineNumbersArg, ok := request.Params.Arguments["showLineNumbers"].(bool); ok { + showLineNumbers = showLineNumbersArg + } + + coreLogger.Debug("Executing read_definition for symbol: %s", symbolName) + text, err := tools.ReadDefinition(s.ctx, s.lspClient, symbolName, showLineNumbers) + if err != nil { + coreLogger.Error("Failed to get definition: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("failed to get definition: %v", err)), nil + } + return mcp.NewToolResultText(text), nil + }) + + findReferencesTool := mcp.NewTool("find_references", + mcp.WithDescription("Find all usages and references of a symbol throughout the codebase. Returns a list of all files and locations where the symbol appears."), + mcp.WithString("symbolName", + mcp.Required(), + mcp.Description("The name of the symbol to search for (e.g. 'mypackage.MyFunction', 'MyType')"), + ), + mcp.WithBoolean("showLineNumbers", + mcp.Description("Include line numbers when showing where the symbol is used"), + mcp.DefaultBool(true), + ), ) - if err != nil { - return fmt.Errorf("failed to register tool: %v", err) - } - - err = s.mcpServer.RegisterTool( - "execute_codelens", - "Execute a code lens command for a given file and lens index.", - func(args ExecuteCodeLensArgs) (*mcp_golang.ToolResponse, error) { - coreLogger.Debug("Executing execute_codelens for file: %s index: %d", args.FilePath, args.Index) - text, err := tools.ExecuteCodeLens(s.ctx, s.lspClient, args.FilePath, args.Index) - if err != nil { - coreLogger.Error("Failed to execute code lens: %v", err) - return nil, fmt.Errorf("failed to execute code lens: %v", err) - } - return mcp_golang.NewToolResponse(mcp_golang.NewTextContent(text)), nil - }, + + s.mcpServer.AddTool(findReferencesTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + symbolName, ok := request.Params.Arguments["symbolName"].(string) + if !ok { + return mcp.NewToolResultError("symbolName must be a string"), nil + } + + showLineNumbers := true // default value + if showLineNumbersArg, ok := request.Params.Arguments["showLineNumbers"].(bool); ok { + showLineNumbers = showLineNumbersArg + } + + coreLogger.Debug("Executing find_references for symbol: %s", symbolName) + text, err := tools.FindReferences(s.ctx, s.lspClient, symbolName, showLineNumbers) + if err != nil { + coreLogger.Error("Failed to find references: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("failed to find references: %v", err)), nil + } + return mcp.NewToolResultText(text), nil + }) + + getDiagnosticsTool := mcp.NewTool("get_diagnostics", + mcp.WithDescription("Get diagnostic information for a specific file from the language server."), + mcp.WithString("filePath", + mcp.Required(), + mcp.Description("The path to the file to get diagnostics for"), + ), + mcp.WithBoolean("includeContext", + mcp.Description("Include additional context for each diagnostic. Prefer false."), + mcp.DefaultBool(false), + ), + mcp.WithBoolean("showLineNumbers", + mcp.Description("If true, adds line numbers to the output"), + mcp.DefaultBool(true), + ), ) - if err != nil { - return fmt.Errorf("failed to register tool: %v", err) - } + + s.mcpServer.AddTool(getDiagnosticsTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + filePath, ok := request.Params.Arguments["filePath"].(string) + if !ok { + return mcp.NewToolResultError("filePath must be a string"), nil + } + + includeContext := false // default value + if includeContextArg, ok := request.Params.Arguments["includeContext"].(bool); ok { + includeContext = includeContextArg + } + + showLineNumbers := true // default value + if showLineNumbersArg, ok := request.Params.Arguments["showLineNumbers"].(bool); ok { + showLineNumbers = showLineNumbersArg + } + + coreLogger.Debug("Executing get_diagnostics for file: %s", filePath) + text, err := tools.GetDiagnosticsForFile(s.ctx, s.lspClient, filePath, includeContext, showLineNumbers) + if err != nil { + coreLogger.Error("Failed to get diagnostics: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("failed to get diagnostics: %v", err)), nil + } + return mcp.NewToolResultText(text), nil + }) + + getCodeLensTool := mcp.NewTool("get_codelens", + mcp.WithDescription("Get code lens hints for a given file from the language server."), + mcp.WithString("filePath", + mcp.Required(), + mcp.Description("The path to the file to get code lens information for"), + ), + ) + + s.mcpServer.AddTool(getCodeLensTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + filePath, ok := request.Params.Arguments["filePath"].(string) + if !ok { + return mcp.NewToolResultError("filePath must be a string"), nil + } + + coreLogger.Debug("Executing get_codelens for file: %s", filePath) + text, err := tools.GetCodeLens(s.ctx, s.lspClient, filePath) + if err != nil { + coreLogger.Error("Failed to get code lens: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("failed to get code lens: %v", err)), nil + } + return mcp.NewToolResultText(text), nil + }) + + executeCodeLensTool := mcp.NewTool("execute_codelens", + mcp.WithDescription("Execute a code lens command for a given file and lens index."), + mcp.WithString("filePath", + mcp.Required(), + mcp.Description("The path to the file containing the code lens to execute"), + ), + mcp.WithNumber("index", + mcp.Required(), + mcp.Description("The index of the code lens to execute (from get_codelens output), 1 indexed"), + ), + ) + + s.mcpServer.AddTool(executeCodeLensTool, func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { + // Extract arguments + filePath, ok := request.Params.Arguments["filePath"].(string) + if !ok { + return mcp.NewToolResultError("filePath must be a string"), nil + } + + // Handle both float64 and int for index due to JSON parsing + var index int + switch v := request.Params.Arguments["index"].(type) { + case float64: + index = int(v) + case int: + index = v + default: + return mcp.NewToolResultError("index must be a number"), nil + } + + coreLogger.Debug("Executing execute_codelens for file: %s index: %d", filePath, index) + text, err := tools.ExecuteCodeLens(s.ctx, s.lspClient, filePath, index) + if err != nil { + coreLogger.Error("Failed to execute code lens: %v", err) + return mcp.NewToolResultError(fmt.Sprintf("failed to execute code lens: %v", err)), nil + } + return mcp.NewToolResultText(text), nil + }) coreLogger.Info("Successfully registered all MCP tools") return nil