@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
192 lines (191 loc) • 8.27 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.startServer = startServer;
const mcp_js_1 = require("@modelcontextprotocol/sdk/server/mcp.js");
const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
const logger_util_js_1 = require("./utils/logger.util.js");
const config_util_js_1 = require("./utils/config.util.js");
const constants_util_js_1 = require("./utils/constants.util.js");
const index_js_1 = require("./cli/index.js");
const express_1 = __importDefault(require("express"));
const cors_1 = __importDefault(require("cors"));
// Import Confluence-specific tools
const atlassian_spaces_tool_js_1 = __importDefault(require("./tools/atlassian.spaces.tool.js"));
const atlassian_pages_tool_js_1 = __importDefault(require("./tools/atlassian.pages.tool.js"));
const atlassian_search_tool_js_1 = __importDefault(require("./tools/atlassian.search.tool.js"));
const atlassian_comments_tool_js_1 = __importDefault(require("./tools/atlassian.comments.tool.js"));
const atlassian_inline_comments_tool_js_1 = __importDefault(require("./tools/atlassian.inline-comments.tool.js"));
// Create a contextualized logger for this file
const indexLogger = logger_util_js_1.Logger.forContext('index.ts');
// Log initialization at debug level
indexLogger.debug('Confluence MCP server module loaded');
let serverInstance = null;
let transportInstance = null;
/**
* Start the MCP server with the specified transport mode
*
* @param mode The transport mode to use (stdio or sse)
* @returns Promise that resolves to the server instance when started successfully
*/
async function startServer(mode = 'stdio') {
// Create method-level logger with more specific context
const serverLogger = logger_util_js_1.Logger.forContext('index.ts', 'startServer');
// Load configuration
serverLogger.info('Starting MCP server initialization...');
config_util_js_1.config.load();
serverLogger.info('Configuration loaded successfully');
// Enable debug logging if DEBUG is set to true
if (config_util_js_1.config.getBoolean('DEBUG')) {
serverLogger.debug('Debug mode enabled');
}
// Log debug configuration settings at debug level
serverLogger.debug(`DEBUG environment variable: ${process.env.DEBUG}`);
serverLogger.debug(`ATLASSIAN_API_TOKEN exists: ${Boolean(process.env.ATLASSIAN_API_TOKEN)}`);
serverLogger.debug(`Config DEBUG value: ${config_util_js_1.config.get('DEBUG')}`);
serverLogger.info(`Initializing Confluence MCP server v${constants_util_js_1.VERSION}`);
serverInstance = new mcp_js_1.McpServer({
name: constants_util_js_1.PACKAGE_NAME,
version: constants_util_js_1.VERSION,
});
// Register tools
serverLogger.info('Registering MCP tools...');
atlassian_spaces_tool_js_1.default.registerTools(serverInstance);
serverLogger.debug('Registered Spaces tools');
atlassian_pages_tool_js_1.default.registerTools(serverInstance);
serverLogger.debug('Registered Pages tools');
atlassian_search_tool_js_1.default.registerTools(serverInstance);
serverLogger.debug('Registered Search tools');
atlassian_comments_tool_js_1.default.registerTools(serverInstance);
serverLogger.debug('Registered Comments tools');
atlassian_inline_comments_tool_js_1.default.registerTools(serverInstance);
serverLogger.debug('Registered Inline Comments tools');
serverLogger.info('All tools registered successfully');
if (mode === 'stdio') {
serverLogger.info('Using STDIO transport for MCP communication');
transportInstance = new stdio_js_1.StdioServerTransport();
try {
await serverInstance.connect(transportInstance);
serverLogger.info('MCP server started successfully on STDIO transport');
setupGracefulShutdown();
return serverInstance;
}
catch (err) {
serverLogger.error('Failed to start server on STDIO transport', err);
process.exit(1);
}
}
else {
// HTTP Transport with Express
serverLogger.info('Using Streamable HTTP transport for MCP communication');
const app = (0, express_1.default)();
app.use((0, cors_1.default)());
app.use(express_1.default.json());
const mcpEndpoint = '/mcp';
serverLogger.debug(`MCP endpoint: ${mcpEndpoint}`);
// Create transport instance
const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
// Connect server to transport
await serverInstance.connect(transport);
transportInstance = transport;
// Handle all MCP requests
app.all(mcpEndpoint, (req, res) => {
transport
.handleRequest(req, res, req.body)
.catch((err) => {
serverLogger.error('Error in transport.handleRequest', err);
if (!res.headersSent) {
res.status(500).json({
error: 'Internal Server Error',
});
}
});
});
// Health check endpoint
app.get('/', (_req, res) => {
res.send(`Confluence MCP Server v${constants_util_js_1.VERSION} is running`);
});
// Start HTTP server
const PORT = Number(process.env.PORT ?? 3000);
await new Promise((resolve) => {
app.listen(PORT, () => {
serverLogger.info(`HTTP transport listening on http://localhost:${PORT}${mcpEndpoint}`);
resolve();
});
});
setupGracefulShutdown();
return serverInstance;
}
}
/**
* Main entry point - this will run when executed directly
* Determines whether to run in CLI or server mode based on command-line arguments
*/
async function main() {
const mainLogger = logger_util_js_1.Logger.forContext('index.ts', 'main');
// Load configuration
config_util_js_1.config.load();
// CLI mode - if any arguments are provided
if (process.argv.length > 2) {
mainLogger.info('Starting in CLI mode');
await (0, index_js_1.runCli)(process.argv.slice(2));
mainLogger.info('CLI execution completed');
return;
}
// Server mode - determine transport
const transportMode = (process.env.TRANSPORT_MODE || 'stdio').toLowerCase();
let mode;
if (transportMode === 'stdio') {
mode = 'stdio';
}
else if (transportMode === 'http') {
mode = 'http';
}
else {
mainLogger.warn(`Unknown TRANSPORT_MODE "${transportMode}", defaulting to stdio`);
mode = 'stdio';
}
mainLogger.info(`Starting server with ${mode.toUpperCase()} transport`);
await startServer(mode);
mainLogger.info('Server is now running');
}
/**
* Set up graceful shutdown handlers for the server
*/
function setupGracefulShutdown() {
const shutdownLogger = logger_util_js_1.Logger.forContext('index.ts', 'shutdown');
const shutdown = async () => {
try {
shutdownLogger.info('Shutting down gracefully...');
if (transportInstance &&
'close' in transportInstance &&
typeof transportInstance.close === 'function') {
await transportInstance.close();
}
if (serverInstance && typeof serverInstance.close === 'function') {
await serverInstance.close();
}
process.exit(0);
}
catch (err) {
shutdownLogger.error('Error during shutdown', err);
process.exit(1);
}
};
['SIGINT', 'SIGTERM'].forEach((signal) => {
process.on(signal, shutdown);
});
}
// If this file is being executed directly (not imported), run the main function
if (require.main === module) {
main().catch((err) => {
indexLogger.error('Unhandled error in main process', err);
process.exit(1);
});
}