get_schedule
Retrieve work schedule for authenticated users within specified date ranges to view planned tasks and manage time tracking in JIRA Tempo.
Instructions
Retrieve work schedule for authenticated user and date range
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| startDate | Yes | Start date in YYYY-MM-DD format | |
| endDate | No | End date in YYYY-MM-DD format (optional, defaults to startDate) |
Implementation Reference
- src/tools/get-schedule.ts:16-171 (handler)The core handler function for the 'get_schedule' tool. Fetches the user's work schedule from Tempo API for a given date range, processes working/non-working days, computes summaries, formats a detailed markdown response, and integrates hints for other tools like bulk_post_worklogs.export async function getSchedule( tempoClient: TempoClient, input: GetScheduleInput ): Promise<CallToolResult> { try { const { startDate, endDate } = input; // Use endDate or default to startDate const actualEndDate = endDate || startDate; // Fetch schedule from Tempo API (automatically filters by authenticated user) const scheduleResponses = await tempoClient.getSchedule({ startDate, endDate: actualEndDate }); if (!scheduleResponses || scheduleResponses.length === 0) { return { content: [ { type: "text", text: "No schedule data found for the specified date range." } ], isError: true }; } // Process the first schedule response (should contain the authenticated user's schedule) const scheduleResponse: TempoScheduleResponse = scheduleResponses[0]; const { schedule } = scheduleResponse; // Process and format the schedule days const scheduleDays: ScheduleDay[] = schedule.days.map((day) => { // Extract date part and parse it const datePart = day.date; const parsedDate = parseISO(datePart); const humanReadableDate = format(parsedDate, "EEEE, MMMM do, yyyy"); // Create hybrid format: ISO date + human-readable in parentheses const formattedDate = `${datePart} (${humanReadableDate})`; return { date: day.date, formattedDate, requiredHours: Math.round((day.requiredSeconds / 3600) * 100) / 100, // Round to 2 decimal places isWorkingDay: day.type === "WORKING_DAY", type: day.type === "WORKING_DAY" ? "Working Day" : "Non-Working Day" }; }); // Calculate summary statistics const totalDays = scheduleDays.length; const workingDays = scheduleDays.filter(day => day.isWorkingDay).length; const nonWorkingDays = totalDays - workingDays; const totalRequiredHours = Math.round((schedule.requiredSeconds / 3600) * 100) / 100; const averageDailyHours = workingDays > 0 ? Math.round((totalRequiredHours / workingDays) * 100) / 100 : 0; const result: GetScheduleResponse = { days: scheduleDays, summary: { totalDays, workingDays, nonWorkingDays, totalRequiredHours, averageDailyHours } }; // Format response for display let displayText = `## Work Schedule (${startDate}`; if (endDate && endDate !== startDate) { displayText += ` to ${endDate}`; } displayText += `)\n\n`; // Period Summary displayText += `**Period Summary:**\n`; displayText += `- Total Days: ${result.summary.totalDays}\n`; displayText += `- Working Days: ${result.summary.workingDays}\n`; displayText += `- Non-Working Days: ${result.summary.nonWorkingDays}\n`; displayText += `- Total Required Hours: ${result.summary.totalRequiredHours}h\n`; if (result.summary.workingDays > 0) { displayText += `- Average Daily Hours: ${result.summary.averageDailyHours}h\n`; } displayText += `\n`; // Schedule Details if (scheduleDays.length > 0) { displayText += `**Schedule Details:**\n`; // Show all days (limit to reasonable amount for display) const displayLimit = 100; // Reasonable limit for display const daysToShow = scheduleDays.slice(0, displayLimit); for (const day of daysToShow) { if (day.isWorkingDay) { displayText += `β’ ${day.formattedDate}: ${day.requiredHours}h (${day.type})\n`; } else { displayText += `β’ ${day.formattedDate}: - (${day.type})\n`; } } if (scheduleDays.length > displayLimit) { displayText += `\n*Showing first ${displayLimit} of ${scheduleDays.length} total days*\n`; displayText += `*π‘ Tip: Use a shorter date range for more detailed display*\n`; } } // Add helpful information for integration with other tools if (result.summary.workingDays > 0) { displayText += `\n**π‘ Next Steps - Schedule-Aware Time Logging:**\n`; displayText += `- **Single Entry**: Use this schedule to verify working days before post_worklog\n`; displayText += `- **Bulk Entry**: Create bulk worklogs only for the ${result.summary.workingDays} working days shown above\n`; displayText += `- **Smart Planning**: Total capacity is ${result.summary.totalRequiredHours}h across ${result.summary.workingDays} working days\n`; displayText += `- **Avoid Errors**: Non-working days (${result.summary.nonWorkingDays} days) should not have time entries\n`; if (result.summary.totalRequiredHours > 0) { displayText += `\n**Example Bulk Entry**: "Fill all working days shown above with 8 hours on PROJ-1234"\n`; } } return { content: [ { type: "text", text: displayText } ], isError: false }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); // Provide more helpful error messages for common issues let enhancedErrorMessage = errorMessage; if (errorMessage.includes('Authentication failed')) { enhancedErrorMessage += `\n\nTip: Check your Personal Access Token (PAT) in the TEMPO_PAT environment variable.`; } else if (errorMessage.includes('Access forbidden')) { enhancedErrorMessage += `\n\nTip: Make sure you have permission to access schedule data in Tempo.`; } else if (errorMessage.includes('404') || errorMessage.includes('not found')) { enhancedErrorMessage = `Schedule endpoint not found. This may indicate that Tempo Core API v2 is not available on your JIRA instance.\n\nTip: Verify that Tempo is properly installed and the Core API is enabled.`; } return { content: [ { type: "text", text: `## Error Retrieving Schedule\n\n**Date Range:** ${input.startDate}${input.endDate ? ` to ${input.endDate}` : ''}\n\n**Error:** ${enhancedErrorMessage}` } ], isError: true }; } }
- src/types/mcp.ts:44-47 (schema)Zod schema defining the input validation for the get_schedule tool: requires startDate (YYYY-MM-DD), optional endDate.export const GetScheduleInputSchema = z.object({ startDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "Start date must be in YYYY-MM-DD format"), endDate: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "End date must be in YYYY-MM-DD format").optional(), });
- src/index.ts:191-210 (registration)Registration of the get_schedule tool in the MCP ListToolsRequestSchema handler, providing name, description, and input schema.{ name: TOOL_NAMES.GET_SCHEDULE, description: "Retrieve work schedule for authenticated user and date range", inputSchema: { type: "object", properties: { startDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "Start date in YYYY-MM-DD format", }, endDate: { type: "string", pattern: "^\\d{4}-\\d{2}-\\d{2}$", description: "End date in YYYY-MM-DD format (optional, defaults to startDate)", }, }, required: ["startDate"], }, },
- src/index.ts:241-244 (registration)Execution handler for the get_schedule tool in the MCP CallToolRequestSchema switch statement: parses input with schema and calls the handler function.case TOOL_NAMES.GET_SCHEDULE: { const input = GetScheduleInputSchema.parse(args); return await getSchedule(tempoClient, input); }
- src/tempo-client.ts:252-283 (helper)Helper method in TempoClient class that calls the Tempo API to retrieve schedule data, used by the get_schedule tool handler.async getSchedule(params: GetScheduleParams): Promise<TempoScheduleResponse[]> { // Get the current authenticated user const currentUser = await this.getCurrentUser(); console.error(`π SCHEDULE SEARCH: Processing request for params:`, JSON.stringify(params)); console.error(`π€ USER: Using authenticated user ${currentUser}`); try { const { startDate, endDate } = params; const actualEndDate = endDate || startDate; const searchParams = { from: startDate, to: actualEndDate, userKeys: [currentUser] }; console.error(`π TEMPO SCHEDULE SEARCH: Sending request with:`, JSON.stringify(searchParams)); const response = await this.axiosInstance.post( `/rest/tempo-core/2/user/schedule/search`, searchParams ); console.error(`π TEMPO SCHEDULE RESPONSE: Received ${Array.isArray(response.data) ? response.data.length : 'non-array'} results`); const results = Array.isArray(response.data) ? response.data : []; return results; } catch (error) { console.error(`β ERROR in getSchedule:`, error);