@neverprepared/mcp-markdown-to-confluence 1.1.0 → 1.1.4

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.
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ import { register } from 'node:module';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { dirname, resolve } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const __dirname = dirname(fileURLToPath(import.meta.url));
8
+ register(pathToFileURL(resolve(__dirname, '..', 'dist', 'loader.js')));
9
+
10
+ await import(pathToFileURL(resolve(__dirname, '..', 'dist', 'index.js')));
package/dist/index.js CHANGED
@@ -5,7 +5,12 @@ import { z } from 'zod';
5
5
  import { ConfluenceClient } from 'confluence.js';
6
6
  import matter from 'gray-matter';
7
7
  import { readFile } from 'fs/promises';
8
- import { parseMarkdownToADF, renderADFDoc, executeADFProcessingPipeline, createPublisherFunctions, MermaidRendererPlugin, } from '@markdown-confluence/lib';
8
+ // Deep imports to avoid loading adaptors/filesystem.js which has broken CJS named exports.
9
+ // Pin @markdown-confluence/lib version if these paths change.
10
+ import { parseMarkdownToADF } from '@markdown-confluence/lib/dist/MdToADF.js';
11
+ import { renderADFDoc } from '@markdown-confluence/lib/dist/ADFToMarkdown.js';
12
+ import { executeADFProcessingPipeline, createPublisherFunctions, } from '@markdown-confluence/lib/dist/ADFProcessingPlugins/types.js';
13
+ import { MermaidRendererPlugin } from '@markdown-confluence/lib/dist/ADFProcessingPlugins/MermaidRendererPlugin.js';
9
14
  import { KrokiClient, KrokiMermaidRenderer, KrokiDiagramPlugin } from './kroki/index.js';
10
15
  // ---------------------------------------------------------------------------
11
16
  // Environment
@@ -18,10 +23,29 @@ const KROKI_URL = process.env.KROKI_URL ?? 'http://localhost:8371';
18
23
  // Kroki client
19
24
  // ---------------------------------------------------------------------------
20
25
  const krokiClient = new KrokiClient(KROKI_URL);
26
+ // Diagram types and their preferred output format
27
+ // PNG is preferred when supported; SVG is the universal fallback
28
+ const KROKI_DIAGRAM_CONFIGS = [
29
+ { type: 'plantuml', format: 'png' },
30
+ { type: 'graphviz', format: 'png' },
31
+ { type: 'dot', format: 'png' },
32
+ { type: 'ditaa', format: 'svg' },
33
+ { type: 'nomnoml', format: 'svg' },
34
+ { type: 'd2', format: 'svg' },
35
+ { type: 'dbml', format: 'svg' },
36
+ { type: 'erd', format: 'svg' },
37
+ { type: 'svgbob', format: 'svg' },
38
+ { type: 'pikchr', format: 'svg' },
39
+ { type: 'bytefield', format: 'svg' },
40
+ { type: 'wavedrom', format: 'svg' },
41
+ { type: 'vega', format: 'svg' },
42
+ { type: 'vega-lite', format: 'svg' },
43
+ { type: 'bpmn', format: 'svg' },
44
+ { type: 'c4plantuml', format: 'png' },
45
+ ];
21
46
  const SUPPORTED_DIAGRAM_TYPES = [
22
- 'mermaid', 'plantuml', 'graphviz', 'dot', 'd2', 'ditaa', 'nomnoml',
23
- 'svgbob', 'pikchr', 'bytefield', 'wavedrom', 'vega', 'vega-lite',
24
- 'excalidraw', 'bpmn', 'erd', 'dbml', 'c4plantuml',
47
+ 'mermaid',
48
+ ...KROKI_DIAGRAM_CONFIGS.map((c) => c.type),
25
49
  ];
26
50
  // ---------------------------------------------------------------------------
27
51
  // Confluence client
@@ -140,9 +164,7 @@ async function publishMarkdown(markdown, title, spaceKey, pageId, parentId, skip
140
164
  // Run ADF processing pipeline (renders diagrams via Kroki)
141
165
  const finalAdf = await executeADFProcessingPipeline([
142
166
  new MermaidRendererPlugin(new KrokiMermaidRenderer(krokiClient)),
143
- ...SUPPORTED_DIAGRAM_TYPES
144
- .filter((t) => t !== 'mermaid')
145
- .map((t) => new KrokiDiagramPlugin(t, krokiClient)),
167
+ ...KROKI_DIAGRAM_CONFIGS.map((c) => new KrokiDiagramPlugin(c.type, krokiClient, c.format)),
146
168
  ], adf, publisherFunctions);
147
169
  // Update the page with the final ADF
148
170
  const updateParams = {
@@ -1,16 +1,17 @@
1
1
  import { filter, traverse } from '@atlaskit/adf-utils/traverse';
2
2
  import SparkMD5 from 'spark-md5';
3
- function getDiagramFileName(diagramType, content) {
3
+ function getDiagramFileName(diagramType, content, outputFormat) {
4
4
  const text = content ?? `${diagramType} placeholder`;
5
5
  const hash = SparkMD5.hash(text);
6
- const uploadFilename = `RenderedKrokiChart-${diagramType}-${hash}.png`;
6
+ const ext = outputFormat === 'png' ? 'png' : 'svg';
7
+ const uploadFilename = `RenderedKrokiChart-${diagramType}-${hash}.${ext}`;
7
8
  return { uploadFilename, text };
8
9
  }
9
10
  export class KrokiDiagramPlugin {
10
11
  diagramType;
11
12
  client;
12
13
  outputFormat;
13
- constructor(diagramType, client, outputFormat = 'png') {
14
+ constructor(diagramType, client, outputFormat = 'svg') {
14
15
  this.diagramType = diagramType;
15
16
  this.client = client;
16
17
  this.outputFormat = outputFormat;
@@ -19,7 +20,7 @@ export class KrokiDiagramPlugin {
19
20
  const nodes = filter(adf, (node) => node.type === 'codeBlock' &&
20
21
  (node.attrs || {})?.['language'] === this.diagramType);
21
22
  const charts = new Set(nodes.map((node) => {
22
- const details = getDiagramFileName(this.diagramType, node?.content?.at(0)?.text);
23
+ const details = getDiagramFileName(this.diagramType, node?.content?.at(0)?.text, this.outputFormat);
23
24
  return {
24
25
  name: details.uploadFilename,
25
26
  data: details.text,
@@ -52,7 +53,7 @@ export class KrokiDiagramPlugin {
52
53
  if (!content) {
53
54
  return;
54
55
  }
55
- const filename = getDiagramFileName(this.diagramType, content);
56
+ const filename = getDiagramFileName(this.diagramType, content, this.outputFormat);
56
57
  if (!imageMap[filename.uploadFilename]) {
57
58
  return;
58
59
  }
package/dist/loader.js ADDED
@@ -0,0 +1,57 @@
1
+ import { fileURLToPath, pathToFileURL } from 'node:url';
2
+ import { resolve as pathResolve, dirname } from 'node:path';
3
+ import { existsSync, readFileSync } from 'node:fs';
4
+
5
+ function tryResolveFile(filePath) {
6
+ // Direct .js extension
7
+ if (existsSync(filePath + '.js')) {
8
+ return pathToFileURL(filePath + '.js').href;
9
+ }
10
+ // Directory with index.js
11
+ if (existsSync(filePath + '/index.js')) {
12
+ return pathToFileURL(filePath + '/index.js').href;
13
+ }
14
+ // Directory with package.json (module or main field)
15
+ if (existsSync(filePath + '/package.json')) {
16
+ try {
17
+ const pkg = JSON.parse(readFileSync(filePath + '/package.json', 'utf-8'));
18
+ const entry = pkg.module || pkg.main;
19
+ if (entry) {
20
+ const resolved = pathResolve(filePath, entry);
21
+ if (existsSync(resolved)) {
22
+ return pathToFileURL(resolved).href;
23
+ }
24
+ }
25
+ } catch {}
26
+ }
27
+ return null;
28
+ }
29
+
30
+ export async function load(url, context, nextLoad) {
31
+ if (url.endsWith('.json')) {
32
+ return nextLoad(url, { ...context, importAttributes: { type: 'json' } });
33
+ }
34
+ return nextLoad(url, context);
35
+ }
36
+
37
+ export async function resolve(specifier, context, nextResolve) {
38
+ try {
39
+ return await nextResolve(specifier, context);
40
+ } catch (err) {
41
+ if (err?.code === 'ERR_MODULE_NOT_FOUND' || err?.code === 'ERR_UNSUPPORTED_DIR_IMPORT') {
42
+ // Try from the error's resolved URL
43
+ if (err.url) {
44
+ const resolved = tryResolveFile(fileURLToPath(err.url));
45
+ if (resolved) return { url: resolved, shortCircuit: true };
46
+ }
47
+
48
+ // Try from parent for relative specifiers
49
+ if (specifier.startsWith('.') && context.parentURL) {
50
+ const parentPath = dirname(fileURLToPath(context.parentURL));
51
+ const resolved = tryResolveFile(pathResolve(parentPath, specifier));
52
+ if (resolved) return { url: resolved, shortCircuit: true };
53
+ }
54
+ }
55
+ throw err;
56
+ }
57
+ }
package/package.json CHANGED
@@ -1,25 +1,26 @@
1
1
  {
2
2
  "name": "@neverprepared/mcp-markdown-to-confluence",
3
- "version": "1.1.0",
3
+ "version": "1.1.4",
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",
7
7
  "bin": {
8
- "mcp-markdown-to-confluence": "dist/index.js"
8
+ "mcp-markdown-to-confluence": "bin/mcp-markdown-to-confluence.js"
9
9
  },
10
10
  "files": [
11
+ "bin",
11
12
  "dist",
12
13
  "docker",
13
14
  "scripts"
14
15
  ],
15
16
  "scripts": {
16
- "build": "tsc",
17
- "start": "node dist/index.js",
18
- "dev": "node --watch dist/index.js",
17
+ "build": "tsc && cp src/loader.js dist/loader.js",
18
+ "start": "node bin/mcp-markdown-to-confluence.js",
19
+ "dev": "node --watch bin/mcp-markdown-to-confluence.js",
19
20
  "postinstall": "sh scripts/postinstall.sh"
20
21
  },
21
22
  "dependencies": {
22
- "@markdown-confluence/lib": "^5.5.2",
23
+ "@markdown-confluence/lib": "5.5.2",
23
24
  "@modelcontextprotocol/sdk": "^1.10.1",
24
25
  "confluence.js": "^1.6.3",
25
26
  "gray-matter": "^4.0.3",