Group character beats into arc-stage sections; reciprocal relationship types
Arc stages now group the beats that establish or pay off that stage of a
character's arc (many-to-many via ArcStageBeats), and carry a Result field
(renamed from Description) describing what the stage results in for the
character. Assigning a beat to a stage moves it out of any other stage of
the same character. New endpoint POST /api/arc-stages/{id}/beats, MCP tool
set_arc_stage_beats, and frontend grouping UI in CharacterArc/CharacterBeats.
Also records a relationship's reciprocal type so both characters' dossiers
show the correct direction (e.g. "sister" / "brother") instead of mirroring
the same label.
This commit is contained in:
@@ -147,11 +147,11 @@ public static class CharacterTools
|
||||
[Description("Id of the character whose arc to add to.")] Guid characterId,
|
||||
[Description("A short handle for the change, three to five words.")] string title,
|
||||
CancellationToken ct,
|
||||
[Description("What shifts in the character here, and what it costs them.")] string? description = null,
|
||||
[Description("What this stage of the arc results in for the character — what shifts, and what it costs them.")] string? result = null,
|
||||
[Description("Id of the chapter where this stage lands, if it is pinned to one.")] Guid? chapterId = null,
|
||||
[Description("Position in the arc. Appended to the end when omitted.")] int? sortOrder = null) =>
|
||||
api.PostAsync($"/api/characters/{characterId}/arc",
|
||||
new { title, sortOrder, description, chapterId }, ct);
|
||||
new { title, sortOrder, result, chapterId }, ct);
|
||||
|
||||
[McpServerTool(Name = "update_arc_stage")]
|
||||
[Description("Revise a stage of a character's arc. Only the fields you supply change.")]
|
||||
@@ -160,11 +160,11 @@ public static class CharacterTools
|
||||
[Description("The arc stage's id.")] Guid arcStageId,
|
||||
CancellationToken ct,
|
||||
[Description("New title for the stage.")] string? title = null,
|
||||
[Description("What shifts in the character here.")] string? description = null,
|
||||
[Description("What this stage of the arc results in for the character.")] string? result = null,
|
||||
[Description("Id of the chapter where this stage lands.")] Guid? chapterId = null,
|
||||
[Description("Position in the arc.")] int? sortOrder = null) =>
|
||||
api.PatchAsync($"/api/arc-stages/{arcStageId}",
|
||||
new { title, sortOrder, description, chapterId }, ct);
|
||||
new { title, sortOrder, result, chapterId }, ct);
|
||||
|
||||
[McpServerTool(Name = "delete_arc_stage")]
|
||||
[Description("Remove a stage from a character's arc.")]
|
||||
@@ -184,17 +184,34 @@ public static class CharacterTools
|
||||
CancellationToken ct) =>
|
||||
api.PostAsync($"/api/characters/{characterId}/arc/reorder", new { stageIds }, ct);
|
||||
|
||||
[McpServerTool(Name = "set_arc_stage_beats")]
|
||||
[Description("Set which beats belong to an arc stage, replacing its current set. This groups the "
|
||||
+ "chapter-level beats that establish or pay off this stage of the character's arc. A "
|
||||
+ "beat moved into this stage leaves any other stage of the same character it was in. "
|
||||
+ "Each beat must already include this character.")]
|
||||
public static Task<CallToolResult> SetArcStageBeats(
|
||||
NovelApiClient api,
|
||||
[Description("The arc stage's id.")] Guid arcStageId,
|
||||
[Description("Beat ids that belong to this stage, replacing whatever was there before.")] string[] beatIds,
|
||||
CancellationToken ct) =>
|
||||
api.PostAsync($"/api/arc-stages/{arcStageId}/beats", new { beatIds }, ct);
|
||||
|
||||
[McpServerTool(Name = "relate_characters")]
|
||||
[Description("Record a relationship from one character to another in the same project.")]
|
||||
[Description("Record a relationship between two characters in the same project. Creates both directions "
|
||||
+ "at once — characterId's side and relatedCharacterId's side — so the pair always shows up "
|
||||
+ "on both dossiers.")]
|
||||
public static Task<CallToolResult> RelateCharacters(
|
||||
NovelApiClient api,
|
||||
[Description("Id of the character the relationship belongs to.")] Guid characterId,
|
||||
[Description("Id of the character they are related to.")] Guid relatedCharacterId,
|
||||
[Description("How they are related, e.g. 'sister', 'rival', 'former mentor'.")] string relationshipType,
|
||||
[Description("How characterId is related to relatedCharacterId, e.g. 'sister', 'rival', 'former mentor'.")] string relationshipType,
|
||||
CancellationToken ct,
|
||||
[Description("How relatedCharacterId is related back to characterId, if different — e.g. 'brother' for "
|
||||
+ "'sister'. Defaults to relationshipType when the relation is symmetric, like 'rival'.")]
|
||||
string? reciprocalRelationshipType = null,
|
||||
[Description("What the relationship is like, and where it is headed.")] string? description = null) =>
|
||||
api.PostAsync($"/api/characters/{characterId}/relationships",
|
||||
new { relatedCharacterId, relationshipType, description }, ct);
|
||||
new { relatedCharacterId, relationshipType, reciprocalRelationshipType, description }, ct);
|
||||
|
||||
[McpServerTool(Name = "link_character_identity")]
|
||||
[Description("Record that this character is really another character — e.g. a character introduced "
|
||||
|
||||
Reference in New Issue
Block a user