@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
236 lines (235 loc) • 8.85 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.McpError = exports.ErrorType = void 0;
exports.getDeepOriginalError = getDeepOriginalError;
exports.createAuthMissingError = createAuthMissingError;
exports.createAuthInvalidError = createAuthInvalidError;
exports.createApiError = createApiError;
exports.createNotFoundError = createNotFoundError;
exports.createUnexpectedError = createUnexpectedError;
exports.ensureMcpError = ensureMcpError;
exports.formatErrorForMcpTool = formatErrorForMcpTool;
exports.formatErrorForMcpResource = formatErrorForMcpResource;
exports.handleCliError = handleCliError;
const logger_util_js_1 = require("./logger.util.js");
const formatter_util_js_1 = require("./formatter.util.js");
/**
* Error types for classification
*/
var ErrorType;
(function (ErrorType) {
ErrorType["AUTH_MISSING"] = "AUTH_MISSING";
ErrorType["AUTH_INVALID"] = "AUTH_INVALID";
ErrorType["API_ERROR"] = "API_ERROR";
ErrorType["NOT_FOUND"] = "NOT_FOUND";
ErrorType["UNEXPECTED_ERROR"] = "UNEXPECTED_ERROR";
})(ErrorType || (exports.ErrorType = ErrorType = {}));
/**
* Custom error class with type classification
*/
class McpError extends Error {
constructor(message, type, statusCode, originalError) {
super(message);
this.name = 'McpError';
this.type = type;
this.statusCode = statusCode;
this.originalError = originalError;
}
}
exports.McpError = McpError;
/**
* Helper to unwrap nested McpErrors and return the deepest original error.
* This is useful when an McpError contains another McpError as `originalError`
* which in turn may wrap the vendor (Confluence) error text or object.
*/
function getDeepOriginalError(error) {
if (!error) {
return error;
}
let current = error;
let depth = 0;
const maxDepth = 10; // Prevent infinite recursion
while (depth < maxDepth &&
current instanceof Error &&
'originalError' in current &&
current.originalError) {
current = current.originalError;
depth++;
}
return current;
}
/**
* Create an authentication missing error
*/
function createAuthMissingError(message = 'Authentication credentials are missing', originalError) {
return new McpError(message, ErrorType.AUTH_MISSING, undefined, originalError);
}
/**
* Create an authentication invalid error
*/
function createAuthInvalidError(message = 'Authentication credentials are invalid', originalError) {
return new McpError(message, ErrorType.AUTH_INVALID, 401, originalError);
}
/**
* Create an API error
*/
function createApiError(message, statusCode, originalError) {
return new McpError(message, ErrorType.API_ERROR, statusCode, originalError);
}
/**
* Create a not found error
*/
function createNotFoundError(message = 'Resource not found', originalError) {
return new McpError(message, ErrorType.NOT_FOUND, 404, originalError);
}
/**
* Create an unexpected error
*/
function createUnexpectedError(message = 'An unexpected error occurred', originalError) {
return new McpError(message, ErrorType.UNEXPECTED_ERROR, undefined, originalError);
}
/**
* Ensure an error is an McpError
*/
function ensureMcpError(error) {
if (error instanceof McpError) {
return error;
}
if (error instanceof Error) {
return createUnexpectedError(error.message, error);
}
return createUnexpectedError(String(error));
}
/**
* Format error for MCP tool response
*/
function formatErrorForMcpTool(error) {
const methodLogger = logger_util_js_1.Logger.forContext('utils/error.util.ts', 'formatErrorForMcpTool');
const mcpError = ensureMcpError(error);
methodLogger.error(`${mcpError.type} error`, mcpError);
// Get the deep original error for additional context
const originalError = getDeepOriginalError(mcpError.originalError);
// Safely extract details from the original error
const errorDetails = originalError instanceof Error
? { message: originalError.message }
: originalError;
return {
content: [
{
type: 'text',
text: `Error: ${mcpError.message}`,
},
],
metadata: {
errorType: mcpError.type,
statusCode: mcpError.statusCode,
errorDetails,
},
};
}
/**
* Format error for MCP resource response
*/
function formatErrorForMcpResource(error, uri) {
const methodLogger = logger_util_js_1.Logger.forContext('utils/error.util.ts', 'formatErrorForMcpResource');
const mcpError = ensureMcpError(error);
methodLogger.error(`${mcpError.type} error`, mcpError);
return {
contents: [
{
uri,
text: `Error: ${mcpError.message}`,
mimeType: 'text/plain',
description: `Error: ${mcpError.type}`,
},
],
};
}
/**
* Handle error in CLI context with improved user feedback
*/
function handleCliError(error) {
const methodLogger = logger_util_js_1.Logger.forContext('utils/error.util.ts', 'handleCliError');
const mcpError = ensureMcpError(error);
methodLogger.error(`${mcpError.type} error`, mcpError);
// Get the deep original error for more context
const originalError = getDeepOriginalError(mcpError.originalError);
// Build a well-formatted CLI output using markdown-style helpers
const cliLines = [];
// Primary error headline
cliLines.push(`❌ ${mcpError.message}`);
// Status code (if any)
if (mcpError.statusCode) {
cliLines.push(`HTTP Status: ${mcpError.statusCode}`);
}
// Separator
cliLines.push((0, formatter_util_js_1.formatSeparator)());
// Provide helpful context based on error type
if (mcpError.type === ErrorType.AUTH_MISSING) {
cliLines.push('Tip: Make sure to set up your Atlassian credentials in the configuration file or environment variables:');
cliLines.push('- ATLASSIAN_SITE_NAME, ATLASSIAN_USER_EMAIL, and ATLASSIAN_API_TOKEN');
}
else if (mcpError.type === ErrorType.AUTH_INVALID) {
cliLines.push('Tip: Check that your Atlassian API token is correct and has not expired.');
cliLines.push('Also verify that the configured user has access to the requested resource.');
}
else if (mcpError.type === ErrorType.API_ERROR) {
if (mcpError.statusCode === 429) {
cliLines.push('Tip: You may have exceeded your Confluence API rate limits. Try again later.');
}
}
else if (mcpError.type === ErrorType.NOT_FOUND) {
cliLines.push('Tip: Verify the resource ID or key is correct and that you have access to view this resource.');
cliLines.push('If you are using a space key, make sure it is spelled correctly (including case).');
}
// Vendor error details (if available)
if (originalError) {
cliLines.push('Confluence API Error:');
cliLines.push('```');
if (typeof originalError === 'object' && originalError !== null) {
// Try to extract the most useful parts of Confluence's error response
const origErr = originalError;
if (origErr.message) {
cliLines.push(`Message: ${origErr.message}`);
}
if (origErr.detail) {
cliLines.push(`Detail: ${origErr.detail}`);
}
if (origErr.title) {
cliLines.push(`Title: ${origErr.title}`);
}
if (origErr.status) {
cliLines.push(`Status: ${origErr.status}`);
}
// Handle errors array format
if (origErr.errors &&
Array.isArray(origErr.errors) &&
origErr.errors.length > 0) {
cliLines.push('Errors:');
for (const err of origErr.errors) {
if (typeof err === 'object' && err !== null) {
const errObj = err;
cliLines.push(` - ${errObj.message || errObj.title || JSON.stringify(err)}`);
}
else {
cliLines.push(` - ${String(err)}`);
}
}
}
else {
// Fall back to JSON representation for anything else
cliLines.push(JSON.stringify(originalError, null, 2));
}
}
else {
cliLines.push(String(originalError).trim());
}
cliLines.push('```');
}
// Display DEBUG tip
if (!process.env.DEBUG || !process.env.DEBUG.includes('mcp:')) {
cliLines.push('For more detailed error information, run with DEBUG=mcp:* environment variable.');
}
console.error(cliLines.join('\n'));
process.exit(1);
}