@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
JavaScript
;
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);
}
}