@hanna84/mcp-writing 1.3.8 → 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 CHANGED
@@ -1,5 +1,12 @@
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
+
3
10
  ## [1.3.8](https://github.com/hannasdev/mcp-writing/compare/v1.3.7...v1.3.8) (2026-04-18)
4
11
 
5
12
 
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 the active sync dir, DB path, and runtime capabilities |
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
- **Cause:** Node.js version is below 22.6.0 or the `--experimental-sqlite` flag was not passed.
452
+ Your Node.js version is too old, or SQLite support was not started with the required flag.
343
453
 
344
- **Solution:**
345
- 1. Check your Node.js version: `node --version` (should be v22.6.0+)
346
- 2. Update Node.js if needed: use nvm, homebrew, or download from nodejs.org
347
- 3. Restart with `npm start` (which includes the flag automatically)
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
- **Cause:** Port 3000 is already in use by another application.
462
+ Port 3000 is already in use.
352
463
 
353
- **Solution:**
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
- **Cause:** The directory for `DB_PATH` does not exist.
474
+ The directory for `DB_PATH` does not exist.
365
475
 
366
- **Solution:**
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
- **Cause:** The `WRITING_SYNC_DIR` path does not exist.
491
+ The `WRITING_SYNC_DIR` path does not exist.
383
492
 
384
- **Solution:**
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
- **Cause:** The Scrivener export format was not plain text (`.txt`) or the folder structure is unexpected.
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
- **Cause:** SQLite module cache may be stale.
548
+ Local install state may be stale after the Node.js change.
407
549
 
408
- **Solution:**
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
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hanna84/mcp-writing",
3
- "version": "1.3.8",
3
+ "version": "1.4.0",
4
4
  "description": "MCP service for AI-assisted reasoning and editing on long-form fiction projects",
5
5
  "type": "module",
6
6
  "main": "index.js",