@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
JavaScript
;
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 };