From 553928fd1a1f288c5d432ac9e2d83bd25f03c68f Mon Sep 17 00:00:00 2001 From: "Ebersbach, Stephan" Date: Mon, 18 May 2026 15:54:55 +0200 Subject: [PATCH 1/2] Add code cleanup support for open documents in VS Added RunCodeCleanupAsync to service interfaces, client, and server to enable code cleanup on open documents via DTE command. Introduced the document_cleanup server tool in DocumentTools for external access, with documentation and attributes for discoverability. --- .../RpcClient.cs | 1 + .../Tools/DocumentTools.cs | 9 +++++++ .../RpcContracts.cs | 1 + .../Services/IVisualStudioService.cs | 1 + .../Services/RpcServer.cs | 1 + .../Services/VisualStudioService.cs | 25 +++++++++++++++++++ 6 files changed, 38 insertions(+) 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(); From a7df1720599c37b534473412bd79919a00ddae37 Mon Sep 17 00:00:00 2001 From: "Ebersbach, Stephan" Date: Mon, 18 May 2026 16:09:03 +0200 Subject: [PATCH 2/2] Add the new document_cleanup tool on the readme.md documentation --- README.md | 1 + 1 file changed, 1 insertion(+) 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 |