Skip to main content
Glama

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

TableJSON Schema
NameRequiredDescriptionDefault
issueKeyYesThe issue key (e.g., "TSSE-984")
refreshDateNoIf true, updates the date to today while preserving all existing content. Use this to "refresh" the progress update without changing the content.
weeklyUpdateNoText to add after the weekly header. Current date will be auto-inserted.
deliveredNoText describing what was delivered
whatsNextNoText describing upcoming work

Implementation Reference

  • 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, }; } } );
  • 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(), },
  • 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; }
  • 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:' } ), ], }; }

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/eh24905-wiz/jira-mcp'

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