UNPKG

@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

106 lines (105 loc) 4.08 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.formatSearchResults = formatSearchResults; exports.processCqlQuery = processCqlQuery; const formatter_util_js_1 = require("../utils/formatter.util.js"); /** * Format search results for display * @param searchData - Raw search results from the API * @returns Formatted string with search results in markdown format */ function formatSearchResults(searchData) { if (searchData.length === 0) { return 'No Confluence content found matching your query.'; } const lines = [(0, formatter_util_js_1.formatHeading)('Confluence Search Results', 1), '']; // Use the numbered list formatter for consistent formatting const formattedList = (0, formatter_util_js_1.formatNumberedList)(searchData, (result) => { const itemLines = []; const content = result.content; // Basic information itemLines.push((0, formatter_util_js_1.formatHeading)(content.title, 2)); // Create an object with all the properties to display const properties = { ID: content.id, Type: content.type, Status: content.status, Space: result.resultGlobalContainer?.title, URL: result.url ? { url: result.url, title: 'View in Confluence', } : undefined, Excerpt: result.excerpt ? result.excerpt.replace(/\n/g, ' ') : undefined, }; // Format as a bullet list with proper formatting for each value type itemLines.push((0, formatter_util_js_1.formatBulletList)(properties, (key) => key)); return itemLines.join('\n'); }); lines.push(formattedList); return lines.join('\n'); } /** * Process and prepare a CQL query for sending to the API. * Handles escaping special characters and proper formatting. * @param cql - Raw CQL query string * @returns Processed CQL query ready for the API */ function processCqlQuery(cql) { if (!cql || cql.trim() === '') { return 'type IN (page, blogpost) ORDER BY lastmodified DESC'; } // List of CQL reserved keywords that might be used as space keys const reservedKeywords = [ 'AND', 'OR', 'NOT', 'IN', 'LIKE', 'IS', 'NULL', 'EMPTY', 'ORDER', 'BY', 'ASC', 'DESC', ]; // Process the CQL query in multiple steps let processedCql = cql; // Step 1: Check for space=KEYWORD pattern and quote the keyword if it's reserved processedCql = processedCql.replace(/space\s*=\s*(\w+)(?!\s*")/g, (match, spaceKey) => { if (reservedKeywords.includes(spaceKey.toUpperCase())) { return `space="${spaceKey}"`; } return match; }); // Step 2: Check for other property=KEYWORD patterns with reserved keywords processedCql = processedCql.replace(/(\w+)\s*=\s*(\w+)(?!\s*")/g, (match, property, value) => { if (reservedKeywords.includes(value.toUpperCase())) { return `${property}="${value}"`; } return match; }); // Step 3: Handle space-separated search terms by converting to AND syntax if needed if (!processedCql.includes(' AND ') && !processedCql.includes(' OR ') && !processedCql.includes(' NOT ') && processedCql.includes(' ')) { // Simple space-separated query without logical operators // Split by spaces and reconstruct with AND const terms = processedCql.split(' ').filter((term) => term.trim()); if (terms.length > 1) { // Only apply the conversion if there are multiple terms and not already in a complex query const hasComplexSyntax = /[=~()]/.test(processedCql); if (!hasComplexSyntax) { processedCql = terms .map((term) => `text~"${term}"`) .join(' AND '); } } } return processedCql; }