-
Notifications
You must be signed in to change notification settings - Fork 0
Add consolidated dotnet_workload tool with enum-based action routing #222
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
| foreach (var id in workloadIds) | ||
| { | ||
| if (!ParameterValidator.ValidateWorkloadId(id, out var validationError)) | ||
| { | ||
| if (machineReadable) | ||
| { | ||
| var error = new ErrorResponse | ||
| { | ||
| Success = false, | ||
| Errors = new List<ErrorResult> | ||
| { | ||
| new ErrorResult | ||
| { | ||
| Code = "INVALID_PARAMETER", | ||
| Message = validationError!, | ||
| Category = "Validation", | ||
| Hint = "Workload IDs must contain only alphanumeric characters, hyphens, and underscores", | ||
| McpErrorCode = McpErrorCodes.InvalidParams | ||
| } | ||
| }, | ||
| ExitCode = -1 | ||
| }; | ||
| return ErrorResultFactory.ToJson(error); | ||
| } | ||
| return $"Error: {validationError}"; | ||
| } | ||
| } |
Check notice
Code scanning / CodeQL
Missed opportunity to use Where Note
implicitly filters its target sequence
Show autofix suggestion
Hide autofix suggestion
Copilot Autofix
AI 1 day ago
In general, to address a “missed opportunity to use Where” warning, you replace a foreach loop that checks a condition on each element with iteration over a sequence already filtered by .Where(predicate). This makes the filtering intent explicit and can reduce nesting and conditional noise inside the loop.
Here, we must preserve semantics: we want to find the first invalid workload ID, get its validationError message (from ParameterValidator.ValidateWorkloadId), and return the same error as before. The safest approach is:
- Use
Whereto project the sequence of invalid IDs, but only materialize the first invalid ID to preserve short-circuit-like behavior. - Still call
ValidateWorkloadIdexactly once for that ID so that we can retrievevalidationError. We can’t easily get the out parameter from inside theWherepredicate without making the code more complex than necessary. - Leave the rest of the method (error construction, machineReadable handling, etc.) unchanged.
Concretely, in ValidateWorkloadIds in DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs:
- Replace the
foreach (var id in workloadIds)loop with:var firstInvalidId = workloadIds.FirstOrDefault(id => !ParameterValidator.ValidateWorkloadId(id, out _));- An
if (firstInvalidId != null)block where we callValidateWorkloadId(firstInvalidId, out var validationError)again to get the message, then build and return the same error responses as before.
- Add
using System.Linq;at the top to enableFirstOrDefault.
This keeps external behavior identical while satisfying the CodeQL recommendation to make the filtering explicit via LINQ.
-
Copy modified line R2 -
Copy modified lines R199-R200 -
Copy modified lines R202-R205 -
Copy modified line R207 -
Copy modified lines R209-R210 -
Copy modified line R212 -
Copy modified lines R214-R223 -
Copy modified line R225
| @@ -1,4 +1,5 @@ | ||
| using System.Text; | ||
| using System.Linq; | ||
| using DotNetMcp.Actions; | ||
| using ModelContextProtocol.Server; | ||
|
|
||
| @@ -195,32 +196,33 @@ | ||
| } | ||
|
|
||
| // Validate each workload ID | ||
| foreach (var id in workloadIds) | ||
| var firstInvalidId = workloadIds.FirstOrDefault(id => !ParameterValidator.ValidateWorkloadId(id, out _)); | ||
| if (firstInvalidId != null) | ||
| { | ||
| if (!ParameterValidator.ValidateWorkloadId(id, out var validationError)) | ||
| // Re-validate the first invalid ID to capture the validation error message | ||
| ParameterValidator.ValidateWorkloadId(firstInvalidId, out var validationError); | ||
|
|
||
| if (machineReadable) | ||
| { | ||
| if (machineReadable) | ||
| var error = new ErrorResponse | ||
| { | ||
| var error = new ErrorResponse | ||
| Success = false, | ||
| Errors = new List<ErrorResult> | ||
| { | ||
| Success = false, | ||
| Errors = new List<ErrorResult> | ||
| new ErrorResult | ||
| { | ||
| new ErrorResult | ||
| { | ||
| Code = "INVALID_PARAMETER", | ||
| Message = validationError!, | ||
| Category = "Validation", | ||
| Hint = "Workload IDs must contain only alphanumeric characters, hyphens, and underscores", | ||
| McpErrorCode = McpErrorCodes.InvalidParams | ||
| } | ||
| }, | ||
| ExitCode = -1 | ||
| }; | ||
| return ErrorResultFactory.ToJson(error); | ||
| } | ||
| return $"Error: {validationError}"; | ||
| Code = "INVALID_PARAMETER", | ||
| Message = validationError!, | ||
| Category = "Validation", | ||
| Hint = "Workload IDs must contain only alphanumeric characters, hyphens, and underscores", | ||
| McpErrorCode = McpErrorCodes.InvalidParams | ||
| } | ||
| }, | ||
| ExitCode = -1 | ||
| }; | ||
| return ErrorResultFactory.ToJson(error); | ||
| } | ||
| return $"Error: {validationError}"; | ||
| } | ||
|
|
||
| return null; // Validation succeeded |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR implements the consolidated dotnet_workload tool as part of the strategic tool surface consolidation initiative. It replaces 6 individual workload-related tools with a single enum-driven interface that routes actions through a switch statement pattern.
Key changes:
- Single
DotnetWorkloadmethod withDotnetWorkloadActionenum parameter supporting 6 actions - Shared validation logic via
ValidateWorkloadIdshelper method eliminates duplication - Comprehensive test suite with 39 test cases covering all actions, validation paths, and error conditions
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs | Implements consolidated workload tool with action routing, validation, and machine-readable error handling |
| DotNetMcp.Tests/Tools/ConsolidatedWorkloadToolTests.cs | Comprehensive test coverage for all 6 actions, parameter validation, and error scenarios |
| foreach (var id in workloadIds) | ||
| { | ||
| if (!ParameterValidator.ValidateWorkloadId(id, out var validationError)) | ||
| { | ||
| if (machineReadable) | ||
| { | ||
| var error = new ErrorResponse | ||
| { | ||
| Success = false, | ||
| Errors = new List<ErrorResult> | ||
| { | ||
| new ErrorResult | ||
| { | ||
| Code = "INVALID_PARAMETER", | ||
| Message = validationError!, | ||
| Category = "Validation", | ||
| Hint = "Workload IDs must contain only alphanumeric characters, hyphens, and underscores", | ||
| McpErrorCode = McpErrorCodes.InvalidParams | ||
| } | ||
| }, | ||
| ExitCode = -1 | ||
| }; | ||
| return ErrorResultFactory.ToJson(error); | ||
| } | ||
| return $"Error: {validationError}"; | ||
| } | ||
| } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop implicitly filters its target sequence - consider filtering the sequence explicitly using '.Where(...)'.
| foreach (var action in actions) | ||
| { | ||
| // Act | ||
| var result = action switch | ||
| { | ||
| DotnetWorkloadAction.Install => await _tools.DotnetWorkload(action, workloadIds: new[] { "test-id" }), | ||
| DotnetWorkloadAction.Uninstall => await _tools.DotnetWorkload(action, workloadIds: new[] { "test-id" }), | ||
| _ => await _tools.DotnetWorkload(action) | ||
| }; | ||
|
|
||
| // Assert | ||
| Assert.NotNull(result); | ||
| // Should not throw or return "Unsupported action" | ||
| Assert.DoesNotContain("Unsupported action", result); | ||
| } |
Copilot
AI
Jan 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This foreach loop immediately maps its iteration variable to another variable - consider mapping the sequence explicitly using '.Select(...)'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored to use .Select() with Task.WhenAll() for parallel execution. (b7b4d72)
1 similar comment
Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Add global using for action enums and make DotnetWorkload a partial tool method so MCP tool descriptions are generated from XML docs.
|
Reviewer note: This PR switches consolidated tools to rely on MCP’s XML-doc → description source generator.
If you’re reviewing consolidated tools going forward: prefer |
Implementation Plan: Consolidated dotnet_workload Tool
Based on doc/tool-surface-consolidation.md, I need to implement a consolidated
dotnet_workloadtool that replaces the existing 6 individual workload tools.Checklist:
DotnetWorkloadtool method in new fileDotNetCliTools.Workload.Consolidated.csDotnetWorkloadActionenum parameter (List, Info, Search, Install, Update, Uninstall)ConsolidatedWorkloadToolTests.csOriginal prompt
✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.