Skip to content

Commit 958ed5f

Browse files
authored
feat(diagnostics): allow reading of output panes and error messages (#40)
1 parent 3526113 commit 958ed5f

10 files changed

Lines changed: 609 additions & 5 deletions

File tree

README.md

Lines changed: 56 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,43 @@
8282
| `build_cancel` | Cancel a running build |
8383
| `build_status` | Get current build status |
8484

85+
### 🧭 Navigation Tools
86+
87+
| Tool | Description |
88+
|------|-------------|
89+
| `goto_definition` | Navigate to the definition of a symbol |
90+
| `find_references` | Find all references to a symbol |
91+
| `symbol_document` | Get all symbols defined in a document |
92+
| `symbol_workspace` | Search for symbols across the solution |
93+
94+
### 🐛 Debugger Tools
95+
96+
| Tool | Description |
97+
|------|-------------|
98+
| `debugger_status` | Get current debugger state |
99+
| `debugger_launch` | Start debugging (F5) |
100+
| `debugger_launch_without_debugging` | Start without debugger (Ctrl+F5) |
101+
| `debugger_continue` | Continue execution (F5) |
102+
| `debugger_break` | Pause execution (Ctrl+Alt+Break) |
103+
| `debugger_stop` | Stop debugging (Shift+F5) |
104+
| `debugger_step_over` | Step over (F10) |
105+
| `debugger_step_into` | Step into (F11) |
106+
| `debugger_step_out` | Step out (Shift+F11) |
107+
| `debugger_add_breakpoint` | Add a breakpoint at a file and line |
108+
| `debugger_remove_breakpoint` | Remove a breakpoint |
109+
| `debugger_list_breakpoints` | List all breakpoints |
110+
| `debugger_get_locals` | Get local variables in current frame |
111+
| `debugger_get_callstack` | Get the call stack |
112+
113+
### 🔍 Diagnostics Tools
114+
115+
| Tool | Description |
116+
|------|-------------|
117+
| `errors_list` | Read build errors, warnings, and messages from the Error List |
118+
| `output_read` | Read content from an Output window pane |
119+
| `output_write` | Write a message to an Output window pane |
120+
| `output_list_panes` | List all available Output window panes |
121+
85122
## 🛠️ Installation
86123

87124
### Visual Studio Marketplace
@@ -103,20 +140,36 @@ Download the latest `.vsix` from the [Releases](https://github.com/CodingWithCal
103140
2. Go to **Tools > MCP Server > Start Server** (or enable auto-start in settings)
104141
3. The MCP server starts on `http://localhost:5050`
105142

106-
### 🤖 Configuring Claude Desktop
143+
### 🤖 Configuring Claude Desktop & Claude Code
107144

108-
Add this to your Claude Desktop MCP settings:
145+
Add this to your Claude Desktop or Claude Code MCP settings (preferred HTTP method):
109146

110147
```json
111148
{
112149
"mcpServers": {
113-
"visual-studio": {
150+
"visualstudio": {
151+
"type": "http",
152+
"url": "http://localhost:5050"
153+
}
154+
}
155+
}
156+
```
157+
158+
**Legacy SSE method** (deprecated, but still supported):
159+
160+
```json
161+
{
162+
"mcpServers": {
163+
"visualstudio": {
164+
"type": "sse",
114165
"url": "http://localhost:5050/sse"
115166
}
116167
}
117168
}
118169
```
119170

171+
> ℹ️ **Note:** The HTTP method is the preferred standard. SSE (Server-Sent Events) is a legacy protocol and should only be used for backward compatibility.
172+
120173
### ⚙️ Settings
121174

122175
Configure the extension at **Tools > Options > MCP Server**:

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
{
@@ -149,4 +149,11 @@ public Task<ReferencesResult> FindReferencesAsync(string path, int line, int col
149149
public Task<List<BreakpointInfo>> DebugGetBreakpointsAsync() => Proxy.DebugGetBreakpointsAsync();
150150
public Task<List<Shared.Models.LocalVariableInfo>> DebugGetLocalsAsync() => Proxy.DebugGetLocalsAsync();
151151
public Task<List<CallStackFrameInfo>> DebugGetCallStackAsync() => Proxy.DebugGetCallStackAsync();
152+
153+
public Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100)
154+
=> Proxy.GetErrorListAsync(severity, maxResults);
155+
public Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier) => Proxy.ReadOutputPaneAsync(paneIdentifier);
156+
public Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false)
157+
=> Proxy.WriteOutputPaneAsync(paneIdentifier, message, activate);
158+
public Task<List<OutputPaneInfo>> GetOutputPanesAsync() => Proxy.GetOutputPanesAsync();
152159
}
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
@@ -56,6 +56,12 @@ public interface IVisualStudioRpc
5656
Task<List<BreakpointInfo>> DebugGetBreakpointsAsync();
5757
Task<List<LocalVariableInfo>> DebugGetLocalsAsync();
5858
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();
59+
60+
// Diagnostics tools
61+
Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100);
62+
Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier);
63+
Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false);
64+
Task<List<OutputPaneInfo>> GetOutputPanesAsync();
5965
}
6066

6167
/// <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
@@ -52,4 +52,9 @@ public interface IVisualStudioService
5252
Task<List<BreakpointInfo>> DebugGetBreakpointsAsync();
5353
Task<List<LocalVariableInfo>> DebugGetLocalsAsync();
5454
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();
55+
56+
Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100);
57+
Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier);
58+
Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false);
59+
Task<List<OutputPaneInfo>> GetOutputPanesAsync();
5560
}

src/CodingWithCalvin.MCPServer/Services/RpcServer.cs

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

0 commit comments

Comments
 (0)