@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
JavaScript
/**
* @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';
}