Skip to main content
Glama

MCP Atlassian Server

by phuc-nt
projects.ts8.97 kB
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'; import { Config, Resources } from '../../utils/mcp-helpers.js'; import { AtlassianConfig } from '../../utils/atlassian-api-base.js'; import { ApiError, ApiErrorType } from '../../utils/error-handler.js'; import { Logger } from '../../utils/logger.js'; import fetch from 'cross-fetch'; import { projectsListSchema, projectSchema } from '../../schemas/jira.js'; import { getProjects as getProjectsApi, getProject as getProjectApi } from '../../utils/jira-resource-api.js'; const logger = Logger.getLogger('JiraResource:Projects'); /** * Create basic headers for Atlassian API with Basic Authentication */ function createBasicHeaders(email: string, apiToken: string) { const auth = Buffer.from(`${email}:${apiToken}`).toString('base64'); return { 'Authorization': `Basic ${auth}`, 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'MCP-Atlassian-Server/1.0.0' }; } /** * Helper function to get the list of projects */ async function getProjects(config: AtlassianConfig): Promise<any[]> { return await getProjectsApi(config); } /** * Helper function to get project details */ async function getProject(config: AtlassianConfig, projectKey: string): Promise<any> { return await getProjectApi(config, projectKey); } /** * Register resources related to Jira projects * @param server MCP Server instance */ export function registerProjectResources(server: McpServer) { // Resource: List all projects server.resource( 'jira-projects-list', new ResourceTemplate('jira://projects', { list: async (_extra) => { return { resources: [ { uri: 'jira://projects', name: 'Jira Projects', description: 'List and search all Jira projects', mimeType: 'application/json' } ] }; } }), async (uri, _params, _extra) => { logger.info('Getting list of Jira projects'); try { // Get config from environment const config = Config.getAtlassianConfigFromEnv(); // Get the list of projects from Jira API const projects = await getProjects(config); // Convert response to a more friendly format const formattedProjects = projects.map((project: any) => ({ id: project.id, key: project.key, name: project.name, projectType: project.projectTypeKey, url: `${config.baseUrl}/browse/${project.key}`, lead: project.lead?.displayName || 'Unknown' })); const uriString = typeof uri === 'string' ? uri : uri.href; // Return standardized resource with metadata and schema return Resources.createStandardResource( uriString, formattedProjects, 'projects', projectsListSchema, formattedProjects.length, formattedProjects.length, 0, `${config.baseUrl}/jira/projects` ); } catch (error) { logger.error('Error getting Jira projects:', error); throw error; } } ); // Resource: Project details server.resource( 'jira-project-details', new ResourceTemplate('jira://projects/{projectKey}', { list: async (_extra) => ({ resources: [ { uri: 'jira://projects/{projectKey}', name: 'Jira Project Details', description: 'Get details for a specific Jira project by key. Replace {projectKey} with the project key.', mimeType: 'application/json' } ] }) }), async (uri, params, _extra) => { try { // Get config from environment const config = Config.getAtlassianConfigFromEnv(); // Get projectKey from URI pattern let normalizedProjectKey = ''; if (params && 'projectKey' in params) { normalizedProjectKey = Array.isArray(params.projectKey) ? params.projectKey[0] : params.projectKey; } if (!normalizedProjectKey) { throw new ApiError( ApiErrorType.VALIDATION_ERROR, 'Project key not provided', 400, new Error('Missing project key parameter') ); } logger.info(`Getting details for Jira project: ${normalizedProjectKey}`); // Get project info from Jira API const project = await getProject(config, normalizedProjectKey); // Convert response to a more friendly format const formattedProject = { id: project.id, key: project.key, name: project.name, description: project.description || 'No description', lead: project.lead?.displayName || 'Unknown', url: `${config.baseUrl}/browse/${project.key}`, projectCategory: project.projectCategory?.name || 'Uncategorized', projectType: project.projectTypeKey }; const uriString = typeof uri === 'string' ? uri : uri.href; // Chuẩn hóa metadata/schema return Resources.createStandardResource( uriString, [formattedProject], 'project', projectSchema, 1, 1, 0, `${config.baseUrl}/browse/${project.key}` ); } catch (error) { logger.error(`Error getting Jira project details:`, error); throw error; } } ); // Resource: List roles of a project server.resource( 'jira-project-roles', new ResourceTemplate('jira://projects/{projectKey}/roles', { list: async (_extra) => ({ resources: [ { uri: 'jira://projects/{projectKey}/roles', name: 'Jira Project Roles', description: 'List roles for a Jira project. Replace {projectKey} with the project key.', mimeType: 'application/json' } ] }) }), async (uri, params, _extra) => { try { // Get config from environment const config = Config.getAtlassianConfigFromEnv(); let normalizedProjectKey = ''; if (params && 'projectKey' in params) { normalizedProjectKey = Array.isArray(params.projectKey) ? params.projectKey[0] : params.projectKey; } if (!normalizedProjectKey) { throw new Error('Missing projectKey'); } logger.info(`Getting roles for Jira project: ${normalizedProjectKey}`); const auth = Buffer.from(`${config.email}:${config.apiToken}`).toString('base64'); const headers = { 'Authorization': `Basic ${auth}`, 'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'MCP-Atlassian-Server/1.0.0' }; let baseUrl = config.baseUrl; if (!baseUrl.startsWith('https://')) baseUrl = `https://${baseUrl}`; const url = `${baseUrl}/rest/api/3/project/${encodeURIComponent(normalizedProjectKey)}/role`; logger.debug(`Calling Jira API: ${url}`); const response = await fetch(url, { method: 'GET', headers, credentials: 'omit' }); if (!response.ok) { const statusCode = response.status; const responseText = await response.text(); logger.error(`Jira API error (${statusCode}):`, responseText); throw new Error(`Jira API error: ${responseText}`); } const data = await response.json(); // data is an object: key is roleName, value is URL containing roleId const roles = Object.entries(data).map(([roleName, url]) => { const urlStr = String(url); const match = urlStr.match(/\/role\/(\d+)$/); return { roleName, roleId: match ? match[1] : '', url: urlStr }; }); const uriString = typeof uri === 'string' ? uri : uri.href; // Chuẩn hóa metadata/schema (dùng array of role object, schema tự tạo inline) const rolesListSchema = { type: "array", items: { type: "object", properties: { roleName: { type: "string" }, roleId: { type: "string" }, url: { type: "string" } }, required: ["roleName", "roleId", "url"] } }; return Resources.createStandardResource( uriString, roles, 'roles', rolesListSchema, roles.length, roles.length, 0, `${config.baseUrl}/browse/${normalizedProjectKey}/project-roles` ); } catch (error) { logger.error(`Error getting roles for Jira project:`, error); throw error; } } ); logger.info('Jira project resources registered successfully'); }

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