Skip to content

Conversation

Copy link
Contributor

Copilot AI commented Jan 8, 2026

Implementation Plan: Consolidated dotnet_workload Tool

Based on doc/tool-surface-consolidation.md, I need to implement a consolidated dotnet_workload tool that replaces the existing 6 individual workload tools.

Checklist:

  • Create consolidated DotnetWorkload tool method in new file DotNetCliTools.Workload.Consolidated.cs
    • Accept DotnetWorkloadAction enum parameter (List, Info, Search, Install, Update, Uninstall)
    • Add action-specific parameters with proper validation
    • Route to appropriate CLI commands based on action
    • Support machineReadable parameter
    • Add comprehensive McpMeta attributes
  • Implement action routing logic with switch expression
    • List action (no additional params)
    • Info action (no additional params)
    • Search action (optional searchTerm)
    • Install action (required workloadIds array, optional flags)
    • Update action (optional flags)
    • Uninstall action (required workloadIds array)
  • Add comprehensive tests in ConsolidatedWorkloadToolTests.cs
    • Test each action routing (39 test cases)
    • Test parameter validation for each action
    • Test machineReadable output
    • Test error handling (invalid action, missing required params)
    • Test workloadIds array parsing and validation
  • Build verification - compiles successfully
  • Refactor to reduce code duplication (extracted shared validation method)
  • Run tests - 824 tests total, all new tests passing
  • Code review feedback - refactored test to use LINQ Select
  • Security scan (CodeQL) - no alerts found
Original prompt

This section details on the original issue you should resolve

<issue_title>Add consolidated tool: dotnet_workload (install/update/list/search/uninstall)</issue_title>
<issue_description>Implement consolidated dotnet_workload tool as described in doc/tool-surface-consolidation.md.

Actions: list, info, search, install, update, uninstall.

Acceptance:

  • Tool schema lists allowed actions.
  • Tests cover routing + validation.

Parent: #206</issue_description>

Comments on the Issue (you are @copilot in this section)


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI self-assigned this Jan 8, 2026
Copilot AI and others added 2 commits January 8, 2026 08:22
Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Co-authored-by: jongalloway <68539+jongalloway@users.noreply.github.com>
Copilot AI changed the title [WIP] Add consolidated dotnet_workload tool actions Add consolidated dotnet_workload tool with enum-based action routing Jan 8, 2026
Copilot AI requested a review from jongalloway January 8, 2026 08:41
@jongalloway jongalloway requested a review from Copilot January 8, 2026 08:42
@jongalloway jongalloway marked this pull request as ready for review January 8, 2026 08:43
Comment on lines +198 to +224
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

This foreach loop
implicitly filters its target sequence
- consider filtering the sequence explicitly using '.Where(...)'.

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:

  1. Use Where to project the sequence of invalid IDs, but only materialize the first invalid ID to preserve short-circuit-like behavior.
  2. Still call ValidateWorkloadId exactly once for that ID so that we can retrieve validationError. We can’t easily get the out parameter from inside the Where predicate without making the code more complex than necessary.
  3. 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 call ValidateWorkloadId(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 enable FirstOrDefault.

This keeps external behavior identical while satisfying the CodeQL recommendation to make the filtering explicit via LINQ.

Suggested changeset 1
DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs

Autofix patch

Autofix patch
Run the following command in your local git repository to apply this patch
cat << 'EOF' | git apply
diff --git a/DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs b/DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs
--- a/DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs
+++ b/DotNetMcp/Tools/Cli/DotNetCliTools.Workload.Consolidated.cs
@@ -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
EOF
@@ -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
Copilot is powered by AI and may make mistakes. Always verify output.
Copy link
Contributor

Copilot AI left a 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 DotnetWorkload method with DotnetWorkloadAction enum parameter supporting 6 actions
  • Shared validation logic via ValidateWorkloadIds helper 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

Comment on lines +198 to +224
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}";
}
}
Copy link

Copilot AI Jan 8, 2026

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(...)'.

Copilot uses AI. Check for mistakes.
Comment on lines 476 to 490
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);
}
Copy link

Copilot AI Jan 8, 2026

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(...)'.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

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)

@jongalloway
Copy link
Owner

@copilot

1 similar comment
@jongalloway
Copy link
Owner

@copilot

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.
@jongalloway
Copy link
Owner

Reviewer note: This PR switches consolidated tools to rely on MCP’s XML-doc → description source generator.

  • The generator only applies tool Description metadata via a generated partial declaration, so consolidated tool methods must be partial to avoid empty tool.Description (this was causing McpConformanceTests.Server_ToolList_ShouldHaveRequiredFields to fail).
  • The generator currently emits minimally-qualified parameter types (e.g., DotnetWorkloadAction) without adding using DotNetMcp.Actions;, so we add a repo-level global using (DotNetMcp/GlobalUsings.cs) to keep the generated file compiling.
  • This keeps tool descriptions derived from XML docs consistently without per-method [Description] attributes.

If you’re reviewing consolidated tools going forward: prefer partial tool methods + XML docs, and avoid reintroducing manual [Description] as a workaround.

@jongalloway jongalloway merged commit aab691d into main Jan 8, 2026
4 checks passed
@jongalloway jongalloway deleted the copilot/add-dotnet-workload-tool branch January 8, 2026 21:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add consolidated tool: dotnet_workload (install/update/list/search/uninstall)

2 participants