@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
201 lines (200 loc) • 10.5 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 vendor_atlassian_pages_service_js_1 = __importDefault(require("../services/vendor.atlassian.pages.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");
const pagination_util_js_1 = require("../utils/pagination.util.js");
const formatter_util_js_1 = require("../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 = {}) {
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,
type: 'global', // Add default that matches CLI option
status: 'current', // Add default that matches CLI option
};
// Apply defaults
const mergedOptions = (0, defaults_util_js_1.applyDefaults)(options, defaults);
// Map controller options to service parameters
const params = {
// 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,
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 vendor_atlassian_spaces_service_js_1.default.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 = (0, pagination_util_js_1.extractPaginationInfo)(spacesData, pagination_util_js_1.PaginationType.CURSOR, 'Space');
// Format the spaces data using the formatter
const formattedSpaces = (0, atlassian_spaces_formatter_js_1.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 = (0, formatter_util_js_1.formatPagination)(pagination);
finalContent += '\n\n' + paginationString;
}
return {
content: finalContent,
};
}
catch (error) {
// Use the standardized error handler
throw (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.*';
}
}
// 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 vendor_atlassian_pages_service_js_1.default.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 = (0, atlassian_spaces_formatter_js_1.formatSpaceDetails)(spaceData, homepageContent, topLevelPagesData);
return {
content: formattedSpace,
};
}
catch (error) {
// Use the standardized error handler
throw (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 };