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 TaskConfigDto(string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns); public sealed record TaskConfigResult(bool Found, TaskConfigDto? Config); public sealed record SetListConfigResult(bool Ok, string ListId, TaskConfigDto? Config); public sealed record SetTaskConfigResult(bool Ok, string TaskId, TaskConfigDto? Config); [McpServerToolType] public sealed class ConfigMcpTools { private readonly ListRepository _lists; private readonly TaskRepository _tasks; private readonly HubBroadcaster _broadcaster; public ConfigMcpTools(ListRepository lists, TaskRepository tasks, HubBroadcaster broadcaster) { _lists = lists; _tasks = tasks; _broadcaster = broadcaster; } [McpServerTool, Description("Get a list's default config (model, system prompt, agent path). Returns { found: false, config: null } if no config is set.")] public async Task GetListConfig(string listId, CancellationToken cancellationToken) { var cfg = await _lists.GetConfigAsync(listId, cancellationToken); return cfg is null ? new TaskConfigResult(false, null) : new TaskConfigResult(true, new TaskConfigDto(cfg.Model, cfg.SystemPrompt, cfg.AgentPath, cfg.MaxTurns)); } [McpServerTool, Description( "Set a list's default model/system prompt/agent path/max turns. Passing all four as null clears the list " + "config. Returns { ok, listId, config } — config is null when the config was cleared, otherwise it echoes " + "the fields that were set (a field is null there if it was individually left unset/cleared).")] public async Task SetListConfig( string listId, string? model = null, string? systemPrompt = null, string? agentPath = null, int? maxTurns = null, CancellationToken cancellationToken = default) { _ = await _lists.GetByIdAsync(listId, cancellationToken) ?? throw new InvalidOperationException($"List {listId} not found."); var m = model.NullIfBlank(); var sp = systemPrompt.NullIfBlank(); var ap = agentPath.NullIfBlank(); TaskConfigDto? config; if (m is null && sp is null && ap is null && maxTurns is null) { await _lists.DeleteConfigAsync(listId, cancellationToken); config = null; } else { await _lists.SetConfigAsync(new ListConfigEntity { ListId = listId, Model = m, SystemPrompt = sp, AgentPath = ap, MaxTurns = maxTurns, }, cancellationToken); config = new TaskConfigDto(m, sp, ap, maxTurns); } await _broadcaster.ListUpdated(listId); return new SetListConfigResult(true, listId, config); } [McpServerTool, Description( "Set per-task config overrides (model/system prompt/agent path/max turns). Pass null for any field to " + "clear that override. Returns { ok, taskId, config } — config echoes the resulting overrides (a field is " + "null there if it was cleared or never set).")] public async Task SetTaskConfig( string taskId, string? model = null, string? systemPrompt = null, string? agentPath = null, int? maxTurns = null, CancellationToken cancellationToken = default) { _ = await _tasks.GetByIdAsync(taskId, cancellationToken) ?? throw new InvalidOperationException($"Task {taskId} not found."); var m = model.NullIfBlank(); var sp = systemPrompt.NullIfBlank(); var ap = agentPath.NullIfBlank(); await _tasks.UpdateAgentSettingsAsync(taskId, m, sp, ap, maxTurns, ct: cancellationToken); await _broadcaster.TaskUpdated(taskId); return new SetTaskConfigResult(true, taskId, new TaskConfigDto(m, sp, ap, maxTurns)); } [McpServerTool, Description("Get per-task config overrides (model/system prompt/agent path/max turns). Returns { found: false, config: null } if no override is set on this task.")] public async Task GetTaskConfig(string taskId, CancellationToken cancellationToken) { var task = await _tasks.GetByIdAsync(taskId, cancellationToken) ?? throw new InvalidOperationException($"Task {taskId} not found."); if (task.Model is null && task.SystemPrompt is null && task.AgentPath is null && task.MaxTurns is null) return new TaskConfigResult(false, null); return new TaskConfigResult(true, new TaskConfigDto(task.Model, task.SystemPrompt, task.AgentPath, task.MaxTurns)); } }