UNPKG

@aashari/mcp-server-atlassian-jira

Version:

Node.js/TypeScript MCP server for Atlassian Jira. Equips AI systems (LLMs) with tools to list/get projects, search/get issues (using JQL/ID), and view dev info (commits, PRs). Connects AI capabilities directly into Jira project management and issue tracki

135 lines (134 loc) 6.36 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getAtlassianCredentials = getAtlassianCredentials; exports.fetchAtlassian = fetchAtlassian; const logger_util_js_1 = require("./logger.util.js"); const config_util_js_1 = require("./config.util.js"); const error_util_js_1 = require("./error.util.js"); // Create a contextualized logger for this file const transportLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts'); // Log transport utility initialization transportLogger.debug('Transport utility initialized'); /** * Get Atlassian credentials from environment variables * @returns AtlassianCredentials object or null if credentials are missing */ function getAtlassianCredentials() { const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'getAtlassianCredentials'); const siteName = config_util_js_1.config.get('ATLASSIAN_SITE_NAME'); const userEmail = config_util_js_1.config.get('ATLASSIAN_USER_EMAIL'); const apiToken = config_util_js_1.config.get('ATLASSIAN_API_TOKEN'); if (!siteName || !userEmail || !apiToken) { methodLogger.warn('Missing Atlassian credentials. Please set ATLASSIAN_SITE_NAME, ATLASSIAN_USER_EMAIL, and ATLASSIAN_API_TOKEN environment variables.'); return null; } methodLogger.debug('Using Atlassian credentials'); return { siteName, userEmail, apiToken, }; } /** * Fetch data from Atlassian API * @param credentials Atlassian API credentials * @param path API endpoint path (without base URL) * @param options Request options * @returns Response data */ async function fetchAtlassian(credentials, path, options = {}) { const methodLogger = logger_util_js_1.Logger.forContext('utils/transport.util.ts', 'fetchAtlassian'); const { siteName, userEmail, apiToken } = credentials; // Ensure path starts with a slash const normalizedPath = path.startsWith('/') ? path : `/${path}`; // Construct the full URL const baseUrl = `https://${siteName}.atlassian.net`; const url = `${baseUrl}${normalizedPath}`; // Set up authentication and headers const headers = { Authorization: `Basic ${Buffer.from(`${userEmail}:${apiToken}`).toString('base64')}`, 'Content-Type': 'application/json', Accept: 'application/json', ...options.headers, }; // Prepare request options const requestOptions = { method: options.method || 'GET', headers, body: options.body ? JSON.stringify(options.body) : undefined, }; methodLogger.debug(`Calling Atlassian API: ${url}`); try { const response = await fetch(url, requestOptions); // Log the raw response status and headers methodLogger.debug(`Raw response received: ${response.status} ${response.statusText}`, { url, status: response.status, statusText: response.statusText, // Just log a simplified representation of headers headers: { contentType: response.headers.get('content-type'), contentLength: response.headers.get('content-length'), }, }); if (!response.ok) { const errorText = await response.text(); methodLogger.error(`API error: ${response.status} ${response.statusText}`, errorText); // Try to parse the error response let errorMessage = `${response.status} ${response.statusText}`; let parsedError = null; try { if (errorText && (errorText.startsWith('{') || errorText.startsWith('['))) { parsedError = JSON.parse(errorText); // Extract specific error details from Atlassian API response formats if (parsedError.errors && Array.isArray(parsedError.errors) && parsedError.errors.length > 0) { // Format: {"errors":[{"status":400,"code":"INVALID_REQUEST_PARAMETER","title":"..."}]} const atlassianError = parsedError.errors[0]; if (atlassianError.title) { errorMessage = atlassianError.title; } } else if (parsedError.message) { // Format: {"message":"Some error message"} errorMessage = parsedError.message; } } } catch (parseError) { methodLogger.debug(`Error parsing error response:`, parseError); // Fall back to the default error message } // Classify HTTP errors based on status code if (response.status === 401 || response.status === 403) { throw (0, error_util_js_1.createAuthInvalidError)('Invalid Atlassian credentials'); } else if (response.status === 404) { throw (0, error_util_js_1.createApiError)(`Resource not found`, 404, errorText); } else { // For other API errors, preserve the original error message from Atlassian API throw (0, error_util_js_1.createApiError)(errorMessage, response.status, errorText); } } // Clone the response to log its content without consuming it const clonedResponse = response.clone(); const responseJson = await clonedResponse.json(); methodLogger.debug(`Response body:`, responseJson); return response.json(); } catch (error) { methodLogger.error(`Request failed`, error); // If it's already an McpError, just rethrow it if (error instanceof error_util_js_1.McpError) { throw error; } // Handle network or parsing errors if (error instanceof TypeError || error instanceof SyntaxError) { throw (0, error_util_js_1.createApiError)(`Network or parsing error: ${error instanceof Error ? error.message : String(error)}`, 500, error); } throw (0, error_util_js_1.createUnexpectedError)(`Unexpected error while calling Atlassian API: ${error instanceof Error ? error.message : String(error)}`, error); } }