Skip to content

Commit e28158a

Browse files
gclpixelEbersbach, Stephan
andauthored
feat(tools): add document_cleanup tool (#89)
* 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. * Add the new document_cleanup tool on the readme.md documentation * Update DocumentTools.cs --------- Co-authored-by: Ebersbach, Stephan <ebersbach@facton.com>
1 parent efee101 commit e28158a

7 files changed

Lines changed: 39 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
| Tool | Description |
4343
|------|-------------|
4444
| `document_active` | Get the active document |
45+
| `document_cleanup` | Run code cleanup on a document |
4546
| `document_close` | Close a document |
4647
| `document_list` | List all open documents |
4748
| `document_open` | Open a file in the editor |

src/CodingWithCalvin.MCPServer.Server/RpcClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@ public Task ShutdownAsync()
112112
public Task<bool> OpenDocumentAsync(string path) => Proxy.OpenDocumentAsync(path);
113113
public Task<bool> CloseDocumentAsync(string path, bool save) => Proxy.CloseDocumentAsync(path, save);
114114
public Task<bool> SaveDocumentAsync(string path) => Proxy.SaveDocumentAsync(path);
115+
public Task<bool> RunCodeCleanupAsync(string path) => Proxy.RunCodeCleanupAsync(path);
115116
public Task<string?> ReadDocumentAsync(string path) => Proxy.ReadDocumentAsync(path);
116117
public Task<bool> WriteDocumentAsync(string path, string content) => Proxy.WriteDocumentAsync(path, content);
117118
public Task<SelectionInfo?> GetSelectionAsync() => Proxy.GetSelectionAsync();

src/CodingWithCalvin.MCPServer.Server/Tools/DocumentTools.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,15 @@ public async Task<string> SaveDocumentAsync(
7272
return success ? $"Saved: {path}" : $"Document not found or failed to save: {path}";
7373
}
7474

75+
[McpServerTool(Name = "document_cleanup", Destructive = true, Idempotent = true)]
76+
[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 'EditorContextMenus.FileHealthIndicator.RunDefaultCodeCleanup' command.")]
77+
public async Task<string> RunCodeCleanupAsync(
78+
[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)
79+
{
80+
var success = await _rpcClient.RunCodeCleanupAsync(path);
81+
return success ? $"Code cleanup completed: {path}" : $"Document not found or code cleanup failed: {path}";
82+
}
83+
7584
[McpServerTool(Name = "document_read", ReadOnly = true)]
7685
[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.")]
7786
public async Task<string> ReadDocumentAsync(

src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ public interface IVisualStudioRpc
2020
Task<bool> OpenDocumentAsync(string path);
2121
Task<bool> CloseDocumentAsync(string path, bool save);
2222
Task<bool> SaveDocumentAsync(string path);
23+
Task<bool> RunCodeCleanupAsync(string path);
2324
Task<string?> ReadDocumentAsync(string path);
2425
Task<bool> WriteDocumentAsync(string path, string content);
2526
Task<SelectionInfo?> GetSelectionAsync();

src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public interface IVisualStudioService
1616
Task<bool> OpenDocumentAsync(string path);
1717
Task<bool> CloseDocumentAsync(string path, bool save = true);
1818
Task<bool> SaveDocumentAsync(string path);
19+
Task<bool> RunCodeCleanupAsync(string path);
1920
Task<string?> ReadDocumentAsync(string path);
2021
Task<bool> WriteDocumentAsync(string path, string content);
2122
Task<SelectionInfo?> GetSelectionAsync();

src/CodingWithCalvin.MCPServer/Services/RpcServer.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,7 @@ public async Task RequestShutdownAsync()
170170
public Task<bool> OpenDocumentAsync(string path) => _vsService.OpenDocumentAsync(path);
171171
public Task<bool> CloseDocumentAsync(string path, bool save) => _vsService.CloseDocumentAsync(path, save);
172172
public Task<bool> SaveDocumentAsync(string path) => _vsService.SaveDocumentAsync(path);
173+
public Task<bool> RunCodeCleanupAsync(string path) => _vsService.RunCodeCleanupAsync(path);
173174
public Task<string?> ReadDocumentAsync(string path) => _vsService.ReadDocumentAsync(path);
174175
public Task<bool> WriteDocumentAsync(string path, string content) => _vsService.WriteDocumentAsync(path, content);
175176
public Task<SelectionInfo?> GetSelectionAsync() => _vsService.GetSelectionAsync();

src/CodingWithCalvin.MCPServer/Services/VisualStudioService.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,31 @@ public async Task<bool> SaveDocumentAsync(string path)
260260
return false;
261261
}
262262

263+
public async Task<bool> RunCodeCleanupAsync(string path)
264+
{
265+
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();
266+
var dte = await GetDteAsync();
267+
268+
foreach (Document doc in dte.Documents)
269+
{
270+
try
271+
{
272+
if (PathsEqual(doc.FullName, path))
273+
{
274+
doc.Activate();
275+
dte.ExecuteCommand("EditorContextMenus.FileHealthIndicator.RunDefaultCodeCleanup");
276+
return true;
277+
}
278+
}
279+
catch (Exception ex)
280+
{
281+
VsixTelemetry.TrackException(ex);
282+
}
283+
}
284+
285+
return false;
286+
}
287+
263288
public async Task<string?> ReadDocumentAsync(string path)
264289
{
265290
await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync();

0 commit comments

Comments
 (0)