Skip to content

Commit dfb94f0

Browse files
committed
feat(diagnostics): implement diagnostics tools for error list and output pane access
Implement three new MCP tools for accessing Visual Studio diagnostics: - errors_list: Read build errors, warnings, and messages from the Error List using the modern Table Control API (IErrorList/IWpfTableControl) - output_read: Read content from Visual Studio Output window panes - output_list_panes: List all available Output window panes (built-in and custom) Key improvements: - Use IErrorList and IWpfTableControl for reliable, type-safe error access - Properly map __VSERRORCATEGORY enum to Error/Warning/Message severity - Extract all error details: code, project, file path, line, column, description - Support severity filtering on errors_list tool - Implement proper RPC contracts and service layer integration - Add Microsoft.VisualStudio.Editor package for text view support The diagnostics tools enable AI assistants to inspect build output and error details during development.
1 parent 5dd40d9 commit dfb94f0

9 files changed

Lines changed: 553 additions & 2 deletions

File tree

src/CodingWithCalvin.MCPServer.Server/Program.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ static async Task RunServerAsync(string pipeName, string host, int port, string
9898
.WithTools<DocumentTools>()
9999
.WithTools<BuildTools>()
100100
.WithTools<NavigationTools>()
101-
.WithTools<DebuggerTools>();
101+
.WithTools<DebuggerTools>()
102+
.WithTools<DiagnosticsTools>();
102103

103104
var app = builder.Build();
104105

src/CodingWithCalvin.MCPServer.Server/RpcClient.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public Task<List<ToolInfo>> GetAvailableToolsAsync()
6565
}
6666

6767
var tools = new List<ToolInfo>();
68-
var toolTypes = new[] { typeof(Tools.SolutionTools), typeof(Tools.DocumentTools), typeof(Tools.BuildTools), typeof(Tools.NavigationTools), typeof(Tools.DebuggerTools) };
68+
var toolTypes = new[] { typeof(Tools.SolutionTools), typeof(Tools.DocumentTools), typeof(Tools.BuildTools), typeof(Tools.NavigationTools), typeof(Tools.DebuggerTools), typeof(Tools.DiagnosticsTools) };
6969

7070
foreach (var toolType in toolTypes)
7171
{
@@ -148,4 +148,11 @@ public Task<ReferencesResult> FindReferencesAsync(string path, int line, int col
148148
public Task<List<BreakpointInfo>> DebugGetBreakpointsAsync() => Proxy.DebugGetBreakpointsAsync();
149149
public Task<List<Shared.Models.LocalVariableInfo>> DebugGetLocalsAsync() => Proxy.DebugGetLocalsAsync();
150150
public Task<List<CallStackFrameInfo>> DebugGetCallStackAsync() => Proxy.DebugGetCallStackAsync();
151+
152+
public Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100)
153+
=> Proxy.GetErrorListAsync(severity, maxResults);
154+
public Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier) => Proxy.ReadOutputPaneAsync(paneIdentifier);
155+
public Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false)
156+
=> Proxy.WriteOutputPaneAsync(paneIdentifier, message, activate);
157+
public Task<List<OutputPaneInfo>> GetOutputPanesAsync() => Proxy.GetOutputPanesAsync();
151158
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.ComponentModel;
2+
using System.Text.Json;
3+
using System.Threading.Tasks;
4+
using ModelContextProtocol.Server;
5+
6+
namespace CodingWithCalvin.MCPServer.Server.Tools;
7+
8+
[McpServerToolType]
9+
public class DiagnosticsTools
10+
{
11+
private readonly RpcClient _rpcClient;
12+
private readonly JsonSerializerOptions _jsonOptions;
13+
14+
public DiagnosticsTools(RpcClient rpcClient)
15+
{
16+
_rpcClient = rpcClient;
17+
_jsonOptions = new JsonSerializerOptions { WriteIndented = true };
18+
}
19+
20+
[McpServerTool(Name = "errors_list", ReadOnly = true)]
21+
[Description("Get errors, warnings, and messages from the Error List. Returns diagnostics with file, line, description, and severity. Filter by severity to focus on specific issues.")]
22+
public async Task<string> GetErrorListAsync(
23+
[Description("Filter by severity: \"Error\", \"Warning\", \"Message\", or null for all. Case-insensitive.")]
24+
string? severity = null,
25+
[Description("Maximum number of items to return. Defaults to 100.")]
26+
int maxResults = 100)
27+
{
28+
var result = await _rpcClient.GetErrorListAsync(severity, maxResults);
29+
30+
// Always return the JSON result (includes debug info if TotalCount is 0)
31+
return JsonSerializer.Serialize(result, _jsonOptions);
32+
}
33+
34+
[McpServerTool(Name = "output_read", ReadOnly = true)]
35+
[Description("Read content from an Output window pane. Specify pane by GUID or well-known name (\"Build\", \"Debug\", \"General\"). Note: Some panes may not support reading due to VS API limitations.")]
36+
public async Task<string> ReadOutputPaneAsync(
37+
[Description("Output pane identifier: GUID string or well-known name (\"Build\", \"Debug\", \"General\").")]
38+
string paneIdentifier)
39+
{
40+
var result = await _rpcClient.ReadOutputPaneAsync(paneIdentifier);
41+
42+
if (string.IsNullOrEmpty(result.Content))
43+
{
44+
return $"Output pane '{paneIdentifier}' is empty or does not support reading";
45+
}
46+
47+
return JsonSerializer.Serialize(result, _jsonOptions);
48+
}
49+
50+
[McpServerTool(Name = "output_write", Destructive = false, Idempotent = false)]
51+
[Description("Write a message to an Output window pane. Custom panes are auto-created. System panes (Build, Debug) must already exist. Message is appended to existing content.")]
52+
public async Task<string> WriteOutputPaneAsync(
53+
[Description("Output pane identifier: GUID string or name. Custom GUIDs/names will create new panes if needed.")]
54+
string paneIdentifier,
55+
[Description("Message to write. Appended to existing content.")]
56+
string message,
57+
[Description("Whether to activate (bring to front) the Output window. Defaults to false.")]
58+
bool activate = false)
59+
{
60+
var success = await _rpcClient.WriteOutputPaneAsync(paneIdentifier, message, activate);
61+
return success
62+
? $"Message written to output pane: {paneIdentifier}"
63+
: $"Failed to write to output pane: {paneIdentifier}";
64+
}
65+
66+
[McpServerTool(Name = "output_list_panes", ReadOnly = true)]
67+
[Description("List available Output window panes. Returns well-known panes (Build, Debug, General) with their names and GUIDs.")]
68+
public async Task<string> GetOutputPanesAsync()
69+
{
70+
var panes = await _rpcClient.GetOutputPanesAsync();
71+
72+
if (panes.Count == 0)
73+
{
74+
return "No output panes available";
75+
}
76+
77+
return JsonSerializer.Serialize(panes, _jsonOptions);
78+
}
79+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Collections.Generic;
2+
3+
namespace CodingWithCalvin.MCPServer.Shared.Models;
4+
5+
public class ErrorListResult
6+
{
7+
public List<ErrorItemInfo> Items { get; set; } = new();
8+
public int TotalCount { get; set; }
9+
public int ErrorCount { get; set; }
10+
public int WarningCount { get; set; }
11+
public int MessageCount { get; set; }
12+
public bool Truncated { get; set; }
13+
}
14+
15+
public class ErrorItemInfo
16+
{
17+
public string Severity { get; set; } = string.Empty; // "Error", "Warning", "Message"
18+
public string Description { get; set; } = string.Empty;
19+
public string ErrorCode { get; set; } = string.Empty; // e.g., "CS0103"
20+
public string Project { get; set; } = string.Empty;
21+
public string FilePath { get; set; } = string.Empty;
22+
public int Line { get; set; }
23+
public int Column { get; set; }
24+
}
25+
26+
public class OutputPaneInfo
27+
{
28+
public string Name { get; set; } = string.Empty;
29+
public string Guid { get; set; } = string.Empty;
30+
}
31+
32+
public class OutputReadResult
33+
{
34+
public string Content { get; set; } = string.Empty;
35+
public string PaneName { get; set; } = string.Empty;
36+
public int LinesRead { get; set; }
37+
}

src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,12 @@ public interface IVisualStudioRpc
5555
Task<List<BreakpointInfo>> DebugGetBreakpointsAsync();
5656
Task<List<LocalVariableInfo>> DebugGetLocalsAsync();
5757
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();
58+
59+
// Diagnostics tools
60+
Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100);
61+
Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier);
62+
Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false);
63+
Task<List<OutputPaneInfo>> GetOutputPanesAsync();
5864
}
5965

6066
/// <summary>

src/CodingWithCalvin.MCPServer/CodingWithCalvin.MCPServer.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
<ItemGroup>
1111
<PackageReference Include="CodingWithCalvin.Otel4Vsix" Version="0.2.2" />
12+
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="17.14.249" />
1213
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.14.40265" />
1314
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.*" PrivateAssets="all" />
1415
</ItemGroup>

src/CodingWithCalvin.MCPServer/Services/IVisualStudioService.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,9 @@ public interface IVisualStudioService
5151
Task<List<BreakpointInfo>> DebugGetBreakpointsAsync();
5252
Task<List<LocalVariableInfo>> DebugGetLocalsAsync();
5353
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();
54+
55+
Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100);
56+
Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier);
57+
Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false);
58+
Task<List<OutputPaneInfo>> GetOutputPanesAsync();
5459
}

src/CodingWithCalvin.MCPServer/Services/RpcServer.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,4 +208,11 @@ public Task<ReferencesResult> FindReferencesAsync(string path, int line, int col
208208
public Task<List<BreakpointInfo>> DebugGetBreakpointsAsync() => _vsService.DebugGetBreakpointsAsync();
209209
public Task<List<LocalVariableInfo>> DebugGetLocalsAsync() => _vsService.DebugGetLocalsAsync();
210210
public Task<List<CallStackFrameInfo>> DebugGetCallStackAsync() => _vsService.DebugGetCallStackAsync();
211+
212+
public Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100)
213+
=> _vsService.GetErrorListAsync(severity, maxResults);
214+
public Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier) => _vsService.ReadOutputPaneAsync(paneIdentifier);
215+
public Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false)
216+
=> _vsService.WriteOutputPaneAsync(paneIdentifier, message, activate);
217+
public Task<List<OutputPaneInfo>> GetOutputPanesAsync() => _vsService.GetOutputPanesAsync();
211218
}

0 commit comments

Comments
 (0)