Renames the domain concept from Project to Novel throughout the backend (entities, DTOs, services, endpoints, ProjectAccessService/Permission, ProjectId foreign keys), MCP server (tool names and routes), and the React/Vite frontend (types, hooks, routes, components). Adds a new EF Core migration (RenameProjectToNovel) using RenameTable/RenameColumn to preserve existing data instead of dropping/recreating tables. Updates CLAUDE.md's structure section to reference Novels/ instead of Projects/.
93 lines
4.7 KiB
C#
93 lines
4.7 KiB
C#
using System.ComponentModel;
|
|
using ModelContextProtocol.Protocol;
|
|
using ModelContextProtocol.Server;
|
|
|
|
namespace Novelly.Mcp.Tools;
|
|
|
|
[McpServerToolType]
|
|
public static class QuestionTools
|
|
{
|
|
[McpServerTool(Name = "list_open_questions")]
|
|
[Description("The decisions the writer has not made yet, newest first. Read this before "
|
|
+ "proposing changes — an open question marks somewhere the writer is still "
|
|
+ "thinking, not a gap to fill in for them.")]
|
|
public static Task<CallToolResult> ListOpenQuestions(
|
|
NovelApiClient api,
|
|
[Description("The novel's id.")] Guid novelId,
|
|
CancellationToken ct,
|
|
[Description("Narrow to questions about one chapter outline.")] Guid? chapterId = null,
|
|
[Description("Narrow to questions about one character.")] Guid? characterId = null,
|
|
[Description("Include questions already settled. Defaults to false.")] bool includeResolved = false)
|
|
{
|
|
var query = new List<string> { $"includeResolved={includeResolved.ToString().ToLowerInvariant()}" };
|
|
|
|
if (chapterId is { } chapter)
|
|
{
|
|
query.Add($"chapterId={chapter}");
|
|
}
|
|
|
|
if (characterId is { } character)
|
|
{
|
|
query.Add($"characterId={character}");
|
|
}
|
|
|
|
return api.GetAsync($"/api/novels/{novelId}/questions?{string.Join('&', query)}", ct);
|
|
}
|
|
|
|
[McpServerTool(Name = "raise_open_question")]
|
|
[Description("Record a question the writer has not settled, attached to the chapter outline "
|
|
+ "and/or the character it is about. Prefer raising a question over guessing.")]
|
|
public static Task<CallToolResult> RaiseOpenQuestion(
|
|
NovelApiClient api,
|
|
[Description("The novel's id.")] Guid novelId,
|
|
[Description("The question, in one line.")] string question,
|
|
CancellationToken ct,
|
|
[Description("The thinking around it — options considered, and what each costs.")] string? detail = null,
|
|
[Description("Id of the chapter outline this is about, if any.")] Guid? chapterId = null,
|
|
[Description("Id of the character this is about, if any.")] Guid? characterId = null) =>
|
|
api.PostAsync($"/api/novels/{novelId}/questions",
|
|
new { question, detail, chapterId, characterId }, ct);
|
|
|
|
[McpServerTool(Name = "update_open_question")]
|
|
[Description("Revise a question or change what it is attached to. Only the fields you supply change.")]
|
|
public static Task<CallToolResult> UpdateOpenQuestion(
|
|
NovelApiClient api,
|
|
[Description("The question's id.")] Guid questionId,
|
|
CancellationToken ct,
|
|
[Description("New wording for the question.")] string? question = null,
|
|
[Description("New detail. Pass an empty string to clear it.")] string? detail = null,
|
|
[Description("Attach to this chapter outline.")] Guid? chapterId = null,
|
|
[Description("Attach to this character.")] Guid? characterId = null,
|
|
[Description("Detach from its chapter.")] bool clearChapter = false,
|
|
[Description("Detach from its character.")] bool clearCharacter = false) =>
|
|
api.PatchAsync($"/api/questions/{questionId}",
|
|
new { question, detail, chapterId, characterId, clearChapter, clearCharacter }, ct);
|
|
|
|
[McpServerTool(Name = "resolve_open_question")]
|
|
[Description("Settle a question with what the writer decided. Set appendToNotes to also write "
|
|
+ "the resolution into the notes of the chapter and character it hangs off.")]
|
|
public static Task<CallToolResult> ResolveOpenQuestion(
|
|
NovelApiClient api,
|
|
[Description("The question's id.")] Guid questionId,
|
|
[Description("What was decided.")] string resolution,
|
|
CancellationToken ct,
|
|
[Description("Also append the resolution to the associated notes.")] bool appendToNotes = false) =>
|
|
api.PostAsync($"/api/questions/{questionId}/resolve", new { resolution, appendToNotes }, ct);
|
|
|
|
[McpServerTool(Name = "reopen_question")]
|
|
[Description("Put a resolved question back on the list. Anything already appended to notes stays.")]
|
|
public static Task<CallToolResult> ReopenQuestion(
|
|
NovelApiClient api,
|
|
[Description("The question's id.")] Guid questionId,
|
|
CancellationToken ct) =>
|
|
api.PostAsync($"/api/questions/{questionId}/reopen", new { }, ct);
|
|
|
|
[McpServerTool(Name = "delete_open_question")]
|
|
[Description("Delete a question outright. Resolving is usually better — it keeps the decision.")]
|
|
public static Task<CallToolResult> DeleteOpenQuestion(
|
|
NovelApiClient api,
|
|
[Description("The question's id.")] Guid questionId,
|
|
CancellationToken ct) =>
|
|
api.DeleteAsync($"/api/questions/{questionId}", ct);
|
|
}
|