@hanna84/mcp-writing 1.3.7 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +167 -26
- package/index.js +40 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.0](https://github.com/hannasdev/mcp-writing/compare/v1.3.8...v1.4.0) (2026-04-18)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add runtime write-access diagnostics for onboarding ([434c453](https://github.com/hannasdev/mcp-writing/commit/434c45323b7662312cff8cc24e2c8ee2c0fd8745))
|
|
9
|
+
|
|
10
|
+
## [1.3.8](https://github.com/hannasdev/mcp-writing/compare/v1.3.7...v1.3.8) (2026-04-18)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
### Miscellaneous Chores
|
|
14
|
+
|
|
15
|
+
* enable auto-merge for Release Please version PRs ([#24](https://github.com/hannasdev/mcp-writing/issues/24)) ([07981af](https://github.com/hannasdev/mcp-writing/commit/07981af84c4029cb25aef1947f301eb172df3d3a))
|
|
16
|
+
|
|
3
17
|
## [1.3.7](https://github.com/hannasdev/mcp-writing/compare/v1.3.6...v1.3.7) (2026-04-18)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -32,6 +32,21 @@ npm --version # should be 8.0.0 or later
|
|
|
32
32
|
git --version # should be installed
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
+
## First-time setup path (recommended)
|
|
36
|
+
|
|
37
|
+
If this is your first time, use this path and skip the advanced/reference sections for now:
|
|
38
|
+
|
|
39
|
+
1. Follow either **Quick start with Scrivener** or **Running with Docker**.
|
|
40
|
+
2. Start the server with `npm start`.
|
|
41
|
+
3. Run **Verify your setup** (`/healthz` and `/sse`).
|
|
42
|
+
4. Use the MCP `sync` tool once to build the index.
|
|
43
|
+
|
|
44
|
+
After that, come back to:
|
|
45
|
+
|
|
46
|
+
- **Advanced: Native sync format** for custom project layouts
|
|
47
|
+
- **Reference: Available tools** for the full tool catalog
|
|
48
|
+
- **Appendix: Real-world usage scenarios** for workflow ideas
|
|
49
|
+
|
|
35
50
|
## Quick start with Scrivener
|
|
36
51
|
|
|
37
52
|
If you write in [Scrivener](https://www.literatureandlatte.com/scrivener), you can seed `mcp-writing` from a Scrivener external-sync export for scene prose, then curate non-draft content directly into the target folder structure.
|
|
@@ -51,6 +66,8 @@ The importer:
|
|
|
51
66
|
- Converts `Draft/` files to scene sidecars (`.meta.yaml`) with auto-generated `scene_id`, `title`, `part`, `chapter`, and `save_the_cat_beat` fields derived from the filename/structure.
|
|
52
67
|
- Skips beat-marker files (`-Setup-`, `-Catalyst-`, etc.), chapter-intro files, epigraphs, and trashed files.
|
|
53
68
|
|
|
69
|
+
Important: `sync` does not run this import step for you. If your source is a raw Scrivener `Draft/` export, run `scripts/import.js` first so scene files get `scene_id` metadata before indexing.
|
|
70
|
+
|
|
54
71
|
Non-draft content is not inferred from `Notes/`. Put it directly into the target sync dir using the `world/` folder conventions described below.
|
|
55
72
|
|
|
56
73
|
### 3. Start the server
|
|
@@ -61,7 +78,7 @@ WRITING_SYNC_DIR=/path/to/sync-dir DB_PATH=./writing.db npm start
|
|
|
61
78
|
|
|
62
79
|
You should see:
|
|
63
80
|
|
|
64
|
-
```
|
|
81
|
+
```sh
|
|
65
82
|
Listening on port 3000
|
|
66
83
|
Sync dir: /path/to/sync-dir
|
|
67
84
|
Database: ./writing.db
|
|
@@ -79,7 +96,7 @@ Exits non-zero if any errors are found. Warnings (e.g. `UNKNOWN_KEY`) are inform
|
|
|
79
96
|
|
|
80
97
|
---
|
|
81
98
|
|
|
82
|
-
## Native sync format
|
|
99
|
+
## Advanced: Native sync format
|
|
83
100
|
|
|
84
101
|
For projects not starting from a Scrivener export, place plain `.md` files in the sync folder directly. Metadata lives in a YAML frontmatter block.
|
|
85
102
|
|
|
@@ -177,7 +194,7 @@ Recommended workflow:
|
|
|
177
194
|
|
|
178
195
|
---
|
|
179
196
|
|
|
180
|
-
## Real-world usage scenarios
|
|
197
|
+
## Appendix: Real-world usage scenarios
|
|
181
198
|
|
|
182
199
|
The tool list is useful as reference. These example workflows show how people actually use `mcp-writing` while drafting and revising.
|
|
183
200
|
|
|
@@ -227,7 +244,7 @@ Outcome: you get AI speed with explicit approval and recoverable history for eve
|
|
|
227
244
|
|
|
228
245
|
---
|
|
229
246
|
|
|
230
|
-
## Available tools
|
|
247
|
+
## Reference: Available tools
|
|
231
248
|
|
|
232
249
|
| Tool | Description |
|
|
233
250
|
| --- | --- |
|
|
@@ -235,7 +252,7 @@ Outcome: you get AI speed with explicit approval and recoverable history for eve
|
|
|
235
252
|
| `find_scenes` | Filter scenes by character, beat, tag, part, chapter, or POV |
|
|
236
253
|
| `get_scene_prose` | Load the full prose for a specific scene |
|
|
237
254
|
| `get_chapter_prose` | Load all prose for a chapter |
|
|
238
|
-
| `get_runtime_config` | Show
|
|
255
|
+
| `get_runtime_config` | Show active paths/capabilities plus runtime warnings and setup recommendations |
|
|
239
256
|
| `get_arc` | Ordered scene metadata for all scenes involving a character |
|
|
240
257
|
| `list_characters` | All characters, optionally filtered by project or universe |
|
|
241
258
|
| `get_character_sheet` | Full character metadata, traits, notes, and support notes |
|
|
@@ -294,6 +311,99 @@ Then register in your OpenClaw config:
|
|
|
294
311
|
}
|
|
295
312
|
```
|
|
296
313
|
|
|
314
|
+
<details>
|
|
315
|
+
<summary>Advanced OpenClaw / Docker integration notes</summary>
|
|
316
|
+
|
|
317
|
+
### OpenClaw / Docker integration notes
|
|
318
|
+
|
|
319
|
+
When `mcp-writing` runs behind OpenClaw (or any Docker MCP gateway), these details prevent common runtime failures.
|
|
320
|
+
|
|
321
|
+
#### Required environment and mounts
|
|
322
|
+
|
|
323
|
+
- Set `WRITING_SYNC_DIR=/sync`
|
|
324
|
+
- Set `DB_PATH=/data/writing.db`
|
|
325
|
+
- Mount your manuscript sync repo to `/sync`
|
|
326
|
+
- Mount a persistent path for SQLite data at `/data`
|
|
327
|
+
|
|
328
|
+
If `/sync` contains raw Scrivener external-sync output, run the importer once before normal `sync` usage:
|
|
329
|
+
|
|
330
|
+
```sh
|
|
331
|
+
node scripts/import.js /path/to/scrivener-export /sync --project my-novel
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
`sync` indexes files that already contain scene metadata. It does not convert Scrivener `Draft/` filenames into scene sidecars by itself.
|
|
335
|
+
|
|
336
|
+
#### Git ownership trust for mounted repos
|
|
337
|
+
|
|
338
|
+
If host and container ownership differ, git can fail with:
|
|
339
|
+
|
|
340
|
+
- `fatal: detected dubious ownership in repository`
|
|
341
|
+
|
|
342
|
+
Mark the mounted repo path as safe in the container image:
|
|
343
|
+
|
|
344
|
+
```sh
|
|
345
|
+
git config --system --add safe.directory /sync
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
#### SSH transport hardening
|
|
349
|
+
|
|
350
|
+
For private remotes, mount SSH materials read-only and enforce strict host checks:
|
|
351
|
+
|
|
352
|
+
- Auth key for fetch/pull/push
|
|
353
|
+
- `known_hosts` with GitHub host key
|
|
354
|
+
- `StrictHostKeyChecking=yes`
|
|
355
|
+
|
|
356
|
+
Example:
|
|
357
|
+
|
|
358
|
+
```sh
|
|
359
|
+
export GIT_SSH_COMMAND="ssh -i /root/.ssh/id_ed25519 -o IdentitiesOnly=yes -o StrictHostKeyChecking=yes -o UserKnownHostsFile=/root/.ssh/known_hosts"
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
#### Separate auth and signing keys
|
|
363
|
+
|
|
364
|
+
Use dedicated keys for transport and signing:
|
|
365
|
+
|
|
366
|
+
- Auth key: repository transport (`fetch` / `pull` / `push`)
|
|
367
|
+
- Signing key: commit/tag signatures
|
|
368
|
+
|
|
369
|
+
Recommended git config:
|
|
370
|
+
|
|
371
|
+
```sh
|
|
372
|
+
git config gpg.format ssh
|
|
373
|
+
git config user.signingkey /root/.ssh/id_ed25519_signing
|
|
374
|
+
git config commit.gpgsign true
|
|
375
|
+
git config pull.ff only
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### Git identity and GitHub email privacy
|
|
379
|
+
|
|
380
|
+
If GitHub email privacy is enabled, pushes can fail unless `user.email` is a GitHub noreply address:
|
|
381
|
+
|
|
382
|
+
```sh
|
|
383
|
+
git config user.name "Edda"
|
|
384
|
+
git config user.email "<id>+<username>@users.noreply.github.com"
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
#### Branch safety for automation
|
|
388
|
+
|
|
389
|
+
For bot-driven edits, prefer branch-per-change flow:
|
|
390
|
+
|
|
391
|
+
- Push to `edda/*` or `bot/*`
|
|
392
|
+
- Merge via pull request
|
|
393
|
+
- Protect `main` from direct automation pushes
|
|
394
|
+
|
|
395
|
+
#### Quick validation
|
|
396
|
+
|
|
397
|
+
```sh
|
|
398
|
+
ssh -T git@github.com
|
|
399
|
+
git -C /sync fetch origin
|
|
400
|
+
git -C /sync pull --ff-only
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Then create and push a signed smoke commit on a temporary branch.
|
|
404
|
+
|
|
405
|
+
</details>
|
|
406
|
+
|
|
297
407
|
## Running locally
|
|
298
408
|
|
|
299
409
|
```sh
|
|
@@ -339,19 +449,19 @@ For real projects, keep your manuscript sync folder outside this tool repository
|
|
|
339
449
|
|
|
340
450
|
### "Module not found: sqlite" or "Database support not available"
|
|
341
451
|
|
|
342
|
-
|
|
452
|
+
Your Node.js version is too old, or SQLite support was not started with the required flag.
|
|
343
453
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
454
|
+
Fix:
|
|
455
|
+
|
|
456
|
+
1. Run `node --version` and confirm v22.6.0 or newer.
|
|
457
|
+
2. Upgrade Node.js if needed.
|
|
458
|
+
3. Restart with `npm start` (the script already includes `--experimental-sqlite`).
|
|
348
459
|
|
|
349
460
|
### "EADDRINUSE: address already in use :::3000"
|
|
350
461
|
|
|
351
|
-
|
|
462
|
+
Port 3000 is already in use.
|
|
352
463
|
|
|
353
|
-
|
|
354
|
-
Use a different port:
|
|
464
|
+
Fix: start on a different port.
|
|
355
465
|
|
|
356
466
|
```sh
|
|
357
467
|
HTTP_PORT=3001 WRITING_SYNC_DIR=./my-manuscript DB_PATH=./writing.db npm start
|
|
@@ -361,10 +471,9 @@ Then update your MCP client config to use `http://localhost:3001/sse`.
|
|
|
361
471
|
|
|
362
472
|
### "ENOENT: no such file or directory, open './writing.db'"
|
|
363
473
|
|
|
364
|
-
|
|
474
|
+
The directory for `DB_PATH` does not exist.
|
|
365
475
|
|
|
366
|
-
|
|
367
|
-
Create the directory first:
|
|
476
|
+
Fix: create the directory first.
|
|
368
477
|
|
|
369
478
|
```sh
|
|
370
479
|
mkdir -p $(dirname ./writing.db) # if using a subdirectory
|
|
@@ -379,34 +488,66 @@ WRITING_SYNC_DIR=~/my-manuscript DB_PATH=~/writing-data/writing.db npm start
|
|
|
379
488
|
|
|
380
489
|
### "Sync dir not found: ./my-manuscript"
|
|
381
490
|
|
|
382
|
-
|
|
491
|
+
The `WRITING_SYNC_DIR` path does not exist.
|
|
383
492
|
|
|
384
|
-
|
|
385
|
-
Create the sync folder first:
|
|
493
|
+
Fix: create it (or point to an existing sync folder).
|
|
386
494
|
|
|
387
495
|
```sh
|
|
388
496
|
mkdir -p ./my-manuscript/projects/my-novel
|
|
389
497
|
WRITING_SYNC_DIR=./my-manuscript DB_PATH=./writing.db npm start
|
|
390
498
|
```
|
|
391
499
|
|
|
392
|
-
Or point to an existing folder where you've already placed scene files.
|
|
393
|
-
|
|
394
500
|
### "Import failed: unrecognized format"
|
|
395
501
|
|
|
396
|
-
|
|
502
|
+
Scrivener export is not plain text (`.txt`) or folder layout is unexpected.
|
|
503
|
+
|
|
504
|
+
Fix:
|
|
397
505
|
|
|
398
|
-
**Solution:**
|
|
399
506
|
1. In Scrivener, re-export with **File → Sync → With External Folder**
|
|
400
507
|
2. Ensure the format is set to **Plain text** (not RTF or .docx)
|
|
401
508
|
3. Verify the export folder has a `Draft/` subdirectory with `.txt` files
|
|
402
509
|
4. Try the import again: `node scripts/import.js ~/my-novel-txt /path/to/sync-dir --project my-novel`
|
|
403
510
|
|
|
511
|
+
### "OpenClaw can read tools, but scene indexing is empty or incomplete"
|
|
512
|
+
|
|
513
|
+
You are likely running `sync` on raw Scrivener `Draft/` output that has not been imported yet.
|
|
514
|
+
|
|
515
|
+
Fix:
|
|
516
|
+
|
|
517
|
+
1. Run importer once to create scene metadata sidecars:
|
|
518
|
+
|
|
519
|
+
```sh
|
|
520
|
+
node scripts/import.js /path/to/scrivener-export /path/to/sync-dir --project my-novel
|
|
521
|
+
```
|
|
522
|
+
|
|
523
|
+
2. Restart the service (if needed), then call `sync` again.
|
|
524
|
+
|
|
525
|
+
Note: importer behavior is Draft-aware (`<source>/Draft` if present, else source root), but plain `sync` only indexes already-normalized scene files.
|
|
526
|
+
|
|
527
|
+
### "Write access to repository denied" (or git push/pull fails in container)
|
|
528
|
+
|
|
529
|
+
Your container can start and read files, but cannot write metadata, create snapshots, or push branches.
|
|
530
|
+
|
|
531
|
+
Fix:
|
|
532
|
+
|
|
533
|
+
1. Check runtime diagnostics via `get_runtime_config`:
|
|
534
|
+
- `sync_dir_writable` must be `true`
|
|
535
|
+
- `runtime_warnings` should be empty for normal editing flows
|
|
536
|
+
2. Ensure `/sync` is mounted read-write (no `:ro`) and owned by the container user.
|
|
537
|
+
3. For mounted git repos with UID mismatch, mark safe directory:
|
|
538
|
+
|
|
539
|
+
```sh
|
|
540
|
+
git config --system --add safe.directory /sync
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
4. Verify SSH key has write access to the remote and `known_hosts` is mounted.
|
|
544
|
+
5. Prefer branch-per-change workflow (`bot/*` or `edda/*`) if `main` is protected.
|
|
545
|
+
|
|
404
546
|
### Tests fail after updating Node.js
|
|
405
547
|
|
|
406
|
-
|
|
548
|
+
Local install state may be stale after the Node.js change.
|
|
407
549
|
|
|
408
|
-
|
|
409
|
-
Clear npm cache and reinstall:
|
|
550
|
+
Fix: reinstall dependencies.
|
|
410
551
|
|
|
411
552
|
```sh
|
|
412
553
|
rm -rf node_modules package-lock.json
|
package/index.js
CHANGED
|
@@ -252,6 +252,43 @@ function generateProposalId() {
|
|
|
252
252
|
return `proposal-${nextProposalId++}`;
|
|
253
253
|
}
|
|
254
254
|
|
|
255
|
+
function getRuntimeDiagnostics() {
|
|
256
|
+
const warnings = [];
|
|
257
|
+
const recommendations = [];
|
|
258
|
+
|
|
259
|
+
if (!SYNC_DIR_WRITABLE) {
|
|
260
|
+
warnings.push("SYNC_DIR_READ_ONLY: sync dir is read-only; metadata write-back and prose editing tools are unavailable.");
|
|
261
|
+
recommendations.push("Mount WRITING_SYNC_DIR with write access (avoid read-only mounts like ':ro').");
|
|
262
|
+
recommendations.push("If running in Docker/OpenClaw, verify volume ownership and permissions for the container user.");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
if (!GIT_AVAILABLE) {
|
|
266
|
+
warnings.push("GIT_NOT_FOUND: git is not available on PATH; snapshot/edit tools are unavailable.");
|
|
267
|
+
recommendations.push("Install git in the runtime image/environment.");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (GIT_AVAILABLE && SYNC_DIR_WRITABLE && !GIT_ENABLED) {
|
|
271
|
+
warnings.push("GIT_DISABLED: git is available but repository snapshot tools are not active.");
|
|
272
|
+
recommendations.push("Ensure WRITING_SYNC_DIR points to a writable git repository root, or allow mcp-writing to initialize one.");
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (GIT_AVAILABLE && !SYNC_DIR_WRITABLE) {
|
|
276
|
+
recommendations.push("If git reports 'dubious ownership' for mounted repos, add: git config --system --add safe.directory /sync");
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
recommendations.push("If indexing finds many files without scene_id, run scripts/import.js first for Scrivener Draft exports, then run sync.");
|
|
280
|
+
|
|
281
|
+
return { warnings, recommendations };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const RUNTIME_DIAGNOSTICS = getRuntimeDiagnostics();
|
|
285
|
+
if (RUNTIME_DIAGNOSTICS.warnings.length) {
|
|
286
|
+
process.stderr.write(`[mcp-writing] Runtime diagnostics:\n`);
|
|
287
|
+
for (const line of RUNTIME_DIAGNOSTICS.warnings) {
|
|
288
|
+
process.stderr.write(`[mcp-writing] - ${line}\n`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
255
292
|
// Run sync on startup
|
|
256
293
|
syncAll(db, SYNC_DIR, { writable: SYNC_DIR_WRITABLE });
|
|
257
294
|
|
|
@@ -267,6 +304,7 @@ function createMcpServer() {
|
|
|
267
304
|
const parts = [`Sync complete. ${result.indexed} scenes indexed. ${result.staleMarked} scenes marked stale.`];
|
|
268
305
|
if (result.sidecarsMigrated) parts.push(`${result.sidecarsMigrated} sidecar(s) auto-generated from frontmatter.`);
|
|
269
306
|
if (result.skipped) parts.push(`${result.skipped} file(s) skipped (no scene_id).`);
|
|
307
|
+
if (result.skipped) parts.push(`Tip: for raw Scrivener Draft exports, run scripts/import.js first, then run sync again.`);
|
|
270
308
|
if (result.warnings.length) parts.push(`\n⚠️ Warnings:\n` + result.warnings.map(w => `- ${w}`).join("\n"));
|
|
271
309
|
return { content: [{ type: "text", text: parts.join(" ") }] };
|
|
272
310
|
});
|
|
@@ -284,6 +322,8 @@ function createMcpServer() {
|
|
|
284
322
|
git_available: GIT_AVAILABLE,
|
|
285
323
|
git_enabled: GIT_ENABLED,
|
|
286
324
|
http_port: HTTP_PORT,
|
|
325
|
+
runtime_warnings: RUNTIME_DIAGNOSTICS.warnings,
|
|
326
|
+
setup_recommendations: RUNTIME_DIAGNOSTICS.recommendations,
|
|
287
327
|
});
|
|
288
328
|
}
|
|
289
329
|
);
|