Skip to main content
Glama

jira_create_issue

Create a new issue in Jira with detailed project information, including summary, description, issue type, assignee, reporter, and sprint. Streamline project management tasks by integrating Jira functionality via the MCP server.

Instructions

Creates a new issue in a Jira project with specified details

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
apiTokenNoAPI token for Jira authentication
assigneeNameNoThe display name of the person to assign the issue to
descriptionYesIssue description in ADF (Atlassian Document Format). REQUIRED: Must be an object with structure: {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Your description text"}]}]}
emailNoEmail address associated with the Jira account
issueTypeNoType of issue (e.g., 'Task', 'Bug', 'Story')Task
jiraHostNoThe Jira host URL (e.g., 'your-domain.atlassian.net')
projectKeyYesThe Jira project key (e.g., 'PROJECT')
reporterNameNoThe display name of the person reporting the issue
sprintIdNoID of the sprint to add the issue to
summaryYesThe title/summary of the issue

Implementation Reference

  • The core handler function that executes the jira_create_issue tool. It validates inputs, authenticates with Jira, creates the issue via API, handles assignees/reporters/sprints, fetches details, and returns a formatted Markdown response or detailed error.
    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, }; } }
  • Yup schema used for validating the input arguments to the jira_create_issue handler, extending base Jira API schema with issue-specific fields like projectKey, summary, description (ADF), etc.
    export const JiraCreateIssueRequestSchema = JiraApiRequestSchema.shape({ projectKey: yup.string() .required("Project key is required") .matches(/^[A-Z][A-Z0-9_]+$/, "Invalid project key format. Only uppercase letters, numbers, and underscores are allowed") .test( 'not-empty-project', 'Project key cannot be empty', (value) => !!value && value.trim().length > 0 ), summary: yup.string() .required("Issue summary/title is required") .min(3, "Issue title must be at least 3 characters long") .max(255, "Issue title must be at most 255 characters long"), description: yup.object({ type: yup.string() .oneOf(['doc'], 'ADF document type must be "doc". Use: {"type": "doc", ...}') .required('ADF document type is required. Add "type": "doc" to your description object'), version: yup.number() .oneOf([1], 'ADF document version must be 1. Use: {"version": 1, ...}') .required('ADF document version is required. Add "version": 1 to your description object'), content: yup.array() .required('ADF document content is required. Add "content": [...] array to your description object') .min(1, 'ADF document content cannot be empty. Include at least one paragraph in the content array'), }).required('Issue description (in ADF format) is required. Use this format: {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Your text here"}]}]}') .typeError('❌ CRITICAL: Description must be an ADF object, NOT a string! Use: {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Your description text"}]}]}'), issueType: yup.string() .oneOf( ['Task', 'Bug', 'Story', 'Epic'], "Issue type must be one of: Task, Bug, Story, Epic" ) .default("Task"), assigneeName: yup.string() .optional() .min(2, "Assignee name must be at least 2 characters long"), reporterName: yup.string() .optional() .min(2, "Reporter name must be at least 2 characters long"), sprintId: yup.string() .optional() .test( 'valid-sprint-id', 'Sprint ID must be numeric', (value) => !value || /^\d+$/.test(value) ), });
  • Tool registration in the execution handler (handleCallTool), mapping the tool name to its validation schema and handler function.
    jira_create_issue: { schema: JiraCreateIssueRequestSchema, handler: createIssue },
  • Tool registration in the list tools handler (handleListTools), providing the tool name, description, and JSON input schema for MCP tool discovery.
    name: "jira_create_issue", description: "Creates a new issue in a Jira project with specified details", inputSchema: { type: "object", properties: { ...getCommonJiraProperties(), 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: "Issue description in ADF (Atlassian Document Format). REQUIRED: Must be an object with structure: {\"type\": \"doc\", \"version\": 1, \"content\": [{\"type\": \"paragraph\", \"content\": [{\"type\": \"text\", \"text\": \"Your description text\"}]}]}", }, 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"], }, },
  • JSON schema description for the tool, matching the one in listTools, defining input properties and requirements.
    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"], }, };

Other Tools

Related Tools

Latest Blog Posts

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