UNPKG

@aashari/mcp-server-atlassian-jira

Version:

Node.js/TypeScript MCP server for Atlassian Jira. Equips AI systems (LLMs) with tools to list/get projects, search/get issues (using JQL/ID), and view dev info (commits, PRs). Connects AI capabilities directly into Jira project management and issue tracki

349 lines (348 loc) 10.1 kB
"use strict"; /** * Utility functions for converting Atlassian Document Format (ADF) to Markdown */ Object.defineProperty(exports, "__esModule", { value: true }); exports.adfToMarkdown = adfToMarkdown; const logger_util_js_1 = require("./logger.util.js"); // Create a contextualized logger for this file const adfLogger = logger_util_js_1.Logger.forContext('utils/adf.util.ts'); // Log ADF utility initialization adfLogger.debug('ADF utility initialized'); /** * Convert Atlassian Document Format (ADF) to Markdown * * @param adf - The ADF content to convert (can be string or object) * @returns The converted Markdown content */ function adfToMarkdown(adf) { const methodLogger = logger_util_js_1.Logger.forContext('utils/adf.util.ts', 'adfToMarkdown'); try { // Handle empty or undefined input if (!adf) { return ''; } // Parse ADF if it's a string let adfDoc; if (typeof adf === 'string') { try { adfDoc = JSON.parse(adf); } catch { return adf; // Return as-is if not valid JSON } } else if (typeof adf === 'object') { adfDoc = adf; } else { return String(adf); } // Check if it's a valid ADF document if (!adfDoc.content || !Array.isArray(adfDoc.content)) { return ''; } // Process the document const markdown = processAdfContent(adfDoc.content); methodLogger.debug(`Converted ADF to Markdown, length: ${markdown.length}`); return markdown; } catch (error) { methodLogger.error('[src/utils/adf.util.ts@adfToMarkdown] Error converting ADF to Markdown:', error); return '*Error converting description format*'; } } /** * Process ADF content nodes */ function processAdfContent(content) { if (!content || !Array.isArray(content)) { return ''; } return content.map((node) => processAdfNode(node)).join('\n\n'); } /** * Process mention node */ function processMention(node) { if (!node.attrs) { return ''; } const text = node.attrs.text || node.attrs.displayName || ''; if (!text) { return ''; } // Format as @username to preserve the mention format // Remove any existing @ symbol to avoid double @@ in the output const cleanText = typeof text === 'string' && text.startsWith('@') ? text.substring(1) : text; return `@${cleanText}`; } /** * Process a single ADF node */ function processAdfNode(node) { if (!node || !node.type) { return ''; } switch (node.type) { case 'paragraph': return processParagraph(node); case 'heading': return processHeading(node); case 'bulletList': return processBulletList(node); case 'orderedList': return processOrderedList(node); case 'listItem': return processListItem(node); case 'codeBlock': return processCodeBlock(node); case 'blockquote': return processBlockquote(node); case 'rule': return '---'; case 'mediaGroup': return processMediaGroup(node); case 'table': return processTable(node); case 'text': return processText(node); case 'mention': return processMention(node); default: // For unknown node types, try to process content if available if (node.content) { return processAdfContent(node.content); } return ''; } } /** * Process paragraph node */ function processParagraph(node) { if (!node.content) { return ''; } // Process each child node and join them with proper spacing return node.content .map((childNode, index) => { // Add a space between text nodes if needed const needsSpace = index > 0 && childNode.type === 'text' && node.content[index - 1].type === 'text' && !childNode.text?.startsWith(' ') && !node.content[index - 1].text?.endsWith(' '); return (needsSpace ? ' ' : '') + processAdfNode(childNode); }) .join(''); } /** * Process heading node */ function processHeading(node) { if (!node.content || !node.attrs) { return ''; } const level = typeof node.attrs.level === 'number' ? node.attrs.level : 1; const headingMarker = '#'.repeat(level); const content = node.content .map((childNode) => processAdfNode(childNode)) .join(''); return `${headingMarker} ${content}`; } /** * Process bullet list node */ function processBulletList(node) { if (!node.content) { return ''; } return node.content.map((item) => processAdfNode(item)).join('\n'); } /** * Process ordered list node */ function processOrderedList(node) { if (!node.content) { return ''; } return node.content .map((item, index) => { const processedItem = processAdfNode(item); // Replace the first "- " with "1. ", "2. ", etc. return processedItem.replace(/^- /, `${index + 1}. `); }) .join('\n'); } /** * Process list item node */ function processListItem(node) { if (!node.content) { return ''; } const content = node.content .map((childNode) => { const processed = processAdfNode(childNode); // For nested lists, add indentation if (childNode.type === 'bulletList' || childNode.type === 'orderedList') { return processed .split('\n') .map((line) => ` ${line}`) .join('\n'); } return processed; }) .join('\n'); return `- ${content}`; } /** * Process code block node */ function processCodeBlock(node) { if (!node.content) { return '```\n```'; } const language = node.attrs?.language || ''; const code = node.content .map((childNode) => processAdfNode(childNode)) .join(''); return `\`\`\`${language}\n${code}\n\`\`\``; } /** * Process blockquote node */ function processBlockquote(node) { if (!node.content) { return ''; } const content = node.content .map((childNode) => processAdfNode(childNode)) .join('\n\n'); // Add > to each line return content .split('\n') .map((line) => `> ${line}`) .join('\n'); } /** * Process media group node */ function processMediaGroup(node) { if (!node.content) { return ''; } return node.content .map((mediaNode) => { if (mediaNode.type === 'media' && mediaNode.attrs) { const { id, type } = mediaNode.attrs; if (type === 'file') { return `[Attachment: ${id}]`; } else if (type === 'link') { return `[External Link]`; } } return ''; }) .filter(Boolean) .join('\n'); } /** * Process table node */ function processTable(node) { if (!node.content) { return ''; } const rows = []; // Process table rows node.content.forEach((row) => { if (row.type === 'tableRow' && row.content) { const cells = []; row.content.forEach((cell) => { if ((cell.type === 'tableCell' || cell.type === 'tableHeader') && cell.content) { const cellContent = cell.content .map((cellNode) => processAdfNode(cellNode)) .join(''); cells.push(cellContent.trim()); } }); if (cells.length > 0) { rows.push(cells); } } }); if (rows.length === 0) { return ''; } // Create markdown table const columnCount = Math.max(...rows.map((row) => row.length)); // Ensure all rows have the same number of columns const normalizedRows = rows.map((row) => { while (row.length < columnCount) { row.push(''); } return row; }); // Create header row const headerRow = normalizedRows[0].map((cell) => cell || ''); // Create separator row const separatorRow = headerRow.map(() => '---'); // Create content rows const contentRows = normalizedRows.slice(1); // Build the table const tableRows = [ headerRow.join(' | '), separatorRow.join(' | '), ...contentRows.map((row) => row.join(' | ')), ]; return tableRows.join('\n'); } /** * Process text node */ function processText(node) { if (!node.text) { return ''; } let text = node.text; // Apply marks if available if (node.marks && node.marks.length > 0) { // Process link marks last to avoid issues with other formatting const linkMark = node.marks.find((mark) => mark.type === 'link'); const otherMarks = node.marks.filter((mark) => mark.type !== 'link'); // Apply non-link marks first otherMarks.forEach((mark) => { switch (mark.type) { case 'strong': text = `**${text}**`; break; case 'em': text = `*${text}*`; break; case 'code': text = `\`${text}\``; break; case 'strike': text = `~~${text}~~`; break; case 'underline': // Markdown doesn't support underline, use emphasis instead text = `_${text}_`; break; } }); // Apply link mark last if (linkMark && linkMark.attrs && linkMark.attrs.href) { text = `[${text}](${linkMark.attrs.href})`; } } return text; }