using System.ComponentModel; using ModelContextProtocol.Protocol; using ModelContextProtocol.Server; namespace Novelly.Mcp.Tools; [McpServerToolType] public static class BeatTools { [McpServerTool(Name = "get_chapter_outline")] [Description("Read a chapter's outline: its beats in order. Each beat is one row — a short " + "title, who it belongs to, what happened, and what it sets up. The chapter's " + "summary paragraph and drafted prose sit on the chapter itself, via get_chapter.")] public static Task GetChapterOutline( NovelApiClient api, [Description("The chapter's id.")] Guid chapterId, CancellationToken ct) => api.GetAsync($"/api/chapters/{chapterId}/beats", ct); [McpServerTool(Name = "create_beat")] [Description("Add a beat to a chapter's outline. Keep the title to three to five words — it " + "is a handle, not a sentence; detail belongs in whatHappened and whatsNext.")] public static Task CreateBeat( NovelApiClient api, [Description("The chapter's id.")] Guid chapterId, [Description("Three to five words naming the beat.")] string title, CancellationToken ct, [Description("Position in the chapter. Appended to the end when omitted.")] int? sortOrder = null, [Description("Ids of the characters whose beat this is.")] Guid[]? characterIds = null, [Description("The event itself.")] string? whatHappened = null, [Description("What it sets in motion — the hook into the next beat.")] string? whatsNext = null, [Description("Tags for cross-referencing. Unknown tags are created.")] string[]? tags = null) => api.PostAsync($"/api/chapters/{chapterId}/beats", new { title, sortOrder, characterIds, whatHappened, whatsNext, tags }, ct); [McpServerTool(Name = "update_beat")] [Description("Revise a beat. Only the fields you supply change. Supplying a characterIds or " + "tag list replaces the beat's characters or tags outright — pass an empty list " + "to clear one, and include everything you want to keep.")] public static Task UpdateBeat( NovelApiClient api, [Description("The beat's id.")] Guid beatId, CancellationToken ct, [Description("Three to five words naming the beat.")] string? title = null, [Description("Position in the chapter.")] int? sortOrder = null, [Description("Ids of the characters whose beat this is. Replaces the existing list.")] Guid[]? characterIds = null, [Description("The event itself.")] string? whatHappened = null, [Description("What it sets in motion.")] string? whatsNext = null, [Description("Tags for cross-referencing. Replaces the existing tags.")] string[]? tags = null) => api.PatchAsync($"/api/beats/{beatId}", new { title, sortOrder, characterIds, whatHappened, whatsNext, tags }, ct); [McpServerTool(Name = "delete_beat")] [Description("Remove a beat from a chapter's outline. Confirm with the writer first.")] public static Task DeleteBeat( NovelApiClient api, [Description("The beat's id.")] Guid beatId, CancellationToken ct) => api.DeleteAsync($"/api/beats/{beatId}", ct); [McpServerTool(Name = "assign_character_to_beats")] [Description("Add a character to several beats at once. Leaves each beat's existing characters " + "and other fields alone — this only adds, it never removes.")] public static Task AssignCharacterToBeats( NovelApiClient api, [Description("The chapter's id.")] Guid chapterId, [Description("Id of the character to add.")] Guid characterId, [Description("Ids of the beats to add the character to.")] Guid[] beatIds, CancellationToken ct) => api.PostAsync($"/api/chapters/{chapterId}/beats/assign-character", new { characterId, beatIds }, ct); [McpServerTool(Name = "reorder_beats")] [Description("Renumber a chapter's beats to match the order given. List every beat id in the " + "order wanted; any left out keep their relative position at the end.")] public static Task ReorderBeats( NovelApiClient api, [Description("The chapter's id.")] Guid chapterId, [Description("Beat ids in their new order.")] Guid[] beatIds, CancellationToken ct) => api.PostAsync($"/api/chapters/{chapterId}/beats/reorder", new { beatIds }, ct); [McpServerTool(Name = "move_beats")] [Description("Move one or more beats from one chapter to another, appending them to the " + "target chapter's end in the order given.")] public static Task MoveBeats( NovelApiClient api, [Description("The beats' current chapter id.")] Guid chapterId, [Description("Id of the chapter to move the beats into.")] Guid targetChapterId, [Description("Ids of the beats to move.")] Guid[] beatIds, CancellationToken ct) => api.PostAsync($"/api/chapters/{chapterId}/beats/move", new { targetChapterId, beatIds }, ct); }