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
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,11 +195,13 @@ dotnet build src/CodingWithCalvin.VSMCP/CodingWithCalvin.VSMCP.csproj
- `debugger_stop` - Stop debugging (Shift+F5)

### Diagnostics Tools
- `errors_list` - Read build errors, warnings, and messages from the Error List
- `errors_list` - Read errors, warnings, and messages from the Error List, filtered by severity, scope, Build/IntelliSense source, suppression state, code, tool, category, project, path, or text
- `output_list_panes` - List all available Output window panes
- `output_read` - Read content from an Output window pane
- `output_write` - Write a message to an Output window pane

`errors_list` reads `IWpfTableControl.Entries`, which holds only what the Error List window displays, so the window's own scope and filters apply before the tool's filters. Its severity and Build/IntelliSense filters are readable through `IErrorList` and reported in `Notes`; its column filters are not, and the Suppression State column hides suppressed entries by default. The scope drop-down is internal API, so the tool's `scope` is resolved from the active document, open documents, and the active document's project instead. Changed Documents scope is not supported. Entries from shared files carry `ProjectNames` / `ProjectGuids` instead of `ProjectName` / `ProjectGuid`, so project matching reads both.

### Test Tools
- `test_cancel` - Cancel the test run in progress
- `test_debug` - Debug the tests in a single class or method
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@

| Tool | Description |
|------|-------------|
| `errors_list` | Read build errors, warnings, and messages from the Error List |
| `errors_list` | Read errors, warnings, and messages from the Error List, filtered by severity, scope, Build/IntelliSense source, suppression state, code, tool, category, project, path, or text |
| `output_list_panes` | List all available Output window panes |
| `output_read` | Read content from an Output window pane |
| `output_write` | Write a message to an Output window pane |
Expand Down
3 changes: 1 addition & 2 deletions src/CodingWithCalvin.MCPServer.Server/RpcClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,7 @@ public Task<bool> DebugSetBreakpointEnabledAsync(string file, int line, bool ena
public Task<bool> DebugSetVariableValueAsync(string variableName, string value) => Proxy.DebugSetVariableValueAsync(variableName, value);
public Task<List<CallStackFrameInfo>> DebugGetCallStackAsync() => Proxy.DebugGetCallStackAsync();

public Task<ErrorListResult> GetErrorListAsync(string? severity = null, int maxResults = 100)
=> Proxy.GetErrorListAsync(severity, maxResults);
public Task<ErrorListResult> GetErrorListAsync(ErrorListQuery query) => Proxy.GetErrorListAsync(query);
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);
Expand Down
90 changes: 88 additions & 2 deletions src/CodingWithCalvin.MCPServer.Server/Tools/DiagnosticsTools.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text.Json;
using System.Threading.Tasks;
using CodingWithCalvin.MCPServer.Shared.Models;
using ModelContextProtocol.Server;

namespace CodingWithCalvin.MCPServer.Server.Tools;

[McpServerToolType]
public class DiagnosticsTools
{
private static readonly string[] Severities = { "Error", "Warning", "Message" };

private readonly RpcClient _rpcClient;
private readonly JsonSerializerOptions _jsonOptions;

Expand All @@ -18,14 +24,67 @@ public DiagnosticsTools(RpcClient rpcClient)
}

[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. TotalCount, ErrorCount, WarningCount, and MessageCount always cover the whole Error List, regardless of the severity filter or maxResults. Truncated is true when more entries matched than maxResults allowed. An empty Error List can mean nothing has been built yet; build the solution to populate it.")]
[Description("Get errors, warnings, and messages from the Error List. Returns diagnostics with file, line, description, severity, code, project, source (Build or IntelliSense), tool, category, and suppression state. Filters combine: an entry must match every filter given. Only entries the Error List window is currently showing can be returned, so the window's own scope and filters apply first; Notes reports anything the window is hiding. TotalCount, ErrorCount, WarningCount, and MessageCount always cover the whole Error List, regardless of filters or maxResults. MatchedCount is how many entries passed the filters, and Truncated is true when that is more than maxResults. An empty Error List can mean nothing has been built yet; build the solution to populate it.")]
public async Task<string> GetErrorListAsync(
[Description("Filter by severity: \"Error\", \"Warning\", \"Message\", or null for all. Case-insensitive.")]
string? severity = null,
[Description("Which documents to include: solution, openDocuments, currentProject (the project containing the active document), or currentDocument. Defaults to solution. The Error List's Changed Documents scope is not available.")]
string scope = "solution",
[Description("Which producer to include: all, build, or intellisense. Defaults to all.")]
string source = "all",
[Description("Which suppression state to include: all, active, suppressed, or notApplicable. Defaults to all. The Error List window hides suppressed entries by default, so suppressed returns them only when the window's Suppression State column filter includes them.")]
string suppressionState = "all",
[Description("Error code to match exactly, such as \"CS0103\". Separate several codes with commas. Case-insensitive.")]
string? code = null,
[Description("Tool that produced the entry, as shown in the Error List's Tool column, such as \"Compiler\". Separate several with commas. Case-insensitive.")]
string? tool = null,
[Description("Category, as shown in the Error List's Category column, such as \"Style\". Separate several with commas. Case-insensitive.")]
string? category = null,
[Description("Project name. Also matches multi-targeted entries such as \"MyApp (net8.0)\". Case-insensitive.")]
string? project = null,
[Description("File name, folder, or partial path, matched on whole path segments, such as \"Program.cs\" or \"src/Services\". Case-insensitive.")]
string? path = null,
[Description("Text to find in the description, code, project, file path, tool, or category. Case-insensitive.")]
string? search = null,
[Description("Maximum number of items to return. Defaults to 100.")]
int maxResults = 100)
{
var result = await _rpcClient.GetErrorListAsync(severity, maxResults);
if (!string.IsNullOrEmpty(severity) && !Severities.Contains(severity, StringComparer.OrdinalIgnoreCase))
{
return $"Invalid severity '{severity}'. Valid values are: Error, Warning, Message.";
}

if (!TryParseScope(scope, out var parsedScope))
{
return $"Invalid scope '{scope}'. Valid values are: solution, openDocuments, currentProject, currentDocument.";
}

if (!TryParseSource(source, out var parsedSource))
{
return $"Invalid source '{source}'. Valid values are: all, build, intellisense.";
}

if (!TryParseSuppressionState(suppressionState, out var parsedSuppressionState))
{
return $"Invalid suppressionState '{suppressionState}'. Valid values are: all, active, suppressed, notApplicable.";
}

var query = new ErrorListQuery
{
Severity = string.IsNullOrEmpty(severity) ? null : severity,
Scope = parsedScope,
Source = parsedSource,
SuppressionState = parsedSuppressionState,
Codes = SplitList(code),
Tools = SplitList(tool),
Categories = SplitList(category),
Project = project,
Path = path,
Search = search,
MaxResults = maxResults
};

var result = await _rpcClient.GetErrorListAsync(query);

// Always return the JSON result (includes debug info if TotalCount is 0)
return JsonSerializer.Serialize(result, _jsonOptions);
Expand Down Expand Up @@ -76,4 +135,31 @@ public async Task<string> GetOutputPanesAsync()

return JsonSerializer.Serialize(panes, _jsonOptions);
}

private static bool TryParseScope(string value, out ErrorListScope scope)
{
return Enum.TryParse(value, ignoreCase: true, out scope)
&& Enum.IsDefined(typeof(ErrorListScope), scope);
}

private static bool TryParseSource(string value, out ErrorListSource source)
{
return Enum.TryParse(value, ignoreCase: true, out source)
&& Enum.IsDefined(typeof(ErrorListSource), source);
}

private static bool TryParseSuppressionState(string value, out ErrorListSuppressionState suppressionState)
{
return Enum.TryParse(value, ignoreCase: true, out suppressionState)
&& Enum.IsDefined(typeof(ErrorListSuppressionState), suppressionState);
}

private static List<string> SplitList(string? value)
{
return (value ?? string.Empty)
.Split(',')
.Select(item => item.Trim())
.Where(item => item.Length > 0)
.ToList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,102 @@

namespace CodingWithCalvin.MCPServer.Shared.Models;

/// <summary>
/// Which documents an Error List query covers, mirroring the scope drop-down in the Error List window.
/// </summary>
public enum ErrorListScope
{
Solution,
OpenDocuments,
CurrentProject,
CurrentDocument
}

/// <summary>
/// Which producer an Error List entry must come from, mirroring the Build + IntelliSense drop-down.
/// </summary>
public enum ErrorListSource
{
All,
Build,
IntelliSense
}

/// <summary>
/// Which suppression state an Error List entry must have, mirroring the Suppression State column.
/// </summary>
public enum ErrorListSuppressionState
{
All,
Active,
Suppressed,
NotApplicable
}

/// <summary>
/// Filters applied to the entries the Error List window is currently showing. Every filter that is
/// set must match for an entry to be returned.
/// </summary>
public class ErrorListQuery
{
/// <summary>"Error", "Warning", or "Message"; null for all.</summary>
public string? Severity { get; set; }
public ErrorListScope Scope { get; set; } = ErrorListScope.Solution;
public ErrorListSource Source { get; set; } = ErrorListSource.All;
public ErrorListSuppressionState SuppressionState { get; set; } = ErrorListSuppressionState.All;

/// <summary>Error codes to keep (e.g. "CS0103"); empty for all.</summary>
public List<string> Codes { get; set; } = new();

/// <summary>Tool names to keep, as shown in the Tool column; empty for all.</summary>
public List<string> Tools { get; set; } = new();

/// <summary>Categories to keep, as shown in the Category column; empty for all.</summary>
public List<string> Categories { get; set; } = new();
public string? Project { get; set; }

/// <summary>File name, folder, or partial path, matched on whole path segments.</summary>
public string? Path { get; set; }

/// <summary>Text to find in the description, code, project, file path, tool, or category.</summary>
public string? Search { get; set; }
public int MaxResults { get; set; } = 100;
}

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; }

/// <summary>
/// Entries that passed every filter, including any cut off by the result limit. The other counts
/// cover the whole Error List.
/// </summary>
public int MatchedCount { get; set; }
public bool Truncated { get; set; }

/// <summary>
/// Caveats about the results, such as categories the Error List window itself is hiding.
/// </summary>
public List<string> Notes { get; set; } = new();
}

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 Project { get; set; } = string.Empty; // comma-separated when the entry belongs to several projects
public string FilePath { get; set; } = string.Empty;
public int Line { get; set; }
public int Column { get; set; }
public string Source { get; set; } = string.Empty; // "Build", "IntelliSense", or empty when unknown
public string Tool { get; set; } = string.Empty; // e.g., "Compiler"
public string Category { get; set; } = string.Empty; // e.g., "Style"
public string SuppressionState { get; set; } = string.Empty; // "Active", "Suppressed", "NotApplicable", or empty when unknown
}

public class OutputPaneInfo
Expand Down
2 changes: 1 addition & 1 deletion src/CodingWithCalvin.MCPServer.Shared/RpcContracts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ Task<bool> DebugAddBreakpointAsync(
Task<List<CallStackFrameInfo>> DebugGetCallStackAsync();

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