using System.ComponentModel; using ColaFlow.Modules.Mcp.Application.DTOs; using ColaFlow.Modules.Mcp.Application.Services; using ColaFlow.Modules.Mcp.Domain.Exceptions; using ColaFlow.Modules.Mcp.Domain.Services; using ColaFlow.Modules.IssueManagement.Domain.Enums; using ColaFlow.Modules.IssueManagement.Domain.Repositories; using Microsoft.Extensions.Logging; using ModelContextProtocol.Server; namespace ColaFlow.Modules.Mcp.Application.SdkTools; /// /// MCP Tool: update_status (SDK-based implementation) /// Updates the status of an existing Issue /// Generates a Diff Preview and creates a PendingChange for approval /// [McpServerToolType] public class UpdateStatusSdkTool { private readonly IPendingChangeService _pendingChangeService; private readonly IIssueRepository _issueRepository; private readonly DiffPreviewService _diffPreviewService; private readonly ILogger _logger; public UpdateStatusSdkTool( IPendingChangeService pendingChangeService, IIssueRepository issueRepository, DiffPreviewService diffPreviewService, ILogger logger) { _pendingChangeService = pendingChangeService ?? throw new ArgumentNullException(nameof(pendingChangeService)); _issueRepository = issueRepository ?? throw new ArgumentNullException(nameof(issueRepository)); _diffPreviewService = diffPreviewService ?? throw new ArgumentNullException(nameof(diffPreviewService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } [McpServerTool] [Description("Update the status of an existing issue. Supports workflow transitions (Backlog → Todo → InProgress → Done). Requires human approval before being applied.")] public async Task UpdateStatusAsync( [Description("The ID of the issue to update")] Guid issueId, [Description("The new status: Backlog, Todo, InProgress, or Done")] string newStatus, CancellationToken cancellationToken = default) { try { _logger.LogInformation("Executing update_status tool (SDK)"); // 1. Validate and parse status if (!Enum.TryParse(newStatus, ignoreCase: true, out var statusEnum)) throw new McpInvalidParamsException($"Invalid status: {newStatus}. Must be Backlog, Todo, InProgress, or Done"); // 2. Fetch current issue var issue = await _issueRepository.GetByIdAsync(issueId, cancellationToken); if (issue == null) throw new McpNotFoundException("Issue", issueId.ToString()); var oldStatus = issue.Status; // 3. Build before and after data for diff preview var beforeData = new { id = issue.Id, title = issue.Title, type = issue.Type.ToString(), status = oldStatus.ToString(), priority = issue.Priority.ToString() }; var afterData = new { id = issue.Id, title = issue.Title, type = issue.Type.ToString(), status = statusEnum.ToString(), // Only status changed priority = issue.Priority.ToString() }; // 4. Generate Diff Preview (UPDATE operation) var diff = _diffPreviewService.GenerateUpdateDiff( entityType: "Issue", entityId: issueId, beforeEntity: beforeData, afterEntity: afterData, entityKey: $"{issue.Type}-{issue.Id.ToString().Substring(0, 8)}" ); // 5. Create PendingChange var pendingChange = await _pendingChangeService.CreateAsync( new CreatePendingChangeRequest { ToolName = "update_status", Diff = diff, ExpirationHours = 24 }, cancellationToken); _logger.LogInformation( "PendingChange created: {PendingChangeId} - UPDATE Issue {IssueId} status: {OldStatus} → {NewStatus}", pendingChange.Id, issueId, oldStatus, statusEnum); // 6. Return pendingChangeId to AI return $"Issue status update request submitted for approval.\n\n" + $"**Pending Change ID**: {pendingChange.Id}\n" + $"**Status**: Pending Approval\n" + $"**Issue**: {issue.Title}\n" + $"**Old Status**: {oldStatus}\n" + $"**New Status**: {statusEnum}\n\n" + $"A human user must approve this change before the issue status is updated. " + $"The change will expire at {pendingChange.ExpiresAt:yyyy-MM-dd HH:mm} UTC if not approved."; } catch (McpException) { throw; // Re-throw MCP exceptions as-is } catch (Exception ex) { _logger.LogError(ex, "Error executing update_status tool (SDK)"); throw new McpInvalidParamsException($"Error updating issue status: {ex.Message}"); } } }