@hanna84/mcp-writing 2.14.0 → 2.15.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 +10 -0
- package/package.json +1 -1
- package/src/tools/search.js +154 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,11 +4,21 @@ 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
|
+
#### [v2.15.0](https://github.com/hannasdev/mcp-writing.git
|
|
8
|
+
/compare/v2.14.0...v2.15.0)
|
|
9
|
+
|
|
10
|
+
- feat(reference): add reference link query tools [`#148`](https://github.com/hannasdev/mcp-writing.git
|
|
11
|
+
/pull/148)
|
|
12
|
+
|
|
7
13
|
#### [v2.14.0](https://github.com/hannasdev/mcp-writing.git
|
|
8
14
|
/compare/v2.13.0...v2.14.0)
|
|
9
15
|
|
|
16
|
+
> 30 April 2026
|
|
17
|
+
|
|
10
18
|
- feat(reference): add reference link indexing for Phase 4B [`#147`](https://github.com/hannasdev/mcp-writing.git
|
|
11
19
|
/pull/147)
|
|
20
|
+
- Release 2.14.0 [`0b82946`](https://github.com/hannasdev/mcp-writing.git
|
|
21
|
+
/commit/0b829469448cb86008113dbf1b05f92e700b61a1)
|
|
12
22
|
|
|
13
23
|
#### [v2.13.0](https://github.com/hannasdev/mcp-writing.git
|
|
14
24
|
/compare/v2.12.22...v2.13.0)
|
package/package.json
CHANGED
package/src/tools/search.js
CHANGED
|
@@ -485,6 +485,160 @@ export function registerSearchTools(s, {
|
|
|
485
485
|
}
|
|
486
486
|
);
|
|
487
487
|
|
|
488
|
+
// ---- list_scene_references -----------------------------------------------
|
|
489
|
+
s.tool(
|
|
490
|
+
"list_scene_references",
|
|
491
|
+
"List direct reference documents linked from a scene via metadata (for example, reference_ids). Returns only one-hop scene -> reference links and does not recursively traverse related references. If scene IDs are reused across projects, omitting project_id returns CONFLICT with candidate project_ids.",
|
|
492
|
+
{
|
|
493
|
+
scene_id: z.string().describe("Scene ID to inspect."),
|
|
494
|
+
project_id: z.string().optional().describe("Optional project ID to disambiguate duplicate scene IDs across projects."),
|
|
495
|
+
},
|
|
496
|
+
async ({ scene_id, project_id }) => {
|
|
497
|
+
let scene;
|
|
498
|
+
if (project_id) {
|
|
499
|
+
scene = db.prepare(`
|
|
500
|
+
SELECT scene_id, project_id
|
|
501
|
+
FROM scenes
|
|
502
|
+
WHERE scene_id = ? AND project_id = ?
|
|
503
|
+
LIMIT 1
|
|
504
|
+
`).get(scene_id, project_id);
|
|
505
|
+
if (!scene) {
|
|
506
|
+
return errorResponse("NOT_FOUND", `Scene '${scene_id}' not found in project '${project_id}'.`);
|
|
507
|
+
}
|
|
508
|
+
} else {
|
|
509
|
+
const matches = db.prepare(`
|
|
510
|
+
SELECT scene_id, project_id
|
|
511
|
+
FROM scenes
|
|
512
|
+
WHERE scene_id = ?
|
|
513
|
+
ORDER BY project_id
|
|
514
|
+
`).all(scene_id);
|
|
515
|
+
if (matches.length === 0) {
|
|
516
|
+
return errorResponse("NOT_FOUND", `Scene '${scene_id}' not found.`);
|
|
517
|
+
}
|
|
518
|
+
if (matches.length > 1) {
|
|
519
|
+
return errorResponse(
|
|
520
|
+
"CONFLICT",
|
|
521
|
+
`Scene ID '${scene_id}' exists in multiple projects. Provide project_id to disambiguate.`,
|
|
522
|
+
{ scene_id, project_ids: matches.map(row => row.project_id) }
|
|
523
|
+
);
|
|
524
|
+
}
|
|
525
|
+
scene = matches[0];
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const links = db.prepare(`
|
|
529
|
+
SELECT
|
|
530
|
+
rl.target_doc_id,
|
|
531
|
+
rl.relation,
|
|
532
|
+
rd.project_id AS target_project_id,
|
|
533
|
+
rd.universe_id AS target_universe_id,
|
|
534
|
+
rd.type,
|
|
535
|
+
rd.title,
|
|
536
|
+
rd.summary,
|
|
537
|
+
rd.file_path
|
|
538
|
+
FROM reference_links rl
|
|
539
|
+
LEFT JOIN reference_docs rd ON rd.doc_id = rl.target_doc_id
|
|
540
|
+
WHERE rl.source_kind = 'scene' AND rl.source_project_id = ? AND rl.source_id = ?
|
|
541
|
+
ORDER BY rl.target_doc_id
|
|
542
|
+
`).all(scene.project_id ?? "", scene.scene_id);
|
|
543
|
+
|
|
544
|
+
if (links.length === 0) {
|
|
545
|
+
return errorResponse("NO_RESULTS", `No reference links found for scene '${scene.scene_id}' in project '${scene.project_id}'.`);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
const tagsStmt = db.prepare(`
|
|
549
|
+
SELECT tag
|
|
550
|
+
FROM reference_doc_tags
|
|
551
|
+
WHERE doc_id = ?
|
|
552
|
+
ORDER BY tag
|
|
553
|
+
`);
|
|
554
|
+
const references = links.map((row) => ({
|
|
555
|
+
doc_id: row.target_doc_id,
|
|
556
|
+
relation: row.relation,
|
|
557
|
+
project_id: row.target_project_id,
|
|
558
|
+
universe_id: row.target_universe_id,
|
|
559
|
+
type: row.type,
|
|
560
|
+
title: row.title,
|
|
561
|
+
summary: row.summary,
|
|
562
|
+
file_path: row.file_path,
|
|
563
|
+
tags: tagsStmt.all(row.target_doc_id).map(tagRow => tagRow.tag),
|
|
564
|
+
}));
|
|
565
|
+
|
|
566
|
+
return {
|
|
567
|
+
content: [{
|
|
568
|
+
type: "text",
|
|
569
|
+
text: JSON.stringify({
|
|
570
|
+
scene_id: scene.scene_id,
|
|
571
|
+
project_id: scene.project_id,
|
|
572
|
+
references,
|
|
573
|
+
}, null, 2),
|
|
574
|
+
}],
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
// ---- get_reference_doc ----------------------------------------------------
|
|
580
|
+
s.tool(
|
|
581
|
+
"get_reference_doc",
|
|
582
|
+
"Get metadata for a reference document by doc_id. Optionally includes exactly one hop of related reference docs.",
|
|
583
|
+
{
|
|
584
|
+
doc_id: z.string().describe("Reference document ID."),
|
|
585
|
+
include_related: z.boolean().optional().describe("If true, include one-hop related reference docs."),
|
|
586
|
+
},
|
|
587
|
+
async ({ doc_id, include_related = false }) => {
|
|
588
|
+
const doc = db.prepare(`
|
|
589
|
+
SELECT doc_id, project_id, universe_id, type, title, summary, file_path
|
|
590
|
+
FROM reference_docs
|
|
591
|
+
WHERE doc_id = ?
|
|
592
|
+
`).get(doc_id);
|
|
593
|
+
if (!doc) {
|
|
594
|
+
return errorResponse("NOT_FOUND", `Reference document '${doc_id}' not found.`);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
const tagsStmt = db.prepare(`
|
|
598
|
+
SELECT tag
|
|
599
|
+
FROM reference_doc_tags
|
|
600
|
+
WHERE doc_id = ?
|
|
601
|
+
ORDER BY tag
|
|
602
|
+
`);
|
|
603
|
+
const payload = {
|
|
604
|
+
...doc,
|
|
605
|
+
tags: tagsStmt.all(doc.doc_id).map(row => row.tag),
|
|
606
|
+
};
|
|
607
|
+
|
|
608
|
+
if (include_related) {
|
|
609
|
+
const relatedRows = db.prepare(`
|
|
610
|
+
SELECT
|
|
611
|
+
rl.target_doc_id,
|
|
612
|
+
rl.relation,
|
|
613
|
+
rd.project_id,
|
|
614
|
+
rd.universe_id,
|
|
615
|
+
rd.type,
|
|
616
|
+
rd.title,
|
|
617
|
+
rd.summary,
|
|
618
|
+
rd.file_path
|
|
619
|
+
FROM reference_links rl
|
|
620
|
+
LEFT JOIN reference_docs rd ON rd.doc_id = rl.target_doc_id
|
|
621
|
+
WHERE rl.source_kind = 'reference' AND rl.source_project_id = ? AND rl.source_id = ?
|
|
622
|
+
ORDER BY rl.target_doc_id
|
|
623
|
+
`).all(doc.project_id ?? "", doc.doc_id);
|
|
624
|
+
|
|
625
|
+
payload.related = relatedRows.map((row) => ({
|
|
626
|
+
doc_id: row.target_doc_id,
|
|
627
|
+
relation: row.relation,
|
|
628
|
+
project_id: row.project_id,
|
|
629
|
+
universe_id: row.universe_id,
|
|
630
|
+
type: row.type,
|
|
631
|
+
title: row.title,
|
|
632
|
+
summary: row.summary,
|
|
633
|
+
file_path: row.file_path,
|
|
634
|
+
tags: tagsStmt.all(row.target_doc_id).map(tagRow => tagRow.tag),
|
|
635
|
+
}));
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }] };
|
|
639
|
+
}
|
|
640
|
+
);
|
|
641
|
+
|
|
488
642
|
// ---- list_threads --------------------------------------------------------
|
|
489
643
|
s.tool(
|
|
490
644
|
"list_threads",
|