update_progress
Update Jira issue progress using a structured template with weekly updates, delivered work, and next steps. Refresh dates or modify specific sections while preserving existing content.
Instructions
Update the Progress Update field (customfield_15112) on a Jira issue. This field uses a structured template with three sections:
Weekly Update: "ℹ️ Update for week of [date]:" - automatically includes current date
Delivered: "✅ What we've delivered so far:"
What's Next: "❓ What's next:"
Options:
Use refreshDate=true to update just the date while preserving all existing content
Only sections you explicitly provide will be updated; others are preserved from existing content
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| issueKey | Yes | The issue key (e.g., "TSSE-984") | |
| refreshDate | No | If true, updates the date to today while preserving all existing content. Use this to "refresh" the progress update without changing the content. | |
| weeklyUpdate | No | Text to add after the weekly header. Current date will be auto-inserted. | |
| delivered | No | Text describing what was delivered | |
| whatsNext | No | Text describing upcoming work |
Implementation Reference
- src/jira-client.ts:1135-1227 (handler)Core handler function that implements the update_progress tool logic: fetches current Progress Update field, parses existing sections, applies selective updates (refresh date, weeklyUpdate, delivered, whatsNext), generates new ADF document, and updates the Jira issue.export async function updateProgressField( issueKey: string, options: { refreshDate?: boolean; weeklyUpdate?: string; delivered?: string; whatsNext?: string; } ): Promise<{ success: boolean; updatedSections: string[]; parsedExisting?: { weeklyUpdate: string; delivered: string; whatsNext: string } }> { const PROGRESS_FIELD_ID = 'customfield_15112'; // Fetch current field value const currentValue = await getIssueCustomField(issueKey, PROGRESS_FIELD_ID); // Parse existing content const existing = parseProgressUpdateADF(currentValue); // Format current date const currentDate = new Date().toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric', }); // Determine which sections to update const updatedSections: string[] = []; let weeklyUpdate = existing.weeklyUpdate; let delivered = existing.delivered; let whatsNext = existing.whatsNext; // Handle refreshDate option - preserve existing content but update the date if (options.refreshDate) { // Extract just the content part from weeklyUpdate (remove old date or [date] placeholder if present) // The parsed weeklyUpdate may be: // - "December 10, 2025 - Some content" // - "[date] - Some content" // - Just "[date]" // - Just content with no date prefix let contentOnly = weeklyUpdate; // Remove date pattern like "December 10, 2025 - " or "December 10, 2025" const dateMatch = contentOnly.match(/^[A-Z][a-z]+ \d{1,2}, \d{4}\s*-?\s*/); if (dateMatch) { contentOnly = contentOnly.slice(dateMatch[0].length); } // Remove [date] placeholder pattern like "[date] - " or "[date]" const placeholderMatch = contentOnly.match(/^\[date\]\s*-?\s*/); if (placeholderMatch) { contentOnly = contentOnly.slice(placeholderMatch[0].length); } contentOnly = contentOnly.trim(); weeklyUpdate = contentOnly ? `${currentDate} - ${contentOnly}` : currentDate; updatedSections.push('weeklyUpdate (date refreshed)'); } // Handle explicit weeklyUpdate option if (options.weeklyUpdate !== undefined) { weeklyUpdate = options.weeklyUpdate ? `${currentDate} - ${options.weeklyUpdate}` : currentDate; updatedSections.push('weeklyUpdate'); } if (options.delivered !== undefined) { delivered = options.delivered; updatedSections.push('delivered'); } if (options.whatsNext !== undefined) { whatsNext = options.whatsNext; updatedSections.push('whatsNext'); } // Create the updated ADF document const updatedADF = createProgressUpdateADF(weeklyUpdate, delivered, whatsNext); // Update the field await jiraFetch<void>(`/issue/${issueKey}`, { method: 'PUT', body: JSON.stringify({ fields: { [PROGRESS_FIELD_ID]: updatedADF, }, }), }); return { success: true, updatedSections, parsedExisting: existing, }; }
- src/index.ts:520-582 (registration)MCP tool registration for 'update_progress', including title, description, inputSchema, outputSchema, and wrapper handler that validates inputs and delegates to updateProgressField.server.registerTool( 'update_progress', { title: 'Update Progress', description: `Update the Progress Update field (customfield_15112) on a Jira issue. This field uses a structured template with three sections: - Weekly Update: "ℹ️ Update for week of [date]:" - automatically includes current date - Delivered: "✅ What we've delivered so far:" - What's Next: "❓ What's next:" Options: - Use refreshDate=true to update just the date while preserving all existing content - Only sections you explicitly provide will be updated; others are preserved from existing content`, inputSchema: { issueKey: z.string().describe('The issue key (e.g., "TSSE-984")'), refreshDate: z.boolean().optional().describe('If true, updates the date to today while preserving all existing content. Use this to "refresh" the progress update without changing the content.'), weeklyUpdate: z.string().optional().describe('Text to add after the weekly header. Current date will be auto-inserted.'), delivered: z.string().optional().describe('Text describing what was delivered'), whatsNext: z.string().optional().describe('Text describing upcoming work'), }, outputSchema: { success: z.boolean(), updatedSections: z.array(z.string()).optional(), parsedExisting: z.object({ weeklyUpdate: z.string(), delivered: z.string(), whatsNext: z.string(), }).optional(), error: z.object({ message: z.string(), statusCode: z.number().optional(), details: z.unknown().optional(), }).optional(), }, }, async ({ issueKey, refreshDate, weeklyUpdate, delivered, whatsNext }) => { try { if (!issueKey || !issueKey.trim()) { throw new Error('issueKey is required'); } if (!refreshDate && weeklyUpdate === undefined && delivered === undefined && whatsNext === undefined) { throw new Error('At least one of refreshDate, weeklyUpdate, delivered, or whatsNext must be provided'); } const result = await updateProgressField(issueKey, { refreshDate, weeklyUpdate, delivered, whatsNext, }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], structuredContent: result, }; } catch (error) { const output = { success: false, ...formatError(error) }; return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }], structuredContent: output, isError: true, }; } } );
- src/index.ts:532-552 (schema)Zod inputSchema and outputSchema definitions for the update_progress tool, defining parameters like issueKey, refreshDate, weeklyUpdate, delivered, whatsNext and response structure.inputSchema: { issueKey: z.string().describe('The issue key (e.g., "TSSE-984")'), refreshDate: z.boolean().optional().describe('If true, updates the date to today while preserving all existing content. Use this to "refresh" the progress update without changing the content.'), weeklyUpdate: z.string().optional().describe('Text to add after the weekly header. Current date will be auto-inserted.'), delivered: z.string().optional().describe('Text describing what was delivered'), whatsNext: z.string().optional().describe('Text describing upcoming work'), }, outputSchema: { success: z.boolean(), updatedSections: z.array(z.string()).optional(), parsedExisting: z.object({ weeklyUpdate: z.string(), delivered: z.string(), whatsNext: z.string(), }).optional(), error: z.object({ message: z.string(), statusCode: z.number().optional(), details: z.unknown().optional(), }).optional(), },
- src/jira-client.ts:880-985 (helper)Helper function to parse existing Progress Update ADF content into structured sections (weeklyUpdate, delivered, whatsNext), supporting both simple paragraphs and complex panel/bullet formats.function parseProgressUpdateADF(adf: unknown): { weeklyUpdate: string; delivered: string; whatsNext: string; } { const result = { weeklyUpdate: '[date]', delivered: '', whatsNext: '', }; if (!adf || typeof adf !== 'object') return result; const doc = adf as { content?: unknown[] }; if (!doc.content || !Array.isArray(doc.content)) return result; // Track which section we're currently parsing let currentSection: 'weekly' | 'delivered' | 'whatsNext' | null = null; for (const block of doc.content) { const b = block as { type?: string; content?: unknown[]; attrs?: unknown }; // Handle panel blocks (complex format) if (b.type === 'panel' && b.content) { let panelHeader = ''; const panelItems: string[] = []; for (const panelChild of b.content) { const pc = panelChild as { type?: string; content?: unknown[] }; // Extract heading text if (pc.type === 'heading') { panelHeader = extractTextFromADFNode(pc); } // Extract bullet list items if (pc.type === 'bulletList') { panelItems.push(...extractBulletListItems(pc)); } } // Determine which section based on header (handle both straight and curly apostrophes) const normalizedHeader = panelHeader.replace(/[']/g, "'"); if (normalizedHeader.includes('Update for week of')) { const dateMatch = panelHeader.match(/Update for week of ([^:]+):/); result.weeklyUpdate = dateMatch ? dateMatch[1].trim() : '[date]'; if (panelItems.length > 0) { result.weeklyUpdate += ' - ' + panelItems.join('. '); } } else if (normalizedHeader.includes('delivered so far')) { result.delivered = panelItems.join('. '); } else if (normalizedHeader.includes("What's next")) { result.whatsNext = panelItems.join('. '); } continue; } // Handle simple paragraph format if (b.type === 'paragraph' && b.content) { const paragraphText = extractTextFromADFNode(b); // Check for section headers const weeklyMatch = paragraphText.match(/Update for week of ([^:]+):(.*)/); if (weeklyMatch) { result.weeklyUpdate = weeklyMatch[1].trim(); if (weeklyMatch[2]?.trim()) { result.weeklyUpdate += ' - ' + weeklyMatch[2].trim(); } currentSection = 'weekly'; continue; } const deliveredMatch = paragraphText.match(/What we['']ve delivered so far:(.*)/); if (deliveredMatch) { result.delivered = deliveredMatch[1]?.trim() || ''; currentSection = 'delivered'; continue; } const whatsNextMatch = paragraphText.match(/What['']s next:(.*)/); if (whatsNextMatch) { result.whatsNext = whatsNextMatch[1]?.trim() || ''; currentSection = 'whatsNext'; continue; } // If we're in a section and this is additional content, append it if (currentSection && paragraphText.trim()) { switch (currentSection) { case 'weekly': result.weeklyUpdate += (result.weeklyUpdate.includes(' - ') ? '. ' : ' - ') + paragraphText.trim(); break; case 'delivered': result.delivered += (result.delivered ? '. ' : '') + paragraphText.trim(); break; case 'whatsNext': result.whatsNext += (result.whatsNext ? '. ' : '') + paragraphText.trim(); break; } } } } return result; }
- src/jira-client.ts:686-709 (helper)Helper function to create the ADF document for the Progress Update field with the three-section template using emojis and proper formatting.export function createProgressUpdateADF( weeklyUpdate: string, delivered: string, whatsNext: string ): object { return { type: 'doc', version: 1, content: [ createADFParagraph( `Update for week of ${weeklyUpdate}:`, { shortName: ':info:', id: 'atlassian-info', text: ':info:' } ), createADFParagraph( `What we've delivered so far: ${delivered}`, { shortName: ':check_mark:', id: 'atlassian-check_mark', text: ':check_mark:' } ), createADFParagraph( `What's next: ${whatsNext}`, { shortName: ':question:', id: 'atlassian-question_mark', text: ':question:' } ), ], }; }