@hanna84/mcp-writing 3.29.2 → 3.29.3
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 +7 -0
- package/package.json +1 -1
- package/src/core/canonical-target-resolution.js +1 -0
- package/src/index.js +5 -1
- package/src/sync/sync.js +15 -6
- package/src/tools/search.js +5 -2
- package/src/tools/sync.js +12 -4
package/CHANGELOG.md
CHANGED
|
@@ -4,9 +4,16 @@ All notable changes to this project will be documented in this file. Dates are d
|
|
|
4
4
|
|
|
5
5
|
Generated by [`auto-changelog`](https://github.com/CookPete/auto-changelog).
|
|
6
6
|
|
|
7
|
+
#### [v3.29.3](https://github.com/hannasdev/mcp-writing/compare/v3.29.2...v3.29.3)
|
|
8
|
+
|
|
9
|
+
- fix: tighten MCP response contracts [`#242`](https://github.com/hannasdev/mcp-writing/pull/242)
|
|
10
|
+
|
|
7
11
|
#### [v3.29.2](https://github.com/hannasdev/mcp-writing/compare/v3.29.1...v3.29.2)
|
|
8
12
|
|
|
13
|
+
> 11 June 2026
|
|
14
|
+
|
|
9
15
|
- ci: skip PR template check for Dependabot [`#241`](https://github.com/hannasdev/mcp-writing/pull/241)
|
|
16
|
+
- Release 3.29.2 [`a26b7cc`](https://github.com/hannasdev/mcp-writing/commit/a26b7cc3d66d250fd9f30bd6d9ddcad49187008c)
|
|
10
17
|
|
|
11
18
|
#### [v3.29.1](https://github.com/hannasdev/mcp-writing/compare/v3.29.0...v3.29.1)
|
|
12
19
|
|
package/package.json
CHANGED
|
@@ -114,6 +114,7 @@ function buildResolutionFailure({ targetKind, input, projectId, universeId, cand
|
|
|
114
114
|
input,
|
|
115
115
|
project_id: projectId,
|
|
116
116
|
...(universeId !== undefined ? { universe_id: universeId } : {}),
|
|
117
|
+
suggestions_available: cappedCandidates.length > 0,
|
|
117
118
|
candidate_matches: cappedCandidates,
|
|
118
119
|
next_step: nextStepForTargetKind(targetKind),
|
|
119
120
|
};
|
package/src/index.js
CHANGED
|
@@ -134,7 +134,10 @@ function paginateRows(rows, { page, pageSize, forcePagination = false }) {
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
function jsonResponse(payload) {
|
|
137
|
-
return {
|
|
137
|
+
return {
|
|
138
|
+
content: [{ type: "text", text: JSON.stringify(payload, null, 2) }],
|
|
139
|
+
...(payload?.ok === false ? { isError: true } : {}),
|
|
140
|
+
};
|
|
138
141
|
}
|
|
139
142
|
|
|
140
143
|
function errorResponse(code, message, details) {
|
|
@@ -612,6 +615,7 @@ function createMcpServer() {
|
|
|
612
615
|
async () => {
|
|
613
616
|
return jsonResponse({
|
|
614
617
|
server_version: MCP_SERVER_VERSION,
|
|
618
|
+
transport: MCP_TRANSPORT === "stdio" ? "stdio" : "http",
|
|
615
619
|
sync_dir: SYNC_DIR_ABS,
|
|
616
620
|
db_path: DB_PATH_DISPLAY,
|
|
617
621
|
db_migration_warnings: DB_STARTUP_WARNINGS,
|
package/src/sync/sync.js
CHANGED
|
@@ -1688,7 +1688,7 @@ export function observeOrphanedSidecars(syncDir, { indexedSceneIds = new Set() }
|
|
|
1688
1688
|
}
|
|
1689
1689
|
|
|
1690
1690
|
const WARNING_TYPE_LABELS = {
|
|
1691
|
-
no_scene_id: "
|
|
1691
|
+
no_scene_id: "Ignored non-manuscript files",
|
|
1692
1692
|
duplicate_scene_id: "Duplicate scene_id",
|
|
1693
1693
|
path_metadata_mismatch: "Path/metadata mismatch",
|
|
1694
1694
|
chapter_structure: "Chapter structure",
|
|
@@ -1703,7 +1703,7 @@ const WARNING_TYPE_LABELS = {
|
|
|
1703
1703
|
};
|
|
1704
1704
|
|
|
1705
1705
|
const WARNING_PATTERNS = [
|
|
1706
|
-
{ type: "no_scene_id", re: /^
|
|
1706
|
+
{ type: "no_scene_id", re: /^Ignored non-manuscript file \(no scene_id\):/ },
|
|
1707
1707
|
{ type: "duplicate_scene_id", re: /^Duplicate scene_id/ },
|
|
1708
1708
|
{ type: "path_metadata_mismatch", re: /^Path\/metadata mismatch/ },
|
|
1709
1709
|
{ type: "chapter_structure", re: /^(Chapter structure warning|Epigraph requires explicit chapter linkage|Epigraph references unknown chapter_id|Scene references unknown chapter_id|Ambiguous chapter linkage|Epigraph identity conflict|Managed structure sync ignored|Managed structure sync preserved canonical epigraph)/ },
|
|
@@ -1718,6 +1718,7 @@ const WARNING_PATTERNS = [
|
|
|
1718
1718
|
];
|
|
1719
1719
|
|
|
1720
1720
|
const MAX_WARNING_EXAMPLES = 5;
|
|
1721
|
+
const INFO_WARNING_TYPES = new Set(["no_scene_id"]);
|
|
1721
1722
|
|
|
1722
1723
|
export function buildWarningSummary(warnings) {
|
|
1723
1724
|
const summary = {};
|
|
@@ -1725,7 +1726,14 @@ export function buildWarningSummary(warnings) {
|
|
|
1725
1726
|
const firstLine = w.split("\n")[0];
|
|
1726
1727
|
const match = WARNING_PATTERNS.find(p => p.re.test(firstLine));
|
|
1727
1728
|
const type = match?.type ?? "other";
|
|
1728
|
-
if (!summary[type])
|
|
1729
|
+
if (!summary[type]) {
|
|
1730
|
+
summary[type] = {
|
|
1731
|
+
count: 0,
|
|
1732
|
+
examples: [],
|
|
1733
|
+
severity: INFO_WARNING_TYPES.has(type) ? "info" : "warning",
|
|
1734
|
+
label: WARNING_TYPE_LABELS[type] ?? type,
|
|
1735
|
+
};
|
|
1736
|
+
}
|
|
1729
1737
|
summary[type].count++;
|
|
1730
1738
|
if (summary[type].examples.length < MAX_WARNING_EXAMPLES) {
|
|
1731
1739
|
summary[type].examples.push(firstLine);
|
|
@@ -1799,7 +1807,7 @@ export function syncAll(db, syncDir, { quiet = false, writable = false, relation
|
|
|
1799
1807
|
|
|
1800
1808
|
if (!meta.scene_id && !chapterStructure.isEpigraph) {
|
|
1801
1809
|
skipped++;
|
|
1802
|
-
if (!quiet) warnings.push(`
|
|
1810
|
+
if (!quiet) warnings.push(`Ignored non-manuscript file (no scene_id): ${structureObservation.relativePath}`);
|
|
1803
1811
|
continue;
|
|
1804
1812
|
}
|
|
1805
1813
|
|
|
@@ -1921,12 +1929,13 @@ export function syncAll(db, syncDir, { quiet = false, writable = false, relation
|
|
|
1921
1929
|
`[mcp-writing] Sync complete: ${indexed} scenes indexed, ${staleMarked} scenes marked stale` +
|
|
1922
1930
|
(epigraphsIndexed ? `, ${epigraphsIndexed} epigraphs indexed, ${epigraphsStaleMarked} epigraphs marked stale` : "") +
|
|
1923
1931
|
(sidecarsMigrated ? `, ${sidecarsMigrated} sidecars auto-generated` : "") +
|
|
1924
|
-
(skipped ? `, ${skipped}
|
|
1932
|
+
(skipped ? `, ${skipped} non-manuscript file(s) ignored` : "") + "\n"
|
|
1925
1933
|
);
|
|
1926
1934
|
for (const [type, entry] of Object.entries(warningSummary)) {
|
|
1927
1935
|
const count = entry.count;
|
|
1928
1936
|
const label = WARNING_TYPE_LABELS[type] ?? type;
|
|
1929
|
-
|
|
1937
|
+
const level = entry.severity === "info" ? "INFO" : "WARNING";
|
|
1938
|
+
process.stderr.write(`[mcp-writing] ${level}: ${label}: ${count} file(s). First example: ${entry.examples[0]}\n`);
|
|
1930
1939
|
}
|
|
1931
1940
|
}
|
|
1932
1941
|
return {
|
package/src/tools/search.js
CHANGED
|
@@ -485,13 +485,15 @@ export function registerSearchTools(s, {
|
|
|
485
485
|
pageSize: page_size,
|
|
486
486
|
forcePagination: rows.length > DEFAULT_METADATA_PAGE_SIZE,
|
|
487
487
|
});
|
|
488
|
+
const resolvedFrom = mergeResolvedFilters(resolvedFilters);
|
|
488
489
|
|
|
489
490
|
const payload = paged.paginated
|
|
490
491
|
? {
|
|
491
492
|
results: paged.rows,
|
|
492
493
|
...paged.meta,
|
|
493
494
|
warning,
|
|
494
|
-
resolved_filters:
|
|
495
|
+
resolved_filters: resolvedFrom,
|
|
496
|
+
resolved_from: resolvedFrom,
|
|
495
497
|
next_step: staleCount > 0
|
|
496
498
|
? "Touch stale scenes as you work and run enrich_scene(scene_id, project_id) to recover metadata parity incrementally."
|
|
497
499
|
: undefined,
|
|
@@ -500,7 +502,8 @@ export function registerSearchTools(s, {
|
|
|
500
502
|
results: rows,
|
|
501
503
|
total_count: rows.length,
|
|
502
504
|
warning,
|
|
503
|
-
resolved_filters:
|
|
505
|
+
resolved_filters: resolvedFrom,
|
|
506
|
+
resolved_from: resolvedFrom,
|
|
504
507
|
next_step: staleCount > 0
|
|
505
508
|
? "Touch stale scenes as you work and run enrich_scene(scene_id, project_id) to recover metadata parity incrementally."
|
|
506
509
|
: undefined,
|
package/src/tools/sync.js
CHANGED
|
@@ -60,14 +60,22 @@ export function registerSyncTools(s, {
|
|
|
60
60
|
}
|
|
61
61
|
if (result.sidecarsMigrated) parts.push(`${result.sidecarsMigrated} sidecar(s) auto-generated from frontmatter.`);
|
|
62
62
|
if (result.skipped) {
|
|
63
|
-
parts.push(`${result.skipped} file(s)
|
|
64
|
-
parts.push(`Tip:
|
|
63
|
+
parts.push(`${result.skipped} non-manuscript file(s) ignored because they did not declare scene_id.`);
|
|
64
|
+
parts.push(`Tip: if these are raw Scrivener Draft scene exports, run src/scripts/import.js first, then run sync again.`);
|
|
65
65
|
}
|
|
66
66
|
const summary = result.warningSummary;
|
|
67
67
|
const summaryEntries = Object.entries(summary);
|
|
68
68
|
if (summaryEntries.length) {
|
|
69
|
-
const
|
|
70
|
-
|
|
69
|
+
const infoEntries = summaryEntries.filter(([, entry]) => entry.severity === "info");
|
|
70
|
+
const warningEntries = summaryEntries.filter(([, entry]) => entry.severity !== "info");
|
|
71
|
+
if (infoEntries.length) {
|
|
72
|
+
const lines = infoEntries.map(([type, entry]) => `- ${entry.label ?? type}: ${entry.count} (e.g. ${entry.examples[0]})`);
|
|
73
|
+
parts.push(`\nIgnored file summary:\n` + lines.join("\n"));
|
|
74
|
+
}
|
|
75
|
+
if (warningEntries.length) {
|
|
76
|
+
const lines = warningEntries.map(([type, entry]) => `- ${entry.label ?? type}: ${entry.count} (e.g. ${entry.examples[0]})`);
|
|
77
|
+
parts.push(`\nWarning summary:\n` + lines.join("\n"));
|
|
78
|
+
}
|
|
71
79
|
}
|
|
72
80
|
return { content: [{ type: "text", text: parts.join(" ") }] };
|
|
73
81
|
});
|