jira_get_issue
Retrieve specific Jira issue details by key, using the Jira host URL, account email, and API token for authentication. Part of the Jira MCP Server for managing project tasks.
Instructions
Retrieves details of a specific Jira issue by key
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| apiToken | No | API token for Jira authentication | |
| No | Email address associated with the Jira account | ||
| issueKey | Yes | The Jira issue key (e.g., 'PROJECT-123') | |
| jiraHost | No | The Jira host URL (e.g., 'your-domain.atlassian.net') |
Implementation Reference
- src/tools/getIssue.ts:54-192 (handler)The main execution function for the jira_get_issue tool. Fetches issue details from Jira API, formats response with tables for basic info, description, comments, attachments, worklog, and changelog.export async function getIssue(args: any) { const validatedArgs = await JiraIssueRequestSchema.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 issueKey = validatedArgs.issueKey; 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 { const response = await axios.get(`https://${jiraHost}/rest/api/3/issue/${issueKey}`, { params: { expand: 'renderedFields,names,changelog,operations', fields: 'summary,status,assignee,issuetype,priority,created,creator,reporter,description,comment,attachment,worklog,updated,labels,fixVersions,components,duedate' }, headers: { 'Authorization': authHeader, 'Accept': 'application/json', }, }); const issue = response.data; const formattedDate = new Date(issue.fields.created).toLocaleString(); const updatedDate = issue.fields.updated ? new Date(issue.fields.updated).toLocaleString() : 'Not updated'; let formattedResponse = `# Issue: ${issue.key} - ${issue.fields.summary}\n\n`; formattedResponse += `## Basic Information\n\n`; formattedResponse += `| Field | Value |\n`; formattedResponse += `|-------|-------|\n`; formattedResponse += `| Status | ${issue.fields.status?.name || 'Unknown'} |\n`; formattedResponse += `| Type | ${issue.fields.issuetype?.name || 'Unknown'} |\n`; formattedResponse += `| Priority | ${issue.fields.priority?.name || 'Not set'} |\n`; formattedResponse += `| Assignee | ${issue.fields.assignee?.displayName || 'Unassigned'} |\n`; formattedResponse += `| Reporter | ${issue.fields.reporter?.displayName || 'Unknown'} |\n`; formattedResponse += `| Created | ${formattedDate} |\n`; formattedResponse += `| Updated | ${updatedDate} |\n`; if (issue.fields.duedate) { const dueDate = new Date(issue.fields.duedate).toLocaleString(); formattedResponse += `| Due Date | ${dueDate} |\n`; } if (issue.fields.labels && issue.fields.labels.length > 0) { formattedResponse += `| Labels | ${issue.fields.labels.join(', ')} |\n`; } if (issue.fields.components && issue.fields.components.length > 0) { const componentNames = issue.fields.components.map((comp: any) => comp.name).join(', '); formattedResponse += `| Components | ${componentNames} |\n`; } if (issue.fields.fixVersions && issue.fields.fixVersions.length > 0) { const fixVersionNames = issue.fields.fixVersions.map((ver: any) => ver.name).join(', '); formattedResponse += `| Fix Versions | ${fixVersionNames} |\n`; } if (issue.fields.description) { formattedResponse += `\n## Description\n\n${issue.renderedFields?.description || issue.fields.description}\n`; } if (issue.fields.comment && issue.fields.comment.comments && issue.fields.comment.comments.length > 0) { formattedResponse += `\n## Comments (${issue.fields.comment.comments.length})\n\n`; issue.fields.comment.comments.forEach((comment: any, index: number) => { const commentDate = new Date(comment.created).toLocaleString(); formattedResponse += `### Comment ${index + 1} - ${comment.author.displayName} (${commentDate})\n\n`; formattedResponse += `${comment.body}\n\n`; }); } if (issue.fields.attachment && issue.fields.attachment.length > 0) { formattedResponse += `\n## Attachments (${issue.fields.attachment.length})\n\n`; formattedResponse += `| Filename | Size | Uploaded by | Date |\n`; formattedResponse += `|----------|------|-------------|------|\n`; issue.fields.attachment.forEach((attachment: any) => { const attachmentDate = new Date(attachment.created).toLocaleString(); const sizeInKb = Math.round(attachment.size / 1024); formattedResponse += `| ${attachment.filename} | ${sizeInKb} KB | ${attachment.author.displayName} | ${attachmentDate} |\n`; }); } if (issue.fields.worklog && issue.fields.worklog.worklogs && issue.fields.worklog.worklogs.length > 0) { formattedResponse += `\n## Work Log (${issue.fields.worklog.worklogs.length})\n\n`; formattedResponse += `| User | Time Spent | Date | Comment |\n`; formattedResponse += `|------|------------|------|--------|\n`; issue.fields.worklog.worklogs.forEach((worklog: any) => { const worklogDate = new Date(worklog.started).toLocaleString(); formattedResponse += `| ${worklog.author.displayName} | ${worklog.timeSpent} | ${worklogDate} | ${worklog.comment || 'No comment'} |\n`; }); } if (issue.changelog && issue.changelog.histories && issue.changelog.histories.length > 0) { formattedResponse += `\n## Change History\n\n`; formattedResponse += `| Date | User | Changes |\n`; formattedResponse += `|------|------|--------|\n`; issue.changelog.histories.forEach((history: any) => { const historyDate = new Date(history.created).toLocaleString(); const changes = history.items.map((item: any) => { return `${item.field} changed from "${item.fromString || 'none'}" to "${item.toString || 'none'}"`; }).join('; '); formattedResponse += `| ${historyDate} | ${history.author.displayName} | ${changes} |\n`; }); } return { content: [{ type: "text", text: formattedResponse }], isError: false, }; } catch (error: any) { let errorMsg = "An error occurred while retrieving the issue."; if (error.response) { if (error.response.status === 404) { errorMsg = `Issue ${issueKey} not found or you don't have permission to view it.`; } else { errorMsg = `Error ${error.response.status}: ${error.response.data?.errorMessages?.join(', ') || error.message}`; } } else if (error.message) { errorMsg = error.message; } return { content: [{ type: "text", text: `# Error\n\n${errorMsg}` }], isError: true, }; } }
- src/validators/index.ts:40-44 (schema)Yup validation schema for jira_get_issue input parameters, extending base Jira API schema with required issueKey validation.export const JiraIssueRequestSchema = JiraApiRequestSchema.shape({ issueKey: yup.string() .required("Issue key is required") .matches(/^[A-Z][A-Z0-9_]+-[1-9][0-9]*$/, "Invalid issue key format. The correct format is PROJECT-123"), });
- src/handlers/handlerTools.ts:32-35 (registration)Registers the jira_get_issue tool in the toolConfigs map used by handleCallTool to map tool name to its schema and handler function.jira_get_issue: { schema: JiraIssueRequestSchema, handler: getIssue },
- src/tools/getIssue.ts:12-40 (schema)JSON schema description for the tool, including input schema properties for jiraHost, email, apiToken, issueKey.export const getIssueToolDescription = { name: "jira_get_issue", description: "Retrieves detailed information about a specific Jira issue by key", 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 || "", }, issueKey: { type: "string", description: "The Jira issue key (e.g., 'PROJECT-123')", }, }, required: ["issueKey"], }, };
- src/handlers/handlerListTools.ts:48-61 (registration)Tool description registration in the listTools handler response for MCP tool listing.name: "jira_get_issue", description: "Retrieves details of a specific Jira issue by key", inputSchema: { type: "object", properties: { ...getCommonJiraProperties(), issueKey: { type: "string", description: "The Jira issue key (e.g., 'PROJECT-123')", }, }, required: ["issueKey"], }, },