@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.
Files changed (2) hide show
  1. package/dist/index.js +62 -2
  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.0",
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",