@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
186 lines (185 loc) • 6.75 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateCredentials = validateCredentials;
exports.normalizePath = normalizePath;
exports.appendQueryParams = appendQueryParams;
exports.request = request;
exports.get = get;
exports.post = post;
exports.put = put;
exports.patch = patch;
exports.del = del;
const logger_util_js_1 = require("../utils/logger.util.js");
const transport_util_js_1 = require("../utils/transport.util.js");
const error_util_js_1 = require("../utils/error.util.js");
/**
* @namespace VendorAtlassianApiService
* @description Service layer for interacting with the Atlassian Confluence API.
* Responsible for credentials validation, path normalization,
* and making raw API requests via the transport utility.
*
* This service provides a thin wrapper around fetchAtlassian() to maintain
* consistent layered architecture across all MCP servers:
* - Transport (transport.util.ts): Raw HTTP operations
* - Service (this file): API-specific logic, credentials, path handling
* - Controller: Business logic, filtering, formatting
*/
// Create a contextualized logger for this file
const serviceLogger = logger_util_js_1.Logger.forContext('services/vendor.atlassian.api.service.ts');
// Log service initialization
serviceLogger.debug('Confluence API service initialized');
/**
* Validates and returns Atlassian credentials
* @throws {McpError} If credentials are missing
* @returns {AtlassianCredentials} Valid credentials
*/
function validateCredentials() {
const methodLogger = logger_util_js_1.Logger.forContext('services/vendor.atlassian.api.service.ts', 'validateCredentials');
const credentials = (0, transport_util_js_1.getAtlassianCredentials)();
if (!credentials) {
methodLogger.error('Missing Atlassian credentials');
throw (0, error_util_js_1.createAuthMissingError)();
}
methodLogger.debug('Credentials validated successfully');
return credentials;
}
/**
* Normalizes the API path by ensuring it starts with /
* @param path - The raw path provided by the user
* @returns Normalized path
*/
function normalizePath(path) {
let normalizedPath = path;
if (!normalizedPath.startsWith('/')) {
normalizedPath = '/' + normalizedPath;
}
return normalizedPath;
}
/**
* Appends query parameters to a path
* @param path - The base path
* @param queryParams - Optional query parameters
* @returns Path with query string appended
*/
function appendQueryParams(path, queryParams) {
if (!queryParams || Object.keys(queryParams).length === 0) {
return path;
}
const queryString = new URLSearchParams(queryParams).toString();
return path + (path.includes('?') ? '&' : '?') + queryString;
}
/**
* Makes a generic API request to the Confluence API
*
* @param path - API endpoint path (e.g., '/wiki/api/v2/spaces')
* @param options - Request options including method, queryParams, and body
* @returns Promise resolving to the raw API response with rawResponsePath
* @throws {McpError} If credentials are missing or API request fails
*
* @example
* // GET request
* const spaces = await request('/wiki/api/v2/spaces', {
* method: 'GET',
* queryParams: { limit: '10' }
* });
*
* @example
* // POST request
* const page = await request('/wiki/api/v2/pages', {
* method: 'POST',
* body: { spaceId: '123', title: 'New Page', ... }
* });
*/
async function request(path, options = {}) {
const methodLogger = logger_util_js_1.Logger.forContext('services/vendor.atlassian.api.service.ts', 'request');
const method = options.method || 'GET';
methodLogger.debug(`Making ${method} request to ${path}`);
try {
// Validate credentials
const credentials = validateCredentials();
// Normalize path and append query params
let normalizedPath = normalizePath(path);
normalizedPath = appendQueryParams(normalizedPath, options.queryParams);
methodLogger.debug(`Normalized path: ${normalizedPath}`);
// Prepare fetch options
const fetchOptions = {
method,
};
// Add body for methods that support it
if (options.body && ['POST', 'PUT', 'PATCH'].includes(method)) {
fetchOptions.body = options.body;
}
// Make the API call
const response = await (0, transport_util_js_1.fetchAtlassian)(credentials, normalizedPath, fetchOptions);
methodLogger.debug('Successfully received response from Confluence API');
return response;
}
catch (error) {
methodLogger.error(`Service error during ${method} request to ${path}`, error);
// Rethrow McpErrors as-is
if (error instanceof error_util_js_1.McpError) {
throw error;
}
// This shouldn't happen as fetchAtlassian wraps all errors
throw error;
}
}
/**
* Makes a GET request to the Confluence API
* @param path - API endpoint path
* @param queryParams - Optional query parameters
* @returns Promise resolving to the API response with rawResponsePath
*/
async function get(path, queryParams) {
return request(path, { method: 'GET', queryParams });
}
/**
* Makes a POST request to the Confluence API
* @param path - API endpoint path
* @param body - Request body
* @param queryParams - Optional query parameters
* @returns Promise resolving to the API response with rawResponsePath
*/
async function post(path, body, queryParams) {
return request(path, { method: 'POST', body, queryParams });
}
/**
* Makes a PUT request to the Confluence API
* @param path - API endpoint path
* @param body - Request body
* @param queryParams - Optional query parameters
* @returns Promise resolving to the API response with rawResponsePath
*/
async function put(path, body, queryParams) {
return request(path, { method: 'PUT', body, queryParams });
}
/**
* Makes a PATCH request to the Confluence API
* @param path - API endpoint path
* @param body - Request body
* @param queryParams - Optional query parameters
* @returns Promise resolving to the API response with rawResponsePath
*/
async function patch(path, body, queryParams) {
return request(path, { method: 'PATCH', body, queryParams });
}
/**
* Makes a DELETE request to the Confluence API
* @param path - API endpoint path
* @param queryParams - Optional query parameters
* @returns Promise resolving to the API response with rawResponsePath
*/
async function del(path, queryParams) {
return request(path, { method: 'DELETE', queryParams });
}
exports.default = {
request,
get,
post,
put,
patch,
del,
validateCredentials,
normalizePath,
appendQueryParams,
};