using System.ComponentModel; using ClaudeDo.Data.Models; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Hub; using ModelContextProtocol.Server; namespace ClaudeDo.Worker.External; public sealed record ListSummaryDto(string Id, string Name, string? WorkingDir, string DefaultCommitType); public sealed record DeleteListResult(bool Deleted, string Id); [McpServerToolType] public sealed class ListMcpTools { private readonly ListRepository _lists; private readonly HubBroadcaster _broadcaster; public ListMcpTools(ListRepository lists, HubBroadcaster broadcaster) { _lists = lists; _broadcaster = broadcaster; } [McpServerTool, Description("Create a new task list. workingDir sets the git repo tasks run against; commitType defaults to 'chore'.")] public async Task CreateList( string name, string? workingDir = null, string? commitType = null, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(name)) throw new InvalidOperationException("name is required."); var entity = new ListEntity { Id = Guid.NewGuid().ToString(), Name = name, WorkingDir = workingDir.NullIfBlank(), DefaultCommitType = commitType.NullIfBlank() ?? CommitTypeRegistry.DefaultType, CreatedAt = DateTime.UtcNow, }; await _lists.AddAsync(entity, cancellationToken); await _broadcaster.ListUpdated(entity.Id); return ToDto(entity); } [McpServerTool, Description("Rename a list and/or change its working dir and default commit type. Pass null to leave a field unchanged.")] public async Task UpdateList( string listId, string? name = null, string? workingDir = null, string? commitType = null, CancellationToken cancellationToken = default) { var entity = await _lists.GetByIdAsync(listId, cancellationToken) ?? throw new InvalidOperationException($"List {listId} not found."); if (name is not null && string.IsNullOrWhiteSpace(name)) throw new InvalidOperationException("name cannot be blank."); if (name is not null) entity.Name = name; if (workingDir is not null) entity.WorkingDir = workingDir.NullIfBlank(); if (commitType is not null) entity.DefaultCommitType = commitType.NullIfBlank() ?? CommitTypeRegistry.DefaultType; await _lists.UpdateAsync(entity, cancellationToken); await _broadcaster.ListUpdated(listId); return ToDto(entity); } [McpServerTool, Description("Delete a list and its tasks. Irreversible. Returns { deleted: true, id } on success.")] public async Task DeleteList(string listId, CancellationToken cancellationToken) { _ = await _lists.GetByIdAsync(listId, cancellationToken) ?? throw new InvalidOperationException($"List {listId} not found."); await _lists.DeleteAsync(listId, cancellationToken); await _broadcaster.ListUpdated(listId); return new DeleteListResult(true, listId); } private static ListSummaryDto ToDto(ListEntity l) => new(l.Id, l.Name, l.WorkingDir, l.DefaultCommitType); }