From e1df79b31ef1efea5b12260bb9a8652fc58080b7 Mon Sep 17 00:00:00 2001 From: James Wampler Date: Fri, 21 Aug 2026 11:00:25 -0700 Subject: [PATCH] Update docs and solution file for the stdio MCP server's removal The prior commit's git add silently dropped these five files because one path in the same invocation didn't exist. Docs, solution file, and example config still needed the paired update: Novelly.slnx drops the Novelly.Mcp project entry, README and CLAUDE.md describe MCP as an in-API HTTP endpoint instead of a stdio binary, .mcp.json.example uses the type: http shape, and outline-importer.md's tool references are fixed to real tool names. --- .claude/agents/outline-importer.md | 10 +++---- .mcp.json.example | 8 +++--- CLAUDE.md | 11 ++++--- Novelly.slnx | 1 - README.md | 46 +++++++++++++----------------- 5 files changed, 34 insertions(+), 42 deletions(-) diff --git a/.claude/agents/outline-importer.md b/.claude/agents/outline-importer.md index 4d226df..90bf24b 100644 --- a/.claude/agents/outline-importer.md +++ b/.claude/agents/outline-importer.md @@ -1,7 +1,7 @@ --- name: outline-importer description: Imports an author's existing novel outline (chapters + character dossiers, in the Kingdom Sleeps folder format) into a Novelly project over the MCP server. Invoke explicitly with a source folder path — this agent does not run proactively. -tools: Read, Glob, Grep, Write, mcp__novelly__list_projects, mcp__novelly__get_project_brief, mcp__novelly__create_project, mcp__novelly__update_project_brief, mcp__novelly__list_chapters, mcp__novelly__get_chapter, mcp__novelly__create_chapter, mcp__novelly__update_chapter, mcp__novelly__get_chapter_outline, mcp__novelly__create_beat, mcp__novelly__update_beat, mcp__novelly__list_characters, mcp__novelly__get_character, mcp__novelly__create_character, mcp__novelly__update_character, mcp__novelly__get_character_arc, mcp__novelly__add_arc_stage, mcp__novelly__list_tags +tools: Read, Glob, Grep, Write, mcp__novelly__list_novels, mcp__novelly__get_novel_brief, mcp__novelly__create_novel, mcp__novelly__update_novel_brief, mcp__novelly__list_chapters, mcp__novelly__get_chapter, mcp__novelly__create_chapter, mcp__novelly__update_chapter, mcp__novelly__get_chapter_outline, mcp__novelly__create_beat, mcp__novelly__update_beat, mcp__novelly__list_characters, mcp__novelly__get_character, mcp__novelly__create_character, mcp__novelly__update_character, mcp__novelly__get_character_arc, mcp__novelly__add_arc_stage, mcp__novelly__list_tags model: inherit --- @@ -101,9 +101,9 @@ in your final report. Do not skip ahead — each pass depends on ids the previous one minted. If you're picking up a resumed run, jump straight to the first incomplete pass. -**0. Preflight.** Call `list_projects` to confirm the API is reachable at all — if this fails, stop -and tell the user to start the API (`ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api`) -and that `.mcp.json` must point at a published `Novelly.Mcp` binary. Glob the source root for +**0. Preflight.** Call `list_novels` to confirm the API is reachable at all — if this fails, stop +and tell the user to start the API (`ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api`). +Glob the source root for `outline.md`, `outlines/*.md` or `chapters/*.md`, and `characters/*.md`. If `outline.md` is missing, stop — that's the one file every pass depends on. Report the file counts found before proceeding. @@ -111,7 +111,7 @@ proceeding. **1. Project.** Skip if `completedPasses` already has `"project"`. Read `outline.md`. Its heading is `# Outline — (<Author>)` or similar — parse title and author out of it; if there's no author, leave it null. The paragraph(s) before the chapter table are the blurb — pass as `notes` -argument to `create_project` (there's no dedicated blurb field; `synopsis` may be filled in later +argument to `create_novel` (there's no dedicated blurb field; `synopsis` may be filled in later by the author). Record `projectId` in the ledger, mark `"project"` complete. **2. Characters — dossier fields only, not arcs yet.** Skip files whose name (matched diff --git a/.mcp.json.example b/.mcp.json.example index e6ebe4d..ebde340 100644 --- a/.mcp.json.example +++ b/.mcp.json.example @@ -1,10 +1,10 @@ { "mcpServers": { "novelly": { - "command": "./mcp-server/Novelly.Mcp", - "env": { - "NOVELLY_API_URL": "http://localhost:5080", - "NOVELLY_API_KEY": "<matches the API's Auth:ServiceApiKey user secret>" + "type": "http", + "url": "http://localhost:5080/mcp", + "headers": { + "X-Novelly-Api-Key": "<matches the API's Auth:ServiceApiKey user secret>" } } } diff --git a/CLAUDE.md b/CLAUDE.md index 02e8c67..506d1bb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,13 +8,12 @@ Novelly: software plan + write novel. ASP.NET Core 10, C#, TypeScript, React, .N ## Structure -- `src/Novelly.Api/` — whole back end, organised by feature. One folder per feature holds - entity, DTOs, service, endpoints together: `Novels/`, `Characters/`, `Chapters/`, `Beats/`, - `Scenes/`, `Tags/`, `Agent/`. `Common/` holds what crosses features; `Data/` holds - `DbContext` + EF migrations. +- `src/Novelly.Api/` — whole back end, organised by feature, plus the MCP endpoint. One folder + per feature holds entity, DTOs, service, endpoints together: `Novels/`, `Characters/`, + `Chapters/`, `Beats/`, `Scenes/`, `Tags/`, `Agent/`, `Mcp/`. `Common/` holds what crosses + features; `Data/` holds `DbContext` + EF migrations. - `src/Novelly.AppHost/` — .NET Aspire orchestration; run this to bring up API + web client - `src/Novelly.ServiceDefaults/` — shared Aspire wiring: OpenTelemetry, health checks, service discovery -- `src/Novelly.Mcp/` — MCP stdio server - `src/Novelly.Web/` — React + Vite client - `tests/` — test suite - `docs/` — documentation @@ -86,7 +85,7 @@ Build + tests passing ≠ working. Anything touching endpoint, agent loop, or MC Vite dev server on :5173, dashboard for logs + traces - API alone: `ASPNETCORE_URLS=http://localhost:5080 dotnet run --project src/Novelly.Api`, then exercise route with curl - Web alone: `cd src/Novelly.Web && npm run dev` — proxies `/api` to :5080 -- MCP: build it, then drive over stdio JSON-RPC (`initialize` → `notifications/initialized` → `tools/list` → `tools/call`) +- MCP: with the API running, drive `/mcp` over HTTP (`initialize` → `notifications/initialized` → `tools/list` → `tools/call`) — Streamable HTTP, so responses are SSE-framed and requests need `Accept: application/json, text/event-stream` Several real bugs here — SQLite refusing ORDER BY DateTimeOffset, agent's model client throwing at construction + taking read-only endpoints down with it — passed build + test suite, only showed up when app actually ran. diff --git a/Novelly.slnx b/Novelly.slnx index cbee84c..84fabc4 100644 --- a/Novelly.slnx +++ b/Novelly.slnx @@ -8,7 +8,6 @@ <Folder Name="/src/"> <Project Path="src/Novelly.Api/Novelly.Api.csproj" /> <Project Path="src/Novelly.AppHost/Novelly.AppHost.csproj" /> - <Project Path="src/Novelly.Mcp/Novelly.Mcp.csproj" /> <Project Path="src/Novelly.ServiceDefaults/Novelly.ServiceDefaults.csproj" /> <Project Path="src/Novelly.Web/Novelly.Web.esproj"> <Build /> diff --git a/README.md b/README.md index e9f771a..4e2c08b 100644 --- a/README.md +++ b/README.md @@ -6,22 +6,21 @@ Software for planning and writing a novel. You outline the book, keep character dossiers, break chapters into scenes, and draft prose — with a Claude-powered agent -embedded in the app that can read and edit the same data you can, and an MCP server that -exposes that data to Claude Code, Claude Desktop, or any other MCP client. +embedded in the app that can read and edit the same data you can, and an MCP endpoint that +exposes that same data to Claude Code, Claude Desktop, or any other MCP client. The point of the three-way arrangement is that there is exactly one source of truth. The -React UI, the embedded agent, and the MCP server all go through the same REST API, so an -edit made from a chat in Claude Code and an edit made by typing in the browser are the -same edit. +React UI's REST calls, the embedded agent, and MCP clients all resolve to the same +application services in-process, so an edit made from a chat in Claude Code and an edit +made by typing in the browser are the same edit. ## Stack | Piece | Built with | |---|---| -| `Novelly.Api` | ASP.NET Core 10 minimal APIs, EF Core 10 + SQLite, Anthropic SDK, OpenAPI | +| `Novelly.Api` | ASP.NET Core 10 minimal APIs, EF Core 10 + SQLite, Anthropic SDK, OpenAPI, MCP over Streamable HTTP (`ModelContextProtocol.AspNetCore`) | | `Novelly.AppHost` | .NET Aspire orchestration for the API and the web client | | `Novelly.ServiceDefaults` | Shared OpenTelemetry, health checks and service discovery | -| `Novelly.Mcp` | MCP stdio server (`ModelContextProtocol`) | | `Novelly.Web` | React 19, TypeScript, Vite, TanStack Query, Tailwind v4 | The back end is one project organised by feature, not by layer. Each feature folder — @@ -192,19 +191,10 @@ A few deliberate choices worth knowing about: ## The MCP server -A stdio MCP server exposing 45 tools over the same REST API. It holds no domain logic of -its own — it is a second front end, not a second implementation. - -Build it, then point your MCP client at the produced binary: - -```bash -./scripts/publish-mcp.sh -``` - -Aspire does not run or manage this process — it's a separate stdio subprocess your MCP -client spawns directly, so nothing rebuilds it automatically. Re-run the script (and -reconnect your MCP client) after pulling changes that touch `src/Novelly.Mcp`, or it keeps -serving whatever was published last, including against a stale auth contract. +The API itself serves MCP over Streamable HTTP at `POST /mcp`, exposing 45 tools that call +the same application services the REST endpoints and the embedded web agent call — it holds +no domain logic of its own, and there's nothing to build or publish separately. The API +process just needs to be running; there's no separate subprocess to keep in sync with it. Copy `.mcp.json.example` to `.mcp.json` (gitignored, since it carries your API key) and fill in the key: @@ -213,10 +203,10 @@ fill in the key: { "mcpServers": { "novelly": { - "command": "/absolute/path/to/mcp-server/Novelly.Mcp", - "env": { - "NOVELLY_API_URL": "http://localhost:5080", - "NOVELLY_API_KEY": "<matches the API's Auth:ServiceApiKey user secret>" + "type": "http", + "url": "http://localhost:5080/mcp", + "headers": { + "X-Novelly-Api-Key": "<matches the API's Auth:ServiceApiKey user secret>" } } } @@ -225,8 +215,12 @@ fill in the key: The API must be running, with `Auth:ServiceApiKey` set (e.g. via `dotnet user-secrets set Auth:ServiceApiKey <key> -p src/Novelly.Api`) to the same value -as `NOVELLY_API_KEY` above. If the API is not running, or the key is missing or mismatched, -the tools say so in a message the model can act on rather than failing opaquely. +as the `X-Novelly-Api-Key` header above. If the API is not running, or the key is missing or +mismatched, the request 401s. + +Tool argument names are camelCase, matching the REST API and every other MCP argument name +this project has ever used. `create_novel` called over MCP is owned by the seeded service +user (an Admin), not whichever person is signed into the web app. ### Importing an existing outline