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

169 lines (168 loc) 6.53 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PaginationType = void 0; exports.adaptResponseForPagination = adaptResponseForPagination; exports.extractPaginationInfo = extractPaginationInfo; const logger_util_js_1 = require("./logger.util.js"); const paginationLogger = logger_util_js_1.Logger.forContext('utils/pagination.util.ts'); /** * Available pagination types supported by the application */ var PaginationType; (function (PaginationType) { /** * Offset-based pagination (startAt, maxResults, total) * Used by Jira APIs */ PaginationType["OFFSET"] = "offset"; /** * Cursor-based pagination (cursor in URL) * Used by Confluence APIs */ PaginationType["CURSOR"] = "cursor"; /** * Page-based pagination (page parameter in URL) * Used by Bitbucket APIs */ PaginationType["PAGE"] = "page"; })(PaginationType || (exports.PaginationType = PaginationType = {})); /** * Adapter function to convert Zod-validated API responses to pagination data * @param data Any API response with potential pagination properties * @returns A PaginationData compatible object */ function adaptResponseForPagination(data) { // Create a basic pagination data structure const paginationData = {}; // Handle _links for cursor-based pagination if (data && typeof data === 'object' && '_links' in data) { const typedData = data; paginationData._links = { next: typedData._links?.next }; } // For offset-based pagination if (data && typeof data === 'object' && 'startAt' in data) { paginationData.startAt = data.startAt; } if (data && typeof data === 'object' && 'maxResults' in data) { paginationData.maxResults = data.maxResults; } if (data && typeof data === 'object' && 'total' in data) { paginationData.total = data.total; } if (data && typeof data === 'object' && 'isLast' in data) { paginationData.isLast = data.isLast; } // For page-based pagination if (data && typeof data === 'object' && 'page' in data) { paginationData.page = data.page; } if (data && typeof data === 'object' && 'size' in data) { paginationData.size = data.size; } if (data && typeof data === 'object' && 'totalElements' in data) { paginationData.totalElements = data.totalElements; } if (data && typeof data === 'object' && 'totalPages' in data) { paginationData.totalPages = data.totalPages; } if (data && typeof data === 'object' && 'last' in data) { paginationData.last = data.last; } return paginationData; } /** * Extract pagination information from an API response and convert it to a standardized format * @param data The API response data containing pagination information * @param type The type of pagination used in the response * @param entityType Optional entity type for logging * @returns Standardized pagination object or undefined if no pagination info is available */ function extractPaginationInfo(data, type, entityType) { // First adapt the response to ensure it has a compatible structure const adaptedData = adaptResponseForPagination(data); // Apply normal extraction logic to the adapted data const logger = paginationLogger.forMethod('extractPaginationInfo'); // Extract the results array for counting items let resultsArray = []; if (data && typeof data === 'object' && 'results' in data && Array.isArray(data.results)) { resultsArray = data.results; } else if (data && Array.isArray(data)) { resultsArray = data; } // Count of items in the current response const count = resultsArray.length; // Default to undefined - will be populated based on pagination type let hasMore = false; let nextCursor = undefined; let total = undefined; try { switch (type) { case PaginationType.CURSOR: // Cursor-based pagination (Confluence API v2 style) if (adaptedData._links?.next) { hasMore = true; // Extract cursor from next link if it exists const cursorMatch = adaptedData._links.next.match(/cursor=([^&]+)/); if (cursorMatch && cursorMatch[1]) { nextCursor = cursorMatch[1]; } } else { hasMore = false; } break; case PaginationType.OFFSET: { // Offset-based pagination (Jira API style) const offsetData = adaptedData; if (offsetData.startAt !== undefined && offsetData.maxResults !== undefined && offsetData.total !== undefined) { const endAt = offsetData.startAt + count; hasMore = endAt < offsetData.total; nextCursor = hasMore ? endAt.toString() : undefined; total = offsetData.total; } else if (offsetData.isLast !== undefined) { hasMore = !offsetData.isLast; } break; } case PaginationType.PAGE: { // Page-based pagination const pageData = adaptedData; if (pageData.last !== undefined) { hasMore = !pageData.last; if (hasMore && pageData.page !== undefined) { nextCursor = (pageData.page + 1).toString(); } } if (pageData.totalElements !== undefined) { total = pageData.totalElements; } break; } default: logger.warn(`Unknown pagination type: ${type} for ${entityType || 'entity'}`); return undefined; } // Return the standardized pagination object return { count, hasMore, ...(nextCursor && { nextCursor }), ...(total !== undefined && { total }), }; } catch (error) { logger.warn(`Error extracting pagination info for ${entityType || 'entity'}: ${String(error)}`); // If error occurred, return basic info without next cursor return { count, hasMore: false, }; } }