using System.ComponentModel; using ClaudeDo.Data.Repositories; using ClaudeDo.Worker.Hub; using ModelContextProtocol.Server; namespace ClaudeDo.Worker.External; public sealed record AddNoteResult(bool Saved, string Id, int AgentNotesToday, int RemainingToday); [McpServerToolType] public sealed class NotesMcpTools { /// Agent-written notes allowed per day. The user's own notes are never counted or /// capped — this only stops an agent from burying the day in its own bullets. public const int DailyAgentCap = 10; /// A note is a line, not a report. Longer output belongs in a task or a finding. public const int MaxLength = 500; private readonly DailyNoteRepository _notes; private readonly HubBroadcaster _broadcaster; public NotesMcpTools(DailyNoteRepository notes, HubBroadcaster broadcaster) { _notes = notes; _broadcaster = broadcaster; } [McpServerTool, Description( "Add one short line to the user's notes for today — the day log they read in ClaudeDo, " + "not a log for you. Write only what the user would want to reread later and would " + "otherwise lose: a decision they made, something you found that changes their plans, " + "something they asked you to remember for the day. Task status, your progress and " + "anything already visible in the task list are noise here. When unsure, don't. " + "Capped at 10 agent notes per day; you cannot edit or delete notes, so a bad one stays " + "until the user removes it.")] public async Task AddNote( [Description("The note itself: one plain sentence, no markdown, no prefix, max 500 characters.")] string text, CancellationToken cancellationToken = default) { var trimmed = (text ?? "").Trim(); if (trimmed.Length == 0) throw new InvalidOperationException("text is required."); if (trimmed.Length > MaxLength) throw new InvalidOperationException( $"Note is {trimmed.Length} characters, the limit is {MaxLength}. Shorten it to one line."); var today = DateOnly.FromDateTime(DateTime.Today); var used = await _notes.CountAgentNotesAsync(today, cancellationToken); if (used >= DailyAgentCap) throw new InvalidOperationException( $"Daily cap reached: {DailyAgentCap} agent notes already written today. " + "Nothing was saved — tell the user instead of retrying."); var note = await _notes.AddAsync(today, trimmed, fromAgent: true, cancellationToken); await _broadcaster.NotesUpdated(); return new AddNoteResult(true, note.Id, used + 1, DailyAgentCap - used - 1); } }