UNPKG

@aashari/mcp-server-atlassian-confluence

Version:

Node.js/TypeScript MCP server for Atlassian Confluence. Provides tools enabling AI systems (LLMs) to list/get spaces & pages (content formatted as Markdown) and search via CQL. Connects AI seamlessly to Confluence knowledge bases using the standard MCP in

248 lines (247 loc) 11.8 kB
"use strict"; /** * Controller for Confluence comments */ Object.defineProperty(exports, "__esModule", { value: true }); exports.atlassianCommentsController = void 0; const logger_util_js_1 = require("../utils/logger.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const vendor_atlassian_comments_service_js_1 = require("../services/vendor.atlassian.comments.service.js"); const pagination_util_js_1 = require("../utils/pagination.util.js"); const atlassian_comments_formatter_js_1 = require("./atlassian.comments.formatter.js"); const atlassian_inline_comments_formatter_js_1 = require("./atlassian.inline-comments.formatter.js"); const defaults_util_js_1 = require("../utils/defaults.util.js"); const adf_util_js_1 = require("../utils/adf.util.js"); const formatter_util_js_1 = require("../utils/formatter.util.js"); // Create logger for this controller const logger = logger_util_js_1.Logger.forContext('controllers/atlassian.comments.controller.ts'); /** * List comments for a specific Confluence page * * @param options - Options for listing comments * @returns Controller response with formatted comments and pagination info */ async function listPageComments(options) { const methodLogger = logger.forMethod('listPageComments'); try { // Apply defaults and prepare service parameters const { pageId, limit = defaults_util_js_1.DEFAULT_PAGE_SIZE, start = 0, bodyFormat = 'atlas_doc_format', // Explicitly define default } = options; methodLogger.debug('Listing page comments', { pageId, limit, start, bodyFormat, }); // Call the service to get comments data const commentsData = await vendor_atlassian_comments_service_js_1.atlassianCommentsService.listPageComments({ pageId, limit, start, bodyFormat, }); // Extract pagination information const pagination = (0, pagination_util_js_1.extractPaginationInfo)(commentsData, pagination_util_js_1.PaginationType.OFFSET, 'Comment'); // Convert ADF content to Markdown and extract highlighted text for inline comments const convertedComments = commentsData.results.map((comment) => { let markdownBody = '*Content format not supported or unavailable*'; // Convert comment body from ADF to Markdown if (comment.body?.atlas_doc_format?.value) { try { markdownBody = (0, adf_util_js_1.adfToMarkdown)(comment.body.atlas_doc_format.value); methodLogger.debug(`Successfully converted ADF to Markdown for comment ${comment.id}`); } catch (conversionError) { methodLogger.error(`ADF conversion failed for comment ${comment.id}`, conversionError); // Keep default error message } } else { methodLogger.warn(`No ADF content available for comment ${comment.id}`); } // Extract the highlighted text for inline comments let highlightedText = undefined; if (comment.extensions?.location === 'inline' && comment.extensions.inlineProperties) { // Safely access inlineProperties fields with type checking const props = comment.extensions .inlineProperties; // Try different properties that might contain the highlighted text // Some Confluence versions use different property names highlightedText = props.originalSelection || props.textContext; // If not found in standard properties, check for custom properties if (!highlightedText && 'selectionText' in props) { highlightedText = String(props.selectionText || ''); } if (highlightedText) { methodLogger.debug(`Found highlighted text for comment ${comment.id}: ${highlightedText.substring(0, 50)}${highlightedText.length > 50 ? '...' : ''}`); } else { methodLogger.warn(`No highlighted text found for inline comment ${comment.id}`); } } // Return comment with added context return { ...comment, convertedMarkdownBody: markdownBody, highlightedText, }; }); // Format the comments for display const baseUrl = commentsData._links?.base || ''; const formattedContent = (0, atlassian_comments_formatter_js_1.formatCommentsList)(convertedComments, pageId, baseUrl); // Create the final content with pagination information included let finalContent = formattedContent; // Add pagination information if available if (pagination && (pagination.hasMore || pagination.count !== undefined)) { const paginationString = (0, formatter_util_js_1.formatPagination)(pagination); finalContent += '\n\n' + paginationString; } return { content: finalContent, }; } catch (error) { // Handle errors throw (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Comment', operation: 'list', source: 'controllers/atlassian.comments.controller.ts@listPageComments', additionalInfo: { pageId: options.pageId }, }); } } /** * List inline comments only for a specific Confluence page * * @param options - Options for listing inline comments * @returns Controller response with formatted inline comments and pagination info */ async function listInlineComments(options) { const methodLogger = logger.forMethod('listInlineComments'); try { // Apply defaults and prepare service parameters const { pageId, includeResolved = false, sortBy = 'position', limit = defaults_util_js_1.DEFAULT_PAGE_SIZE, start = 0, bodyFormat = 'atlas_doc_format', } = options; methodLogger.debug('Listing inline comments for page', { pageId, includeResolved, sortBy, limit, start, bodyFormat, }); // Get all comments first with a higher limit to ensure we capture inline comments // since we'll filter them locally const allCommentsData = await vendor_atlassian_comments_service_js_1.atlassianCommentsService.listPageComments({ pageId, limit: 250, // Get more comments to filter inline ones start: 0, // Always start from beginning for inline filtering bodyFormat, }); methodLogger.debug('Retrieved all comments for filtering', { totalComments: allCommentsData.results.length, pageId, }); // Filter for inline comments only const inlineCommentsRaw = allCommentsData.results.filter((comment) => { const isInline = comment.extensions?.location === 'inline'; // Apply resolved filter if needed if (!includeResolved && comment.status !== 'current') { return false; } return isInline; }); methodLogger.debug('Filtered inline comments', { inlineCount: inlineCommentsRaw.length, totalCount: allCommentsData.results.length, includeResolved, }); // Convert ADF content to Markdown and extract highlighted text for inline comments const convertedComments = inlineCommentsRaw.map((comment) => { let markdownBody = '*Content format not supported or unavailable*'; // Convert comment body from ADF to Markdown if (comment.body?.atlas_doc_format?.value) { try { markdownBody = (0, adf_util_js_1.adfToMarkdown)(comment.body.atlas_doc_format.value); methodLogger.debug(`Successfully converted ADF to Markdown for inline comment ${comment.id}`); } catch (conversionError) { methodLogger.error(`ADF conversion failed for inline comment ${comment.id}`, conversionError); // Keep default error message } } else { methodLogger.warn(`No ADF content available for inline comment ${comment.id}`); } // Extract the highlighted text for inline comments let highlightedText = undefined; if (comment.extensions?.inlineProperties) { // Safely access inlineProperties fields with type checking const props = comment.extensions .inlineProperties; // Try different properties that might contain the highlighted text highlightedText = props.originalSelection || props.textContext; // If not found in standard properties, check for custom properties if (!highlightedText && 'selectionText' in props) { highlightedText = String(props.selectionText || ''); } if (highlightedText) { methodLogger.debug(`Found highlighted text for inline comment ${comment.id}: ${highlightedText.substring(0, 50)}${highlightedText.length > 50 ? '...' : ''}`); } else { methodLogger.warn(`No highlighted text found for inline comment ${comment.id}`); } } // Return comment with added context return { ...comment, convertedMarkdownBody: markdownBody, highlightedText, }; }); // Sort inline comments by requested order if (sortBy === 'position') { convertedComments.sort((a, b) => { // Sort by marker position or container ID if available const aPos = a.extensions?.inlineProperties?.markerRef || a.id; const bPos = b.extensions?.inlineProperties?.markerRef || b.id; return String(aPos).localeCompare(String(bPos)); }); } else if (sortBy === 'created') { // Sort by ID as a proxy for creation order (newer IDs = later created) convertedComments.sort((a, b) => a.id.localeCompare(b.id)); } // Apply pagination after filtering and sorting const paginatedComments = convertedComments.slice(start, start + limit); methodLogger.debug('Applied pagination to inline comments', { totalInline: convertedComments.length, start, limit, returned: paginatedComments.length, }); // Format the inline comments for display const baseUrl = allCommentsData._links?.base || ''; const formattedContent = (0, atlassian_inline_comments_formatter_js_1.formatInlineCommentsList)(paginatedComments, pageId, baseUrl, convertedComments.length, start, limit); return { content: formattedContent, }; } catch (error) { // Handle errors throw (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'InlineComment', operation: 'list', source: 'controllers/atlassian.comments.controller.ts@listInlineComments', additionalInfo: { pageId: options.pageId }, }); } } // Export controller functions exports.atlassianCommentsController = { listPageComments, listInlineComments, };