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

172 lines (171 loc) 8.61 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const logger_util_js_1 = require("../utils/logger.util.js"); const error_util_js_1 = require("../utils/error.util.js"); const error_handler_util_js_1 = require("../utils/error-handler.util.js"); const vendor_atlassian_spaces_service_js_1 = __importDefault(require("../services/vendor.atlassian.spaces.service.js")); const atlassian_spaces_formatter_js_1 = require("../controllers/atlassian.spaces.formatter.js"); const atlassian_pages_controller_js_1 = __importDefault(require("./atlassian.pages.controller.js")); const defaults_util_js_1 = require("../utils/defaults.util.js"); /** * Controller for managing Confluence spaces. * Provides functionality for listing spaces and retrieving space details. */ /** * List Confluence spaces with optional filtering * @param options - Options for filtering spaces * @param options.type - Filter by space type (global, personal, etc.) * @param options.status - Filter by space status (current, archived) * @param options.limit - Maximum number of spaces to return * @param options.cursor - Pagination cursor for subsequent requests * @returns Promise with formatted spaces list content and pagination info * @throws Error if space listing fails */ async function list(options = {}) { const controllerLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.spaces.controller.ts', 'list'); controllerLogger.debug('Listing Confluence spaces with options:', options); try { // Create defaults object with proper typing const defaults = { limit: defaults_util_js_1.DEFAULT_PAGE_SIZE, sort: '-name', }; // Apply defaults const mergedOptions = (0, defaults_util_js_1.applyDefaults)(options, defaults); // Map controller options to service parameters const params = { type: mergedOptions.type, status: mergedOptions.status, limit: mergedOptions.limit, cursor: mergedOptions.cursor, // Additional parameters sort: mergedOptions.sort, // Already typed correctly through ListSpacesParams descriptionFormat: 'view', includeIcon: true, // Include space icons in response }; controllerLogger.debug('Using params:', params); const spacesData = await vendor_atlassian_spaces_service_js_1.default.list(params); // Log only summary information instead of the entire response controllerLogger.debug(`Retrieved ${spacesData.results.length} spaces. Has more: ${spacesData._links?.next ? 'yes' : 'no'}`); // The formatSpacesList function expects a spacesData parameter // Extract the nextCursor from the links const nextCursor = spacesData._links?.next?.split('cursor=')[1] || ''; const formattedSpaces = (0, atlassian_spaces_formatter_js_1.formatSpacesList)(spacesData); return { content: formattedSpaces, pagination: { count: spacesData.results.length, hasMore: !!spacesData._links?.next, nextCursor: nextCursor, }, }; } catch (error) { // Use the standardized error handler return (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Spaces', operation: 'listing', source: 'controllers/atlassian.spaces.controller.ts@list', }); } } /** * Get details of a specific Confluence space * @param args - Object containing the key of the space to retrieve * @param args.spaceKey - The key of the space * @returns Promise with formatted space details content * @throws Error if space retrieval fails */ async function get(args) { const { spaceKey } = args; const controllerLogger = logger_util_js_1.Logger.forContext('controllers/atlassian.spaces.controller.ts', 'get'); controllerLogger.debug(`Getting Confluence space with key: ${spaceKey}...`); try { // Create defaults object with proper typing for space details const defaults = { descriptionFormat: 'view', includeIcon: false, includeLabels: true, includeOperations: false, includePermissions: defaults_util_js_1.SPACE_DEFAULTS.INCLUDE_PERMISSIONS, includeRoleAssignments: false, }; // Hardcoded parameters for the service call - use defaults const params = defaults; controllerLogger.debug('Using params:', params); // The Confluence API v2 doesn't provide a direct endpoint to get full space details by key. // It only allows retrieving spaces by ID for detailed information. Therefore, we must: // 1. Use the list endpoint with keys filter to find the space ID first // 2. Use the get endpoint with the ID to retrieve complete space details // This two-step lookup is necessary due to API constraints. controllerLogger.debug('Searching for space by key'); const spacesResponse = await vendor_atlassian_spaces_service_js_1.default.list({ keys: [spaceKey], limit: 1, ...params, }); // Check if space was found if (!spacesResponse.results || spacesResponse.results.length === 0) { throw (0, error_util_js_1.createApiError)(`Space not found with key: ${spaceKey}. Verify the space key is correct and that you have access to this space.`, 404); } // Get the space from the results const spaceId = spacesResponse.results[0].id; // Get full space details using the ID const spaceData = await vendor_atlassian_spaces_service_js_1.default.get(spaceId, params); // Log only key information instead of the entire response controllerLogger.debug(`Retrieved space: ${spaceData.name} (${spaceData.id})`); // Get homepage content if available let homepageContent = ''; if (spaceData.homepageId) { try { controllerLogger.debug(`Fetching homepage content for ID: ${spaceData.homepageId}`); const homepageResult = await atlassian_pages_controller_js_1.default.get({ pageId: spaceData.homepageId, }); // Extract content from the homepage result const content = homepageResult.content; // Look for the Content section or any main content const contentMatch = content.match(/## Content\n([\s\S]*?)(?=\n## |$)/) || content.match(/# .*?\n([\s\S]*?)(?=\n# |$)/); if (contentMatch && contentMatch[1]) { homepageContent = contentMatch[1].trim(); controllerLogger.debug('Successfully extracted homepage content section'); } else { // If no specific content section found, use everything after the title const titleMatch = content.match(/# .*?\n([\s\S]*)/); if (titleMatch && titleMatch[1]) { homepageContent = titleMatch[1].trim(); controllerLogger.debug('Extracted homepage content from title section'); } else { controllerLogger.debug('No content sections found in homepage'); } } } catch (error) { controllerLogger.warn(`Failed to fetch homepage content: ${error instanceof Error ? error.message : String(error)}`); homepageContent = '*Failed to retrieve homepage content. The page may be inaccessible or deleted.*'; } } // Format the space data for display with homepage content using the formatter const formattedSpace = (0, atlassian_spaces_formatter_js_1.formatSpaceDetails)(spaceData, homepageContent); return { content: formattedSpace, }; } catch (error) { // Use the standardized error handler return (0, error_handler_util_js_1.handleControllerError)(error, { entityType: 'Space', entityId: spaceKey, operation: 'retrieving', source: 'controllers/atlassian.spaces.controller.ts@get', }); } } exports.default = { list, get };