diff --git a/README.md b/README.md index 7c70ce3..ade2120 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,7 @@ | Tool | Description | |------|-------------| | `document_active` | Get the active document | +| `document_cleanup` | Run code cleanup on a document | | `document_close` | Close a document | | `document_list` | List all open documents | | `document_open` | Open a file in the editor | diff --git a/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs b/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs index a7602ff..d4465c2 100644 --- a/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs +++ b/src/CodingWithCalvin.MCPServer.Server/RpcClient.cs @@ -112,6 +112,7 @@ public Task ShutdownAsync() public Task OpenDocumentAsync(string path) => Proxy.OpenDocumentAsync(path); public Task CloseDocumentAsync(string path, bool save) => Proxy.CloseDocumentAsync(path, save); public Task SaveDocumentAsync(string path) => Proxy.SaveDocumentAsync(path); + public Task RunCodeCleanupAsync(string path) => Proxy.RunCodeCleanupAsync(path); public Task ReadDocumentAsync(string path) => Proxy.ReadDocumentAsync(path); public Task WriteDocumentAsync(string path, string content) => Proxy.WriteDocumentAsync(path, content); public Task GetSelectionAsync() => Proxy.GetSelectionAsync(); diff --git a/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs b/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs index 12b0b27..f0f166a 100644 --- a/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs +++ b/src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs @@ -72,6 +72,15 @@ public async Task SaveDocumentAsync( return success ? $"Saved: {path}" : $"Document not found or failed to save: {path}"; } + [McpServerTool(Name = "document_cleanup", Destructive = true, Idempotent = true)] + [Description("Run code cleanup on an open document in Visual Studio. The document must already be open in VS. Activates the document and executes the 'Edit.RunCodeCleanupOnFile' command.")] + public async Task RunCodeCleanupAsync( + [Description("The full absolute path to the document. Must be open in VS. Get the path from document_list. Supports forward slashes (/) or backslashes (\\).")] string path) + { + var success = await _rpcClient.RunCodeCleanupAsync(path); + return success ? $"Code cleanup completed: {path}" : $"Document not found or code cleanup failed: {path}"; + } + [McpServerTool(Name = "document_read", ReadOnly = true)] [Description("Read the contents of a document. If the document is open in VS, reads the current editor buffer (including unsaved changes); otherwise reads from disk.")] public async Task ReadDocumentAsync( diff --git a/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs b/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs index 317a3b4..fb63bd8 100644 --- a/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs +++ b/src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs @@ -20,6 +20,7 @@ public interface IVisualStudioRpc Task OpenDocumentAsync(string path); Task CloseDocumentAsync(string path, bool save); Task SaveDocumentAsync(string path); + Task RunCodeCleanupAsync(string path); Task ReadDocumentAsync(string path); Task WriteDocumentAsync(string path, string content); Task GetSelectionAsync(); diff --git a/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs b/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs index 548f4dd..75926ce 100644 --- a/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs +++ b/src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs @@ -16,6 +16,7 @@ public interface IVisualStudioService Task OpenDocumentAsync(string path); Task CloseDocumentAsync(string path, bool save = true); Task SaveDocumentAsync(string path); + Task RunCodeCleanupAsync(string path); Task ReadDocumentAsync(string path); Task WriteDocumentAsync(string path, string content); Task GetSelectionAsync(); diff --git a/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs b/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs index 14d20be..4ca2877 100644 --- a/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs +++ b/src/CodingWithCalvin.MCPServer/Services/RpcServer.cs @@ -170,6 +170,7 @@ public async Task RequestShutdownAsync() public Task OpenDocumentAsync(string path) => _vsService.OpenDocumentAsync(path); public Task CloseDocumentAsync(string path, bool save) => _vsService.CloseDocumentAsync(path, save); public Task SaveDocumentAsync(string path) => _vsService.SaveDocumentAsync(path); + public Task RunCodeCleanupAsync(string path) => _vsService.RunCodeCleanupAsync(path); public Task ReadDocumentAsync(string path) => _vsService.ReadDocumentAsync(path); public Task WriteDocumentAsync(string path, string content) => _vsService.WriteDocumentAsync(path, content); public Task GetSelectionAsync() => _vsService.GetSelectionAsync(); diff --git a/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs b/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs index 002a2ed..e8d94d3 100644 --- a/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs +++ b/src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs @@ -260,6 +260,31 @@ public async Task SaveDocumentAsync(string path) return false; } + public async Task RunCodeCleanupAsync(string path) + { + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var dte = await GetDteAsync(); + + foreach (Document doc in dte.Documents) + { + try + { + if (PathsEqual(doc.FullName, path)) + { + doc.Activate(); + dte.ExecuteCommand("EditorContextMenus.FileHealthIndicator.RunDefaultCodeCleanup"); + return true; + } + } + catch (Exception ex) + { + VsixTelemetry.TrackException(ex); + } + } + + return false; + } + public async Task ReadDocumentAsync(string path) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();