Update docs and solution file for the stdio MCP server's removal
CI / build-and-push (push) Successful in 47s
CI / deploy (push) Successful in 10s

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.
This commit is contained in:
James Wampler
2026-08-21 11:00:25 -07:00
parent 2d6bb9fc57
commit e1df79b31e
5 changed files with 34 additions and 42 deletions
+5 -5
View File
@@ -1,7 +1,7 @@
--- ---
name: outline-importer 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. 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 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 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. 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 **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`) 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 Glob the source root for
`outline.md`, `outlines/*.md` or `chapters/*.md`, and `characters/*.md`. If `outline.md` is `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 missing, stop — that's the one file every pass depends on. Report the file counts found before
proceeding. proceeding.
@@ -111,7 +111,7 @@ proceeding.
**1. Project.** Skip if `completedPasses` already has `"project"`. Read `outline.md`. Its heading is **1. Project.** Skip if `completedPasses` already has `"project"`. Read `outline.md`. Its heading is
`# Outline — <Title> (<Author>)` or similar — parse title and author out of it; if there's no `# Outline — <Title> (<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` 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. by the author). Record `projectId` in the ledger, mark `"project"` complete.
**2. Characters — dossier fields only, not arcs yet.** Skip files whose name (matched **2. Characters — dossier fields only, not arcs yet.** Skip files whose name (matched
+4 -4
View File
@@ -1,10 +1,10 @@
{ {
"mcpServers": { "mcpServers": {
"novelly": { "novelly": {
"command": "./mcp-server/Novelly.Mcp", "type": "http",
"env": { "url": "http://localhost:5080/mcp",
"NOVELLY_API_URL": "http://localhost:5080", "headers": {
"NOVELLY_API_KEY": "<matches the API's Auth:ServiceApiKey user secret>" "X-Novelly-Api-Key": "<matches the API's Auth:ServiceApiKey user secret>"
} }
} }
} }
+5 -6
View File
@@ -8,13 +8,12 @@ Novelly: software plan + write novel. ASP.NET Core 10, C#, TypeScript, React, .N
## Structure ## Structure
- `src/Novelly.Api/` — whole back end, organised by feature. One folder per feature holds - `src/Novelly.Api/` — whole back end, organised by feature, plus the MCP endpoint. One folder
entity, DTOs, service, endpoints together: `Novels/`, `Characters/`, `Chapters/`, `Beats/`, per feature holds entity, DTOs, service, endpoints together: `Novels/`, `Characters/`,
`Scenes/`, `Tags/`, `Agent/`. `Common/` holds what crosses features; `Data/` holds `Chapters/`, `Beats/`, `Scenes/`, `Tags/`, `Agent/`, `Mcp/`. `Common/` holds what crosses
`DbContext` + EF migrations. features; `Data/` holds `DbContext` + EF migrations.
- `src/Novelly.AppHost/` — .NET Aspire orchestration; run this to bring up API + web client - `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.ServiceDefaults/` — shared Aspire wiring: OpenTelemetry, health checks, service discovery
- `src/Novelly.Mcp/` — MCP stdio server
- `src/Novelly.Web/` — React + Vite client - `src/Novelly.Web/` — React + Vite client
- `tests/` — test suite - `tests/` — test suite
- `docs/` — documentation - `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 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 - 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 - 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. 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.
-1
View File
@@ -8,7 +8,6 @@
<Folder Name="/src/"> <Folder Name="/src/">
<Project Path="src/Novelly.Api/Novelly.Api.csproj" /> <Project Path="src/Novelly.Api/Novelly.Api.csproj" />
<Project Path="src/Novelly.AppHost/Novelly.AppHost.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.ServiceDefaults/Novelly.ServiceDefaults.csproj" />
<Project Path="src/Novelly.Web/Novelly.Web.esproj"> <Project Path="src/Novelly.Web/Novelly.Web.esproj">
<Build /> <Build />
+20 -26
View File
@@ -6,22 +6,21 @@
Software for planning and writing a novel. You outline the book, keep character 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 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 embedded in the app that can read and edit the same data you can, and an MCP endpoint that
exposes that data to Claude Code, Claude Desktop, or any other MCP client. 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 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 React UI's REST calls, the embedded agent, and MCP clients all resolve to the same
edit made from a chat in Claude Code and an edit made by typing in the browser are the application services in-process, so an edit made from a chat in Claude Code and an edit
same edit. made by typing in the browser are the same edit.
## Stack ## Stack
| Piece | Built with | | 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.AppHost` | .NET Aspire orchestration for the API and the web client |
| `Novelly.ServiceDefaults` | Shared OpenTelemetry, health checks and service discovery | | `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 | | `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 — 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 ## The MCP server
A stdio MCP server exposing 45 tools over the same REST API. It holds no domain logic of The API itself serves MCP over Streamable HTTP at `POST /mcp`, exposing 45 tools that call
its own — it is a second front end, not a second implementation. 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
Build it, then point your MCP client at the produced binary: process just needs to be running; there's no separate subprocess to keep in sync with it.
```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.
Copy `.mcp.json.example` to `.mcp.json` (gitignored, since it carries your API key) and Copy `.mcp.json.example` to `.mcp.json` (gitignored, since it carries your API key) and
fill in the key: fill in the key:
@@ -213,10 +203,10 @@ fill in the key:
{ {
"mcpServers": { "mcpServers": {
"novelly": { "novelly": {
"command": "/absolute/path/to/mcp-server/Novelly.Mcp", "type": "http",
"env": { "url": "http://localhost:5080/mcp",
"NOVELLY_API_URL": "http://localhost:5080", "headers": {
"NOVELLY_API_KEY": "<matches the API's Auth:ServiceApiKey user secret>" "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 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 `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, as the `X-Novelly-Api-Key` header above. If the API is not running, or the key is missing or
the tools say so in a message the model can act on rather than failing opaquely. 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 ### Importing an existing outline