Skip to main content
Glama
atlassian.spaces.controller.ts9.2 kB
import { Logger } from '../utils/logger.util.js'; import { createApiError } from '../utils/error.util.js'; import { handleControllerError } from '../utils/error-handler.util.js'; import atlassianSpacesService from '../services/vendor.atlassian.spaces.service.js'; import atlassianPagesService from '../services/vendor.atlassian.pages.service.js'; import { formatSpacesList, formatSpaceDetails, } from '../controllers/atlassian.spaces.formatter.js'; import { ControllerResponse } from '../types/common.types.js'; import atlassianPagesController from './atlassian.pages.controller.js'; import { DEFAULT_PAGE_SIZE, SPACE_DEFAULTS, applyDefaults, } from '../utils/defaults.util.js'; import { ListSpacesParams, SpaceType, } from '../services/vendor.atlassian.spaces.types.js'; import { // ListSpacesToolArgsType, // Removed unused import GetSpaceToolArgsType, ListSpacesOptions, } from '../tools/atlassian.spaces.types.js'; import { extractPaginationInfo, PaginationType, } from '../utils/pagination.util.js'; import { formatPagination } from '../utils/formatter.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 including pagination information * @throws Error if space listing fails */ async function list( options: ListSpacesOptions = {}, ): Promise<ControllerResponse> { const controllerLogger = 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: Partial<ListSpacesOptions> = { limit: DEFAULT_PAGE_SIZE, type: 'global', // Add default that matches CLI option status: 'current', // Add default that matches CLI option }; // Apply defaults const mergedOptions = applyDefaults<ListSpacesOptions>( options, defaults, ); // Map controller options to service parameters const params: ListSpacesParams = { // Pass keys if provided keys: mergedOptions.keys, // Convert 'archived' type to actual API parameters - the tool uses a simplified schema type: mergedOptions.type === 'archived' ? 'global' : (mergedOptions.type as SpaceType | undefined), status: mergedOptions.status, limit: mergedOptions.limit, cursor: mergedOptions.cursor, // Additional parameters descriptionFormat: 'view', includeIcon: true, // Include space icons in response // Add sort parameter for the service sort: '-name', // Default sort order }; controllerLogger.debug('Using params:', params); const spacesData = await atlassianSpacesService.list(params); // Log only summary information controllerLogger.debug( `Retrieved ${spacesData.results.length} spaces. Has more: ${spacesData._links?.next ? 'yes' : 'no'}`, ); // Extract pagination info using the utility const pagination = extractPaginationInfo( spacesData, PaginationType.CURSOR, 'Space', ); // Format the spaces data using the formatter const formattedSpaces = formatSpacesList(spacesData); // Create the complete content string by appending the pagination information let finalContent = formattedSpaces; // Only add pagination information if it exists and contains relevant information if ( pagination && (pagination.hasMore || pagination.count !== undefined) ) { const paginationString = formatPagination(pagination); finalContent += '\n\n' + paginationString; } return { content: finalContent, }; } catch (error) { // Use the standardized error handler throw 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: GetSpaceToolArgsType): Promise<ControllerResponse> { const { spaceKey } = args; const controllerLogger = 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' as const, includeIcon: false, includeLabels: true, includeOperations: false, includePermissions: 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 atlassianSpacesService.list({ keys: [spaceKey], limit: 1, ...params, }); // Check if space was found if (!spacesResponse.results || spacesResponse.results.length === 0) { throw 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 atlassianSpacesService.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 atlassianPagesController.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.*'; } } // Fetch top-level pages in the space to include in the response let topLevelPagesData = null; try { controllerLogger.debug( `Fetching top pages for space ID: ${spaceId}`, ); topLevelPagesData = await atlassianPagesService.list({ spaceId: [spaceId], limit: 5, // Limit to 5 top-level pages sort: '-modified-date', // Sort by most recently modified status: ['current'], // Only show current (not archived/deleted) pages }); controllerLogger.debug( `Retrieved ${topLevelPagesData.results.length} top-level pages for the space`, ); } catch (error) { controllerLogger.warn( `Failed to fetch top-level pages: ${error instanceof Error ? error.message : String(error)}`, ); // Continue even if we couldn't get the pages } // Format the space data for display with homepage content using the formatter const formattedSpace = formatSpaceDetails( spaceData, homepageContent, topLevelPagesData, ); return { content: formattedSpace, }; } catch (error) { // Use the standardized error handler throw handleControllerError(error, { entityType: 'Space', entityId: spaceKey, operation: 'retrieving', source: 'controllers/atlassian.spaces.controller.ts@get', }); } } export default { list, get };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aashari/mcp-server-atlassian-confluence'

If you have feedback or need assistance with the MCP directory API, please join our Discord server