UNPKG

@pavi_thran_7/jira-mcp-server

Version:

MCP server for Jira integration with ticket creation and time logging

337 lines (297 loc) 8.93 kB
/** * @fileoverview Ticket management tools for Jira MCP server */ /** * @typedef {import('../types/jira.js').CreateTicketParams} CreateTicketParams * @typedef {import('../types/jira.js').McpToolResult} McpToolResult * @typedef {import('../jira-client.js').JiraClient} JiraClient */ /** * Create a new Jira ticket * @param {JiraClient} jiraClient - Jira client instance * @param {CreateTicketParams} params - Ticket creation parameters * @returns {Promise<McpToolResult>} Creation result */ export async function createTicket(jiraClient, params) { try { // Validate required parameters if (!params.projectKey || !params.summary || !params.issueType) { return { content: [ { type: 'text', text: 'Error: Missing required parameters. Please provide projectKey, summary, and issueType.', }, ], isError: true, }; } // Get project details to validate project key const projectsResult = await jiraClient.getProjects(); if (!projectsResult.success) { return { content: [ { type: 'text', text: `Error getting projects: ${projectsResult.error}`, }, ], isError: true, }; } const project = projectsResult.data.values.find(p => p.key === params.projectKey); if (!project) { const availableProjects = projectsResult.data.values.map(p => p.key).join(', '); return { content: [ { type: 'text', text: `Error: Project '${params.projectKey}' not found. Available projects: ${availableProjects}`, }, ], isError: true, }; } // Get issue types for the project const issueTypesResult = await jiraClient.getIssueTypes(project.id); if (!issueTypesResult.success) { return { content: [ { type: 'text', text: `Error getting issue types: ${issueTypesResult.error}`, }, ], isError: true, }; } const issueType = issueTypesResult.data.find(it => it.name.toLowerCase() === params.issueType.toLowerCase() ); if (!issueType) { const availableTypes = issueTypesResult.data.map(it => it.name).join(', '); return { content: [ { type: 'text', text: `Error: Issue type '${params.issueType}' not found for project '${params.projectKey}'. Available types: ${availableTypes}`, }, ], isError: true, }; } // Build issue data const issueData = { fields: { project: { key: params.projectKey, }, summary: params.summary, issuetype: { id: issueType.id, }, }, }; // Add description if provided if (params.description) { issueData.fields.description = { type: 'doc', version: 1, content: [ { type: 'paragraph', content: [ { type: 'text', text: params.description, }, ], }, ], }; } // Add priority if provided if (params.priority) { const prioritiesResult = await jiraClient.getPriorities(); if (prioritiesResult.success) { const priority = prioritiesResult.data.find(p => p.name.toLowerCase() === params.priority.toLowerCase() ); if (priority) { issueData.fields.priority = { id: priority.id, }; } } } // Add assignee if provided if (params.assignee) { // Try to find user by email or account ID const usersResult = await jiraClient.searchUsers(params.assignee); if (usersResult.success && usersResult.data.length > 0) { issueData.fields.assignee = { accountId: usersResult.data[0].accountId, }; } else { // If not found, try as account ID directly issueData.fields.assignee = { accountId: params.assignee, }; } } // Add labels if provided if (params.labels && params.labels.length > 0) { issueData.fields.labels = params.labels; } // Create the issue const createResult = await jiraClient.createIssue(issueData); if (!createResult.success) { return { content: [ { type: 'text', text: `Error creating ticket: ${createResult.error}`, }, ], isError: true, }; } const createdIssue = createResult.data; const issueUrl = `${jiraClient.baseUrl}/browse/${createdIssue.key}`; // Try to add to board if boardId is provided let boardMessage = ''; if (params.boardId) { const boardResult = await jiraClient.addIssueToBoard(params.boardId, createdIssue.key); if (boardResult.success) { boardMessage = `\n- Added to board: ${params.boardId}`; } else { boardMessage = `\n- Note: Could not add to board ${params.boardId}: ${boardResult.error}`; } } return { content: [ { type: 'text', text: `✅ Successfully created ticket! **Ticket Details:** - Key: ${createdIssue.key} - Summary: ${params.summary} - Issue Type: ${params.issueType} - Project: ${params.projectKey} - URL: ${issueUrl}${boardMessage} The ticket has been created and is ready for work.`, }, ], isError: false, }; } catch (error) { return { content: [ { type: 'text', text: `Error creating ticket: ${error.message}`, }, ], isError: true, }; } } /** * Get ticket details * @param {JiraClient} jiraClient - Jira client instance * @param {Object} params - Parameters * @param {string} params.issueKey - Issue key * @returns {Promise<McpToolResult>} Ticket details */ export async function getTicket(jiraClient, params) { try { if (!params.issueKey) { return { content: [ { type: 'text', text: 'Error: Missing required parameter issueKey.', }, ], isError: true, }; } const issueResult = await jiraClient.getIssue(params.issueKey); if (!issueResult.success) { return { content: [ { type: 'text', text: `Error getting ticket: ${issueResult.error}`, }, ], isError: true, }; } const issue = issueResult.data; const issueUrl = `${jiraClient.baseUrl}/browse/${issue.key}`; // Format the response const assigneeInfo = issue.fields.assignee ? `${issue.fields.assignee.displayName} (${issue.fields.assignee.emailAddress})` : 'Unassigned'; const labels = issue.fields.labels && issue.fields.labels.length > 0 ? issue.fields.labels.join(', ') : 'None'; const description = issue.fields.description ? extractTextFromDescription(issue.fields.description) : 'No description'; return { content: [ { type: 'text', text: `📋 **Ticket Details: ${issue.key}** **Summary:** ${issue.fields.summary} **Status:** ${issue.fields.status.name} **Issue Type:** ${issue.fields.issuetype.name} **Priority:** ${issue.fields.priority ? issue.fields.priority.name : 'Not set'} **Assignee:** ${assigneeInfo} **Reporter:** ${issue.fields.reporter.displayName} **Labels:** ${labels} **Created:** ${new Date(issue.fields.created).toLocaleString()} **Updated:** ${new Date(issue.fields.updated).toLocaleString()} **Description:** ${description} **URL:** ${issueUrl}`, }, ], isError: false, }; } catch (error) { return { content: [ { type: 'text', text: `Error getting ticket: ${error.message}`, }, ], isError: true, }; } } /** * Extract text content from Jira description object * @param {Object} description - Jira description object * @returns {string} Plain text description */ function extractTextFromDescription(description) { if (!description || !description.content) { return 'No description'; } let text = ''; for (const block of description.content) { if (block.type === 'paragraph' && block.content) { for (const content of block.content) { if (content.type === 'text') { text += content.text; } } text += '\n'; } } return text.trim() || 'No description'; }