create_issue
Create new Jira issues with standard fields (summary, description, assignee, priority, labels) and custom fields (Health Status, Completion Percentage, Progress Update) for project tracking.
Instructions
Create a new issue in Jira. Supports standard fields (summary, description, assignee, priority, labels) and custom fields (Health Status, Completion Percentage, Progress Update, etc.)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| projectKey | Yes | The project key (e.g., "TSSE") | |
| issueType | Yes | The issue type (e.g., "Epic", "Story", "Task", "Bug") | |
| summary | Yes | Issue summary/title | |
| description | No | Issue description (plain text) | |
| assignee | No | Assignee accountId or "currentuser()" for current user | |
| priority | No | Priority name (e.g., "High", "Medium", "Low") | |
| labels | No | Array of labels to apply | |
| duedate | No | Due date in YYYY-MM-DD format | |
| components | No | Array of component names | |
| healthStatus | No | Health Status value (e.g., "On Track", "At Risk", "Off Track") | |
| completionPercentage | No | Completion percentage (0-100) | |
| decisionNeeded | No | Decision Needed field content | |
| risksBlockers | No | Risks/Blockers field content | |
| progressUpdate | No | Progress Update field with three sections | |
| customFields | No | Additional custom fields as key-value pairs (e.g., {"customfield_14707": [{"value": "Data"}]}) |
Implementation Reference
- src/jira-client.ts:438-553 (handler)Core handler function that builds the issue payload with standard and custom fields, handles defaults for TSSE project, and calls Jira REST API /issue POST endpoint to create the issue.export async function createIssue(options: CreateIssueOptions): Promise<CreateIssueResult> { const isTSSEProject = options.projectKey.toUpperCase() === 'TSSE'; const fields: Record<string, unknown> = { project: { key: options.projectKey }, issuetype: { name: options.issueType }, summary: options.summary, }; // Add description if provided if (options.description) { fields.description = createADFDocument(options.description); } // Add assignee - default to currentuser() for TSSE project const assignee = options.assignee ?? (isTSSEProject ? 'currentuser()' : undefined); if (assignee) { // Handle 'currentuser()' or accountId if (assignee.toLowerCase() === 'currentuser()') { // Fetch actual accountId for current user (Jira Cloud requires accountId) const accountId = await getCurrentUserAccountId(); fields.assignee = { accountId }; } else { fields.assignee = { accountId: assignee }; } } // Add priority if provided if (options.priority) { fields.priority = { name: options.priority }; } // Add labels - default to ['EngProd', 'TSSP'] for TSSE project const labels = options.labels ?? (isTSSEProject ? ['EngProd', 'TSSP'] : undefined); if (labels && labels.length > 0) { fields.labels = labels; } // Add duedate - default to 30 days from today for TSSE project let duedate = options.duedate; if (!duedate && isTSSEProject) { const date = new Date(); date.setDate(date.getDate() + 30); duedate = date.toISOString().split('T')[0]; // YYYY-MM-DD format } if (duedate) { fields.duedate = duedate; } // Add components if provided if (options.components && options.components.length > 0) { fields.components = options.components.map(name => ({ name })); } // Add custom fields if (options.healthStatus) { fields[resolveFieldId('Health Status')] = { value: options.healthStatus }; } // BUG FIX: Completion Percentage field expects decimal (0.0-1.0), not percentage (0-100) // Convert percentage input to decimal: 10% -> 0.10 if (options.completionPercentage !== undefined) { const decimalValue = options.completionPercentage / 100; fields[resolveFieldId('Completion Percentage')] = decimalValue; } if (options.decisionNeeded) { fields[resolveFieldId('Decision Needed')] = createADFDocument(options.decisionNeeded); } if (options.risksBlockers) { fields[resolveFieldId('Risks/Blockers')] = createADFDocument(options.risksBlockers); } // Add Progress Update field if provided if (options.progressUpdate) { const currentDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); const weeklyUpdate = options.progressUpdate.weeklyUpdate || ''; const delivered = options.progressUpdate.delivered || ''; const whatsNext = options.progressUpdate.whatsNext || ''; fields[resolveFieldId('Progress Update')] = createProgressUpdateADF( `${currentDate}\n${weeklyUpdate}`, delivered, whatsNext ); } // Add any additional custom fields if (options.customFields) { for (const [key, value] of Object.entries(options.customFields)) { const fieldId = key.startsWith('customfield_') ? key : resolveFieldId(key); fields[fieldId] = value; } } const response = await jiraFetch<{ id: string; key: string; self: string; }>('/issue', { method: 'POST', body: JSON.stringify({ fields }), }); return { key: response.key, id: response.id, self: response.self, }; }
- src/jira-client.ts:402-430 (schema)TypeScript interfaces defining input parameters (CreateIssueOptions) and output (CreateIssueResult) for the createIssue handler.export interface CreateIssueOptions { projectKey: string; issueType: string; summary: string; description?: string; assignee?: string; // 'currentuser()' or accountId priority?: string; labels?: string[]; duedate?: string; // YYYY-MM-DD format components?: string[]; // Component names // Custom fields healthStatus?: string; completionPercentage?: number; decisionNeeded?: string; risksBlockers?: string; // Progress Update field sections progressUpdate?: { weeklyUpdate?: string; delivered?: string; whatsNext?: string; }; // Any additional custom fields as key-value pairs customFields?: Record<string, unknown>; } export interface CreateIssueResult { key: string; id: string; self: string;
- src/index.ts:588-675 (registration)MCP server registration of 'create_issue' tool, defining Zod input/output schemas (mirroring CreateIssueOptions/Result) and wrapper handler that validates inputs and delegates to jira-client.createIssue.server.registerTool( 'create_issue', { title: 'Create Issue', description: `Create a new issue in Jira. Supports standard fields (summary, description, assignee, priority, labels) and custom fields (Health Status, Completion Percentage, Progress Update, etc.)`, inputSchema: { projectKey: z.string().describe('The project key (e.g., "TSSE")'), issueType: z.string().describe('The issue type (e.g., "Epic", "Story", "Task", "Bug")'), summary: z.string().describe('Issue summary/title'), description: z.string().optional().describe('Issue description (plain text)'), assignee: z.string().optional().describe('Assignee accountId or "currentuser()" for current user'), priority: z.string().optional().describe('Priority name (e.g., "High", "Medium", "Low")'), labels: z.array(z.string()).optional().describe('Array of labels to apply'), duedate: z.string().optional().describe('Due date in YYYY-MM-DD format'), components: z.array(z.string()).optional().describe('Array of component names'), healthStatus: z.string().optional().describe('Health Status value (e.g., "On Track", "At Risk", "Off Track")'), completionPercentage: z.number().optional().describe('Completion percentage (0-100)'), decisionNeeded: z.string().optional().describe('Decision Needed field content'), risksBlockers: z.string().optional().describe('Risks/Blockers field content'), progressUpdate: z.object({ weeklyUpdate: z.string().optional().describe('Weekly update content'), delivered: z.string().optional().describe('What was delivered'), whatsNext: z.string().optional().describe('What is next'), }).optional().describe('Progress Update field with three sections'), customFields: z.record(z.unknown()).optional().describe('Additional custom fields as key-value pairs (e.g., {"customfield_14707": [{"value": "Data"}]})'), }, outputSchema: { success: z.boolean(), key: z.string().optional(), id: z.string().optional(), self: z.string().optional(), error: z.object({ message: z.string(), statusCode: z.number().optional(), details: z.unknown().optional(), }).optional(), }, }, async ({ projectKey, issueType, summary, description, assignee, priority, labels, duedate, components, healthStatus, completionPercentage, decisionNeeded, risksBlockers, progressUpdate, customFields }) => { try { if (!projectKey || !projectKey.trim()) { throw new Error('projectKey is required'); } if (!issueType || !issueType.trim()) { throw new Error('issueType is required'); } if (!summary || !summary.trim()) { throw new Error('summary is required'); } const result = await createIssue({ projectKey, issueType, summary, description, assignee, priority, labels, duedate, components, healthStatus, completionPercentage, decisionNeeded, risksBlockers, progressUpdate, customFields: customFields as Record<string, unknown> | undefined, }); const output = { success: true, key: result.key, id: result.id, self: result.self, }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, }; } catch (error) { const output = { success: false, ...formatError(error) }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, isError: true, }; } } );