Skip to main content
Glama

Jira MCP Server

createIssue.ts15.7 kB
import axios from "axios"; import { JiraCreateIssueRequestSchema } from "../validators/index.js"; import { createAuthHeader, validateCredentials } from "../utils/auth.js"; import { toADF } from "../utils/adfUtils.js"; /** * Description object for the Jira create issue tool * @typedef {Object} CreateIssueToolDescription * @property {string} name - The name of the tool * @property {string} description - Description of the tool's functionality * @property {Object} inputSchema - Schema defining the expected input parameters */ export const createIssueToolDescription = { name: "jira_create_issue", description: "Creates a new issue in a Jira project with specified details", inputSchema: { type: "object", properties: { jiraHost: { type: "string", description: "The Jira host URL (e.g., 'your-domain.atlassian.net')", default: process.env.JIRA_HOST || "", }, email: { type: "string", description: "Email address associated with the Jira account", default: process.env.JIRA_EMAIL || "", }, apiToken: { type: "string", description: "API token for Jira authentication", default: process.env.JIRA_API_TOKEN || "", }, projectKey: { type: "string", description: "The Jira project key (e.g., 'PROJECT')", }, summary: { type: "string", description: "The title/summary of the issue", }, description: { type: "object", description: "ADF (Atlassian Document Format) object, see https://developer.atlassian.com/cloud/jira/platform/apis/document/structure/", }, issueType: { type: "string", description: "Type of issue (e.g., 'Task', 'Bug', 'Story')", default: "Task", }, assigneeName: { type: "string", description: "The display name of the person to assign the issue to", }, reporterName: { type: "string", description: "The display name of the person reporting the issue", }, sprintId: { type: "string", description: "ID of the sprint to add the issue to", }, }, required: ["projectKey", "summary", "description"], }, }; /** * Creates a new issue in a Jira project * * @async * @param {Object} args - The arguments for creating the issue * @param {string} args.jiraHost - The Jira host URL * @param {string} args.email - Email for authentication * @param {string} args.apiToken - API token for authentication * @param {string} args.projectKey - The project key * @param {string} args.summary - Issue title/summary * @param {object} args.description - Issue description (ADF JSON object, Atlassian Document Format) * @param {string} [args.issueType] - Type of issue * @param {string} [args.assigneeName] - Name of the assignee * @param {string} [args.reporterName] - Name of the reporter * @param {string} [args.sprintId] - ID of the sprint * @returns {Promise<Object>} A formatted response with the created issue details * @throws {Error} If the required credentials are missing or the request fails */ export async function createIssue(args: any) { const validatedArgs = await JiraCreateIssueRequestSchema.validate(args); const jiraHost = validatedArgs.jiraHost || process.env.JIRA_HOST; const email = validatedArgs.email || process.env.JIRA_EMAIL; const apiToken = validatedArgs.apiToken || process.env.JIRA_API_TOKEN; const projectKey = validatedArgs.projectKey; const summary = validatedArgs.summary; const description = validatedArgs.description; const adfDescription = toADF(description); const issueType = validatedArgs.issueType || "Task"; const assigneeName = validatedArgs.assigneeName; const reporterName = validatedArgs.reporterName; const sprintId = validatedArgs.sprintId; if (!jiraHost || !email || !apiToken) { throw new Error('Missing required authentication credentials. Please provide jiraHost, email, and apiToken.'); } validateCredentials(jiraHost, email, apiToken); const authHeader = createAuthHeader(email, apiToken); try { // Create the issue payload const issuePayload: any = { fields: { project: { key: projectKey }, summary: summary, description: adfDescription, issuetype: { name: issueType } } }; // If assignee name is provided, get their accountId if (assigneeName) { try { const userResponse = await axios.get(`https://${jiraHost}/rest/api/3/user/search`, { params: { query: assigneeName }, headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); if (userResponse.data && userResponse.data.length > 0) { const assigneeUser = userResponse.data.find((user: any) => user.displayName.toLowerCase() === assigneeName.toLowerCase() ) || userResponse.data[0]; issuePayload.fields.assignee = { id: assigneeUser.accountId }; } } catch (error) { console.warn("Could not find assignee:", assigneeName); } } // If reporter name is provided, get their accountId if (reporterName) { try { const userResponse = await axios.get(`https://${jiraHost}/rest/api/3/user/search`, { params: { query: reporterName }, headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); if (userResponse.data && userResponse.data.length > 0) { const reporterUser = userResponse.data.find((user: any) => user.displayName.toLowerCase() === reporterName.toLowerCase() ) || userResponse.data[0]; issuePayload.fields.reporter = { id: reporterUser.accountId }; } } catch (error) { console.warn("Could not find reporter:", reporterName); } } // Create the issue const response = await axios.post(`https://${jiraHost}/rest/api/3/issue`, issuePayload, { headers: { 'Authorization': authHeader, 'Accept': 'application/json', 'Content-Type': 'application/json' }, }); const createdIssue = response.data; // Add the issue to a sprint if sprintId is provided if (sprintId && createdIssue.id) { try { await axios.post(`https://${jiraHost}/rest/agile/1.0/sprint/${sprintId}/issue`, { issues: [createdIssue.id] }, { headers: { 'Authorization': authHeader, 'Accept': 'application/json', 'Content-Type': 'application/json' }, }); } catch (error) { console.warn("Could not add issue to sprint:", sprintId); } } // Fetch the created issue to get full details const issueResponse = await axios.get(`https://${jiraHost}/rest/api/3/issue/${createdIssue.key}`, { headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); const issue = issueResponse.data; const formattedDate = new Date().toLocaleString(); let formattedResponse = `# Issue Created Successfully\n\n`; formattedResponse += `## Issue Details\n\n`; formattedResponse += `| Field | Value |\n`; formattedResponse += `|-------|-------|\n`; formattedResponse += `| Key | [${issue.key}](https://${jiraHost}/browse/${issue.key}) |\n`; formattedResponse += `| Summary | ${issue.fields.summary} |\n`; formattedResponse += `| Type | ${issue.fields.issuetype?.name || issueType} |\n`; formattedResponse += `| Project | ${projectKey} |\n`; formattedResponse += `| Created | ${formattedDate} |\n`; if (issue.fields.assignee) { formattedResponse += `| Assignee | ${issue.fields.assignee.displayName} |\n`; } else if (assigneeName) { formattedResponse += `| Assignee | ${assigneeName} (assignee may not have been found) |\n`; } if (issue.fields.reporter) { formattedResponse += `| Reporter | ${issue.fields.reporter.displayName} |\n`; } else if (reporterName) { formattedResponse += `| Reporter | ${reporterName} (reporter may not have been found) |\n`; } if (sprintId) { formattedResponse += `| Sprint | ${sprintId} |\n`; } let descriptionPreview = ''; if (adfDescription && typeof adfDescription === 'object' && adfDescription.type === 'doc' && Array.isArray(adfDescription.content)) { descriptionPreview = adfDescription.content.map((block: any) => { if (block.type === 'paragraph' && Array.isArray(block.content)) { return block.content.map((c: any) => c.text).join(''); } return ''; }).join('\n'); } else { descriptionPreview = '[ADF description provided]'; } formattedResponse += `\n## Description\n\n${descriptionPreview}\n\n`; formattedResponse += `\n**Issue link:** [${issue.key}](https://${jiraHost}/browse/${issue.key})\n`; return { content: [{ type: "text", text: formattedResponse }], isError: false, }; } catch (error: any) { // Extract detailed error information let errorTitle = "Error Creating Jira Issue"; let errorMsg = "An unexpected error occurred while creating the issue."; let errorDetails = ""; let errorSolution = ""; // Handle validation errors (from Yup schema) if (error.name === "ValidationError") { errorTitle = "Validation Error"; errorMsg = "The provided data does not meet the requirements for creating a Jira issue."; errorDetails = error.message; errorSolution = "Please check the field requirements and provide all necessary information."; } // Handle authentication/credential errors else if (error.message && error.message.includes("credentials")) { errorTitle = "Authentication Error"; errorMsg = "Failed to authenticate with Jira."; errorDetails = error.message; errorSolution = "Please verify your Jira host, email, and API token."; } // Handle user not found errors (assignee/reporter) else if (error.response && error.response.status === 400 && (error.response.data?.errors?.assignee || error.response.data?.errors?.reporter)) { errorTitle = "User Not Found Error"; errorMsg = "One or more specified users could not be found in Jira."; errorDetails = JSON.stringify(error.response.data.errors, null, 2); errorSolution = "Check that the assignee and reporter names match existing users in your Jira instance."; } // Handle project not found errors else if (error.response && error.response.status === 404 && error.response.data?.errorMessages?.some((msg: string) => msg.includes("project"))) { errorTitle = "Project Not Found Error"; errorMsg = `Project with key '${projectKey}' could not be found.`; errorDetails = error.response.data?.errorMessages?.join('\n') || "Project not found or you don't have permission to access it."; errorSolution = "Verify the project key and ensure you have access to the project."; } // Handle permission errors else if (error.response && error.response.status === 403) { errorTitle = "Permission Error"; errorMsg = "You don't have permission to create issues in this project."; errorDetails = error.response.data?.errorMessages?.join('\n') || error.message; errorSolution = "Contact your Jira administrator to request the necessary permissions."; } // Handle rate limit errors else if (error.response && error.response.status === 429) { errorTitle = "Rate Limit Exceeded"; errorMsg = "Too many requests sent to Jira API."; errorDetails = error.response.data?.errorMessages?.join('\n') || error.message; errorSolution = "Please wait before trying again."; } // Handle sprint errors else if (error.message && error.message.includes("sprint")) { errorTitle = "Sprint Error"; errorMsg = "Failed to add issue to the specified sprint."; errorDetails = error.message; errorSolution = "Verify the sprint ID and ensure it is active and associated with the project."; } // Handle any other API response errors else if (error.response) { errorTitle = `API Error (${error.response.status})`; errorMsg = `The Jira API returned an error with status code ${error.response.status}.`; // Try to extract and format the error details try { if (typeof error.response.data === 'object') { errorDetails = JSON.stringify(error.response.data, null, 2); } else { errorDetails = error.response.data || error.message; } } catch { errorDetails = error.message || "No additional details available."; } errorSolution = "Check the error details and adjust your request accordingly."; } // Handle network errors else if (error.request) { errorTitle = "Network Error"; errorMsg = "Failed to connect to the Jira API."; errorDetails = error.message || "No response received from the server."; errorSolution = "Check your internet connection and verify the Jira host URL."; } // Fallback for any other errors else if (error.message) { errorDetails = error.message; } // Format the error response in Markdown const formattedError = `# ${errorTitle}\n\n${errorMsg}\n\n` + (errorDetails ? `## Error Details\n\n\`\`\`\n${errorDetails}\n\`\`\`\n\n` : "") + (errorSolution ? `## Solution\n\n${errorSolution}` : ""); return { content: [{ type: "text", text: formattedError }], isError: true, }; } }

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/samuelrizzo/jira-mcp-server'

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