Skip to main content
Glama

MCP Atlassian Server

by phuc-nt
atlassian-api-base.ts9.66 kB
import { Version3Client } from "jira.js"; import { Logger } from "./logger.js"; import { ApiError, ApiErrorType } from "./error-handler.js"; import fetch from "cross-fetch"; export interface AtlassianConfig { baseUrl: string; apiToken: string; email: string; } // Initialize logger export const logger = Logger.getLogger("AtlassianAPI"); // Cache for Atlassian clients to reuse export const clientCache = new Map<string, Version3Client>(); /** * Create basic headers for API request * @param email User email * @param apiToken User API token * @returns Object containing basic headers */ export const createBasicHeaders = (email: string, apiToken: string) => { // Remove whitespace and newlines from API token const cleanedToken = apiToken.replace(/\s+/g, ""); // Always use Basic Authentication as per API docs const auth = Buffer.from(`${email}:${cleanedToken}`).toString("base64"); // Log headers for debugging logger.debug( "Creating headers with User-Agent:", "MCP-Atlassian-Server/1.0.0" ); return { Authorization: `Basic ${auth}`, "Content-Type": "application/json", Accept: "application/json", // Add User-Agent to help Cloudfront identify the request "User-Agent": "MCP-Atlassian-Server/1.0.0", }; }; // Helper: Create or get Jira client from cache export function getJiraClient(config: AtlassianConfig): Version3Client { const cacheKey = `jira:${config.baseUrl}:${config.email}`; if (clientCache.has(cacheKey)) { return clientCache.get(cacheKey) as Version3Client; } logger.debug(`Creating new Jira client for ${config.baseUrl}`); // Normalize baseUrl let baseUrl = config.baseUrl; if (baseUrl.startsWith("http://")) { baseUrl = baseUrl.replace("http://", "https://"); } else if (!baseUrl.startsWith("https://")) { baseUrl = `https://${baseUrl}`; } if (!baseUrl.includes(".atlassian.net")) { baseUrl = `${baseUrl}.atlassian.net`; } if (baseUrl.match(/\.atlassian\.net\.atlassian\.net/)) { baseUrl = baseUrl.replace(".atlassian.net.atlassian.net", ".atlassian.net"); } const client = new Version3Client({ host: baseUrl, authentication: { basic: { email: config.email, apiToken: config.apiToken, }, }, baseRequestConfig: { headers: { "User-Agent": "MCP-Atlassian-Server/1.0.0", }, }, }); clientCache.set(cacheKey, client); return client; } // Helper: Normalize baseUrl for Atlassian API export function normalizeAtlassianBaseUrl(baseUrl: string): string { let normalizedUrl = baseUrl; if (normalizedUrl.startsWith("http://")) { normalizedUrl = normalizedUrl.replace("http://", "https://"); } else if (!normalizedUrl.startsWith("https://")) { normalizedUrl = `https://${normalizedUrl}`; } if (!normalizedUrl.includes(".atlassian.net")) { normalizedUrl = `${normalizedUrl}.atlassian.net`; } if (normalizedUrl.match(/\.atlassian\.net\.atlassian\.net/)) { normalizedUrl = normalizedUrl.replace( ".atlassian.net.atlassian.net", ".atlassian.net" ); } return normalizedUrl; } // Helper: Call Jira API using jira.js (throw by default) export async function callJiraApi<T>( config: AtlassianConfig, endpoint: string, method: "GET" | "POST" | "PUT" | "DELETE" = "GET", data: any = null, params: Record<string, any> = {} ): Promise<T> { try { const client = getJiraClient(config); logger.debug(`Calling Jira API with jira.js: ${method} ${endpoint}`); throw new ApiError( ApiErrorType.UNKNOWN_ERROR, "This API call method is not implemented with jira.js. Please use specific methods.", 501 ); } catch (error: any) { logger.error(`Jira API error with jira.js:`, error); if (error instanceof ApiError) { throw error; } const statusCode = error.response?.status || 500; const errorMessage = error.message || "Unknown error"; throw new ApiError( ApiErrorType.SERVER_ERROR, `Jira API error: ${errorMessage}`, statusCode, error ); } } // Helper: Call Confluence API using fetch export async function callConfluenceApi<T>( config: AtlassianConfig, endpoint: string, method: "GET" | "POST" | "PUT" | "DELETE" = "GET", data: any = null, params: Record<string, any> = {} ): Promise<T> { try { const headers = createBasicHeaders(config.email, config.apiToken); let baseUrl = config.baseUrl; if (baseUrl.startsWith("http://")) { baseUrl = baseUrl.replace("http://", "https://"); } else if (!baseUrl.startsWith("https://")) { baseUrl = `https://${baseUrl}`; } if (!baseUrl.includes(".atlassian.net")) { baseUrl = `${baseUrl}.atlassian.net`; } if (baseUrl.match(/\.atlassian\.net\.atlassian\.net/)) { baseUrl = baseUrl.replace( ".atlassian.net.atlassian.net", ".atlassian.net" ); } let url = `${baseUrl}/wiki${endpoint}`; if (params && Object.keys(params).length > 0) { const queryParams = new URLSearchParams(); Object.entries(params).forEach(([key, value]) => { queryParams.append(key, String(value)); }); url += `?${queryParams.toString()}`; } logger.debug(`Calling Confluence API: ${method} ${url}`); logger.debug(`With Auth: ${config.email}:*****`); logger.debug(`Token length: ${config.apiToken?.length || 0} characters`); logger.debug( "Full request headers:", JSON.stringify( headers, (key, value) => (key === "Authorization" ? "Basic ***" : value), 2 ) ); const curlCmd = `curl -X ${method} -H "Content-Type: application/json" -H "Accept: application/json" -H "User-Agent: MCP-Atlassian-Server/1.0.0" -u "${ config.email }:${config.apiToken.substring(0, 5)}..." "${url}"$${ data && (method === "POST" || method === "PUT") ? ` -d '${JSON.stringify(data)}'` : "" }`; logger.info(`Debug with curl: ${curlCmd}`); const fetchOptions: RequestInit = { method, headers, credentials: "omit", }; if (data && (method === "POST" || method === "PUT")) { fetchOptions.body = JSON.stringify(data); } logger.debug("Fetch options:", { ...fetchOptions, headers: { ...headers, Authorization: "Basic ***" }, }); const response = await fetch(url, fetchOptions); if (!response.ok) { const statusCode = response.status; const responseText = await response.text(); logger.error(`Confluence API error (${statusCode}):`, responseText); throw new ApiError( ApiErrorType.SERVER_ERROR, `Confluence API error: ${responseText}`, statusCode, new Error(responseText) ); } if (method === 'DELETE') { const text = await response.text(); if (!text) return true as any; try { return JSON.parse(text) as T; } catch { return true as any; } } const responseData = await response.json(); return responseData as T; } catch (error: any) { if (error instanceof ApiError) { throw error; } logger.error("Unhandled error in Confluence API call:", error); throw new ApiError( ApiErrorType.UNKNOWN_ERROR, `Unknown error: ${ error instanceof Error ? error.message : String(error) }`, 500 ); } } // Helper: Convert Atlassian Document Format to simple Markdown export function adfToMarkdown(content: any): string { if (!content || !content.content) return ""; let markdown = ""; const processNode = (node: any): string => { if (!node) return ""; switch (node.type) { case "paragraph": return node.content ? node.content.map(processNode).join("") + "\n\n" : "\n\n"; case "text": let text = node.text || ""; if (node.marks) { node.marks.forEach((mark: any) => { switch (mark.type) { case "strong": text = `**${text}**`; break; case "em": text = `*${text}*`; break; case "code": text = `\`${text}\``; break; case "link": text = `[${text}](${mark.attrs.href})`; break; } }); } return text; case "heading": const level = node.attrs.level; const headingContent = node.content ? node.content.map(processNode).join("") : ""; return "#".repeat(level) + " " + headingContent + "\n\n"; case "bulletList": return node.content ? node.content.map(processNode).join("") : ""; case "listItem": return ( "- " + (node.content ? node.content.map(processNode).join("") : "") + "\n" ); case "orderedList": return node.content ? node.content .map((item: any, index: number) => { return `${index + 1}. ${processNode(item)}`; }) .join("") : ""; case "codeBlock": const code = node.content ? node.content.map(processNode).join("") : ""; const language = node.attrs && node.attrs.language ? node.attrs.language : ""; return ` ${language}\n${code}\n `; default: return node.content ? node.content.map(processNode).join("") : ""; } }; content.content.forEach((node: any) => { markdown += processNode(node); }); return markdown; }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/phuc-nt/mcp-atlassian-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server