@neverprepared/mcp-markdown-to-confluence 1.3.0 → 1.4.1
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/dist/index.js +62 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,7 +16,8 @@ import { KrokiClient, KrokiMermaidRenderer, KrokiDiagramPlugin } from './kroki/i
|
|
|
16
16
|
// ---------------------------------------------------------------------------
|
|
17
17
|
// Environment
|
|
18
18
|
// ---------------------------------------------------------------------------
|
|
19
|
-
const CONFLUENCE_BASE_URL = process.env.CONFLUENCE_BASE_URL ?? ''
|
|
19
|
+
const CONFLUENCE_BASE_URL = (process.env.CONFLUENCE_URL ?? process.env.CONFLUENCE_BASE_URL ?? '')
|
|
20
|
+
.replace(/\/wiki\/?$/, '');
|
|
20
21
|
const CONFLUENCE_USERNAME = process.env.CONFLUENCE_USERNAME ?? '';
|
|
21
22
|
const CONFLUENCE_API_TOKEN = process.env.CONFLUENCE_API_TOKEN ?? '';
|
|
22
23
|
const KROKI_URL = process.env.KROKI_URL ?? 'http://localhost:8371';
|
|
@@ -202,6 +203,30 @@ async function scanDirectoryTree(rootPath, spaceKey, currentPath = rootPath, dep
|
|
|
202
203
|
return { nodes, skipped };
|
|
203
204
|
}
|
|
204
205
|
// ---------------------------------------------------------------------------
|
|
206
|
+
// Wiki link resolution
|
|
207
|
+
// ---------------------------------------------------------------------------
|
|
208
|
+
// Matches [[Page Name]] and [[Page Name#Heading]] and [[Page Name|Display Text]]
|
|
209
|
+
const WIKI_LINK_RE = /\[\[([^\]|#]+)(?:#([^\]|]+))?(?:\|([^\]]+))?\]\]/g;
|
|
210
|
+
function hasWikiLinks(markdown) {
|
|
211
|
+
return WIKI_LINK_RE.test(markdown);
|
|
212
|
+
}
|
|
213
|
+
function resolveWikiLinks(markdown, titleToUrl) {
|
|
214
|
+
return markdown.replace(WIKI_LINK_RE, (_match, pageName, heading, displayText) => {
|
|
215
|
+
const trimmedName = pageName.trim();
|
|
216
|
+
const url = titleToUrl.get(trimmedName);
|
|
217
|
+
if (!url) {
|
|
218
|
+
// No matching page found — leave as plain text
|
|
219
|
+
return displayText?.trim() || trimmedName;
|
|
220
|
+
}
|
|
221
|
+
const label = displayText?.trim() || trimmedName;
|
|
222
|
+
const anchor = heading?.trim();
|
|
223
|
+
const anchorSuffix = anchor
|
|
224
|
+
? '#' + anchor.replace(/\s+/g, '-')
|
|
225
|
+
: '';
|
|
226
|
+
return `[${label}](${url}${anchorSuffix})`;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
// ---------------------------------------------------------------------------
|
|
205
230
|
// Core publish logic
|
|
206
231
|
// ---------------------------------------------------------------------------
|
|
207
232
|
async function publishMarkdown(markdown, title, spaceKey, pageId, parentId, skipPreview = false) {
|
|
@@ -659,6 +684,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
659
684
|
})));
|
|
660
685
|
allResults.push(...levelResults);
|
|
661
686
|
}
|
|
687
|
+
// Second pass: resolve wiki links [[Page Name]] and [[Page Name#Heading]]
|
|
688
|
+
// Build title → URL map from all successfully published pages
|
|
689
|
+
const titleToUrl = new Map();
|
|
690
|
+
for (const r of allResults) {
|
|
691
|
+
if (r.success && r.url) {
|
|
692
|
+
titleToUrl.set(r.title, r.url);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
// Find nodes with wiki links that need re-publishing
|
|
696
|
+
const nodesWithLinks = nodes.filter((n) => n.markdownFile && hasWikiLinks(n.markdownFile.content) && n.resolvedPageId);
|
|
697
|
+
if (nodesWithLinks.length > 0 && titleToUrl.size > 0) {
|
|
698
|
+
const linkResults = await Promise.all(nodesWithLinks.map((node) => limit(async () => {
|
|
699
|
+
try {
|
|
700
|
+
const resolvedMarkdown = resolveWikiLinks(node.markdownFile.content, titleToUrl);
|
|
701
|
+
const result = await publishMarkdown(resolvedMarkdown, node.title, input.spaceKey, node.resolvedPageId, undefined, // don't reparent on second pass
|
|
702
|
+
true);
|
|
703
|
+
return { relativePath: node.relativePath, title: node.title, success: true, version: result.version };
|
|
704
|
+
}
|
|
705
|
+
catch {
|
|
706
|
+
return { relativePath: node.relativePath, title: node.title, success: false };
|
|
707
|
+
}
|
|
708
|
+
})));
|
|
709
|
+
const linkedCount = linkResults.filter((r) => r.success).length;
|
|
710
|
+
if (linkedCount > 0) {
|
|
711
|
+
// Update versions in allResults
|
|
712
|
+
for (const lr of linkResults) {
|
|
713
|
+
if (lr.success && lr.version) {
|
|
714
|
+
const existing = allResults.find((r) => r.relativePath === lr.relativePath);
|
|
715
|
+
if (existing)
|
|
716
|
+
existing.version = lr.version;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
}
|
|
662
721
|
// Build summary
|
|
663
722
|
const succeeded = allResults.filter((r) => r.success);
|
|
664
723
|
const failed = allResults.filter((r) => !r.success);
|
|
@@ -666,7 +725,8 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
666
725
|
`=== DIRECTORY PUBLISH RESULTS ===`,
|
|
667
726
|
`Directory: ${input.directoryPath}`,
|
|
668
727
|
`Space: ${input.spaceKey}`,
|
|
669
|
-
`Succeeded: ${succeeded.length} | Failed: ${failed.length} | Skipped: ${skipped.length}
|
|
728
|
+
`Succeeded: ${succeeded.length} | Failed: ${failed.length} | Skipped: ${skipped.length}` +
|
|
729
|
+
(nodesWithLinks.length > 0 ? ` | Wiki links resolved: ${nodesWithLinks.length} page(s)` : ''),
|
|
670
730
|
'',
|
|
671
731
|
];
|
|
672
732
|
if (succeeded.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neverprepared/mcp-markdown-to-confluence",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.1",
|
|
4
4
|
"description": "MCP server for converting markdown to Confluence ADF and publishing pages with diagram support via Kroki",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|