Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 56 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,43 @@
| `build_cancel` | Cancel a running build |
| `build_status` | Get current build status |

### 🧭 Navigation Tools

| Tool | Description |
|------|-------------|
| `goto_definition` | Navigate to the definition of a symbol |
| `find_references` | Find all references to a symbol |
| `symbol_document` | Get all symbols defined in a document |
| `symbol_workspace` | Search for symbols across the solution |

### 🐛 Debugger Tools

| Tool | Description |
|------|-------------|
| `debugger_status` | Get current debugger state |
| `debugger_launch` | Start debugging (F5) |
| `debugger_launch_without_debugging` | Start without debugger (Ctrl+F5) |
| `debugger_continue` | Continue execution (F5) |
| `debugger_break` | Pause execution (Ctrl+Alt+Break) |
| `debugger_stop` | Stop debugging (Shift+F5) |
| `debugger_step_over` | Step over (F10) |
| `debugger_step_into` | Step into (F11) |
| `debugger_step_out` | Step out (Shift+F11) |
| `debugger_add_breakpoint` | Add a breakpoint at a file and line |
| `debugger_remove_breakpoint` | Remove a breakpoint |
| `debugger_list_breakpoints` | List all breakpoints |
| `debugger_get_locals` | Get local variables in current frame |
| `debugger_get_callstack` | Get the call stack |

### 🔍 Diagnostics Tools

| Tool | Description |
|------|-------------|
| `errors_list` | Read build errors, warnings, and messages from the Error List |
| `output_read` | Read content from an Output window pane |
| `output_write` | Write a message to an Output window pane |
| `output_list_panes` | List all available Output window panes |

## 🛠️ Installation

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

### 🤖 Configuring Claude Desktop
### 🤖 Configuring Claude Desktop & Claude Code

Add this to your Claude Desktop MCP settings:
Add this to your Claude Desktop or Claude Code MCP settings (preferred HTTP method):

```json
{
"mcpServers": {
"visual-studio": {
"visualstudio": {
"type": "http",
"url": "http://localhost:5050"
}
}
}
```

**Legacy SSE method** (deprecated, but still supported):

```json
{
"mcpServers": {
"visualstudio": {
"type": "sse",
"url": "http://localhost:5050/sse"
}
}
}
```

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

### ⚙️ Settings

Configure the extension at **Tools > Options > MCP Server**:
Expand Down
3 changes: 2 additions & 1 deletion src/CodingWithCalvin.MCPServer.Server/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,8 @@ static async Task RunServerAsync(string pipeName, string host, int port, string
.WithTools<DocumentTools>()
.WithTools<BuildTools>()
.WithTools<NavigationTools>()
.WithTools<DebuggerTools>();
.WithTools<DebuggerTools>()
.WithTools<DiagnosticsTools>();

var app = builder.Build();

Expand Down
9 changes: 8 additions & 1 deletion src/CodingWithCalvin.MCPServer.Server/RpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ public Task<List<ToolInfo>> GetAvailableToolsAsync()
}

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

foreach (var toolType in toolTypes)
{
Expand Down Expand Up @@ -149,4 +149,11 @@ public Task<ReferencesResult> FindReferencesAsync(string path, int line, int col
public Task<List<BreakpointInfo>> DebugGetBreakpointsAsync() => Proxy.DebugGetBreakpointsAsync();
public Task<List<Shared.Models.LocalVariableInfo>> DebugGetLocalsAsync() => Proxy.DebugGetLocalsAsync();
public Task<List<CallStackFrameInfo>> DebugGetCallStackAsync() => Proxy.DebugGetCallStackAsync();

public Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100)
=> Proxy.GetErrorListAsync(severity, maxResults);
public Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier) => Proxy.ReadOutputPaneAsync(paneIdentifier);
public Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false)
=> Proxy.WriteOutputPaneAsync(paneIdentifier, message, activate);
public Task<List<OutputPaneInfo>> GetOutputPanesAsync() => Proxy.GetOutputPanesAsync();
}
79 changes: 79 additions & 0 deletions src/CodingWithCalvin.MCPServer.Server/Tools/DiagnosticsTools.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System.ComponentModel;
using System.Text.Json;
using System.Threading.Tasks;
using ModelContextProtocol.Server;

namespace CodingWithCalvin.MCPServer.Server.Tools;

[McpServerToolType]
public class DiagnosticsTools
{
private readonly RpcClient _rpcClient;
private readonly JsonSerializerOptions _jsonOptions;

public DiagnosticsTools(RpcClient rpcClient)
{
_rpcClient = rpcClient;
_jsonOptions = new JsonSerializerOptions { WriteIndented = true };
}

[McpServerTool(Name = "errors_list", ReadOnly = true)]
[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.")]
public async Task<string> GetErrorListAsync(
[Description("Filter by severity: \"Error\", \"Warning\", \"Message\", or null for all. Case-insensitive.")]
string? severity = null,
[Description("Maximum number of items to return. Defaults to 100.")]
int maxResults = 100)
{
var result = await _rpcClient.GetErrorListAsync(severity, maxResults);

// Always return the JSON result (includes debug info if TotalCount is 0)
return JsonSerializer.Serialize(result, _jsonOptions);
}

[McpServerTool(Name = "output_read", ReadOnly = true)]
[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.")]
public async Task<string> ReadOutputPaneAsync(
[Description("Output pane identifier: GUID string or well-known name (\"Build\", \"Debug\", \"General\").")]
string paneIdentifier)
{
var result = await _rpcClient.ReadOutputPaneAsync(paneIdentifier);

if (string.IsNullOrEmpty(result.Content))
{
return $"Output pane '{paneIdentifier}' is empty or does not support reading";
}

return JsonSerializer.Serialize(result, _jsonOptions);
}

[McpServerTool(Name = "output_write", Destructive = false, Idempotent = false)]
[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.")]
public async Task<string> WriteOutputPaneAsync(
[Description("Output pane identifier: GUID string or name. Custom GUIDs/names will create new panes if needed.")]
string paneIdentifier,
[Description("Message to write. Appended to existing content.")]
string message,
[Description("Whether to activate (bring to front) the Output window. Defaults to false.")]
bool activate = false)
{
var success = await _rpcClient.WriteOutputPaneAsync(paneIdentifier, message, activate);
return success
? $"Message written to output pane: {paneIdentifier}"
: $"Failed to write to output pane: {paneIdentifier}";
}

[McpServerTool(Name = "output_list_panes", ReadOnly = true)]
[Description("List available Output window panes. Returns well-known panes (Build, Debug, General) with their names and GUIDs.")]
public async Task<string> GetOutputPanesAsync()
{
var panes = await _rpcClient.GetOutputPanesAsync();

if (panes.Count == 0)
{
return "No output panes available";
}

return JsonSerializer.Serialize(panes, _jsonOptions);
}
}
37 changes: 37 additions & 0 deletions src/CodingWithCalvin.MCPServer.Shared/Models/DiagnosticsModels.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using System.Collections.Generic;

namespace CodingWithCalvin.MCPServer.Shared.Models;

public class ErrorListResult
{
public List<ErrorItemInfo> Items { get; set; } = new();
public int TotalCount { get; set; }
public int ErrorCount { get; set; }
public int WarningCount { get; set; }
public int MessageCount { get; set; }
public bool Truncated { get; set; }
}

public class ErrorItemInfo
{
public string Severity { get; set; } = string.Empty; // "Error", "Warning", "Message"
public string Description { get; set; } = string.Empty;
public string ErrorCode { get; set; } = string.Empty; // e.g., "CS0103"
public string Project { get; set; } = string.Empty;
public string FilePath { get; set; } = string.Empty;
public int Line { get; set; }
public int Column { get; set; }
}

public class OutputPaneInfo
{
public string Name { get; set; } = string.Empty;
public string Guid { get; set; } = string.Empty;
}

public class OutputReadResult
{
public string Content { get; set; } = string.Empty;
public string PaneName { get; set; } = string.Empty;
public int LinesRead { get; set; }
}
6 changes: 6 additions & 0 deletions src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ public interface IVisualStudioRpc
Task<List<BreakpointInfo>> DebugGetBreakpointsAsync();
Task<List<LocalVariableInfo>> DebugGetLocalsAsync();
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();

// Diagnostics tools
Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100);
Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier);
Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false);
Task<List<OutputPaneInfo>> GetOutputPanesAsync();
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

<ItemGroup>
<PackageReference Include="CodingWithCalvin.Otel4Vsix" Version="0.2.2" />
<PackageReference Include="Microsoft.VisualStudio.Editor" Version="17.14.249" />
<PackageReference Include="Microsoft.VisualStudio.SDK" Version="17.14.40265" />
<PackageReference Include="Microsoft.VSSDK.BuildTools" Version="17.*" PrivateAssets="all" />
</ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,9 @@ public interface IVisualStudioService
Task<List<BreakpointInfo>> DebugGetBreakpointsAsync();
Task<List<LocalVariableInfo>> DebugGetLocalsAsync();
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();

Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100);
Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier);
Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false);
Task<List<OutputPaneInfo>> GetOutputPanesAsync();
}
7 changes: 7 additions & 0 deletions src/CodingWithCalvin.MCPServer/Services/RpcServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,4 +209,11 @@ public Task<ReferencesResult> FindReferencesAsync(string path, int line, int col
public Task<List<BreakpointInfo>> DebugGetBreakpointsAsync() => _vsService.DebugGetBreakpointsAsync();
public Task<List<LocalVariableInfo>> DebugGetLocalsAsync() => _vsService.DebugGetLocalsAsync();
public Task<List<CallStackFrameInfo>> DebugGetCallStackAsync() => _vsService.DebugGetCallStackAsync();

public Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100)
=> _vsService.GetErrorListAsync(severity, maxResults);
public Task<OutputReadResult> ReadOutputPaneAsync(string paneIdentifier) => _vsService.ReadOutputPaneAsync(paneIdentifier);
public Task<bool> WriteOutputPaneAsync(string paneIdentifier, string message, bool activate = false)
=> _vsService.WriteOutputPaneAsync(paneIdentifier, message, activate);
public Task<List<OutputPaneInfo>> GetOutputPanesAsync() => _vsService.GetOutputPanesAsync();
}
Loading
Loading