Replace chapter setting with multi-select locations; add chapter character summary

Chapters now carry many Locations (new Tags-style entity with cross-referencing)
instead of a single free-text Setting field, with a Locations tab on the novel
for browsing them and seeing every chapter set at each one. Also surfaces the
distinct characters appearing in a chapter's beats, linked, under the beat/word
count on the outline tab.
This commit is contained in:
James Wampler
2026-08-18 11:36:13 -07:00
parent 4313c8f206
commit c620ddd626
27 changed files with 2380 additions and 44 deletions
+53
View File
@@ -0,0 +1,53 @@
using System.ComponentModel;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
namespace Novelly.Mcp.Tools;
[McpServerToolType]
public static class LocationTools
{
[McpServerTool(Name = "list_locations")]
[Description("List a novel's locations with how many chapters are set there. "
+ "Read this before inventing a new location so you reuse the writer's vocabulary.")]
public static Task<CallToolResult> ListLocations(
NovelApiClient api,
[Description("The novel's id.")] Guid novelId,
CancellationToken ct) =>
api.GetAsync($"/api/novels/{novelId}/locations", ct);
[McpServerTool(Name = "get_location_references")]
[Description("Cross-reference a location: every chapter set there.")]
public static Task<CallToolResult> GetLocationReferences(
NovelApiClient api,
[Description("The location's id.")] Guid locationId,
CancellationToken ct) =>
api.GetAsync($"/api/locations/{locationId}/references", ct);
[McpServerTool(Name = "create_location")]
[Description("Create a location explicitly. Applying an unknown location by name to a chapter "
+ "also creates it, so this is only needed to set one up ahead of time.")]
public static Task<CallToolResult> CreateLocation(
NovelApiClient api,
[Description("The novel's id.")] Guid novelId,
[Description("The location's name. Unique within the novel, matched case-insensitively.")] string name,
CancellationToken ct) =>
api.PostAsync($"/api/novels/{novelId}/locations", new { name }, ct);
[McpServerTool(Name = "update_location")]
[Description("Rename a location. Renaming updates it everywhere it is applied.")]
public static Task<CallToolResult> UpdateLocation(
NovelApiClient api,
[Description("The location's id.")] Guid locationId,
[Description("New name.")] string name,
CancellationToken ct) =>
api.PatchAsync($"/api/locations/{locationId}", new { name }, ct);
[McpServerTool(Name = "delete_location")]
[Description("Delete a location. Whatever carried it is left alone — only the label goes.")]
public static Task<CallToolResult> DeleteLocation(
NovelApiClient api,
[Description("The location's id.")] Guid locationId,
CancellationToken ct) =>
api.DeleteAsync($"/api/locations/{locationId}", ct);
}
+5 -5
View File
@@ -32,7 +32,7 @@ public static class ManuscriptTools
CancellationToken ct,
[Description("Position in the manuscript, 1-based.")] int? number = null,
[Description("The chapter's outline summary paragraph.")] string? summary = null,
[Description("Where and when the chapter takes place.")] string? setting = null,
[Description("Where and when the chapter takes place. Unknown locations are created.")] string[]? locations = null,
[Description("Planned, Outlined, Drafted, Revised or Final.")] string? status = null,
[Description("Target length in words.")] int? targetWordCount = null,
[Description("The chapter's drafted text, in markdown, if you are writing it now.")] string? prose = null,
@@ -42,7 +42,7 @@ public static class ManuscriptTools
title,
number,
summary,
setting,
locations,
status = status ?? "Planned",
targetWordCount,
prose,
@@ -50,7 +50,7 @@ public static class ManuscriptTools
}, ct);
[McpServerTool(Name = "update_chapter")]
[Description("Revise a chapter's title, number, summary, setting, notes, status "
[Description("Revise a chapter's title, number, summary, locations, notes, status "
+ "or drafted prose. Use 'prose' to write or replace the chapter's draft text in "
+ "markdown; the word count is recomputed automatically.")]
public static Task<CallToolResult> UpdateChapter(
@@ -60,12 +60,12 @@ public static class ManuscriptTools
[Description("New title.")] string? title = null,
[Description("Position in the manuscript.")] int? number = null,
[Description("The chapter's outline summary paragraph.")] string? summary = null,
[Description("Where and when the chapter takes place.")] string? setting = null,
[Description("Where and when the chapter takes place. Replaces the existing locations. Unknown locations are created.")] string[]? locations = null,
[Description("Anything else worth recording.")] string? notes = null,
[Description("Planned, Outlined, Drafted, Revised or Final.")] string? status = null,
[Description("Target length in words.")] int? targetWordCount = null,
[Description("The chapter's drafted text, in markdown.")] string? prose = null,
[Description("Tags for cross-referencing. Replaces the existing tags.")] string[]? tags = null) =>
api.PatchAsync($"/api/chapters/{chapterId}",
new { title, number, summary, setting, notes, status, targetWordCount, prose, tags }, ct);
new { title, number, summary, locations, notes, status, targetWordCount, prose, tags }, ct);
}