post_worklog
Log work hours in JIRA Tempo by creating time entries with issue keys, dates, and descriptions for accurate time tracking.
Instructions
Create a new worklog entry. For better results, consider using get_schedule first to verify working days and expected hours.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| issueKey | Yes | JIRA issue key (e.g., PROJ-1234) | |
| hours | Yes | Hours worked (decimal) | |
| startDate | Yes | Start date in YYYY-MM-DD format | |
| endDate | No | End date in YYYY-MM-DD format (optional, defaults to startDate) | |
| billable | No | Whether the time is billable (default: true) | |
| description | No | Work description (optional) |
Implementation Reference
- src/tools/post-worklog.ts:10-102 (handler)The postWorklog function implementing the core tool logic: parses input, creates Tempo worklog payload, calls API, handles response and errors with formatted markdown output.export async function postWorklog( tempoClient: TempoClient, input: PostWorklogInput ): Promise<CallToolResult> { try { const { issueKey, hours, startDate, endDate, billable = true, description } = input; // Create the worklog payload using the Tempo client (automatically uses authenticated user) const payload = await tempoClient.createWorklogPayload({ issueKey, hours, startDate, endDate, billable, description }); // Create the worklog const worklogResponse = await tempoClient.createWorklog(payload); // Handle the response - API returns an array with a single worklog object const worklog = Array.isArray(worklogResponse) ? worklogResponse[0] : worklogResponse; // Format success response const actualEndDate = endDate || startDate; const billableHours = billable ? hours : 0; let displayText = `## Worklog Created Successfully\n\n`; displayText += `**Issue:** ${issueKey} - ${worklog.issue.summary}\n`; displayText += `**Hours:** ${hours}h`; if (billableHours !== hours) { displayText += ` (${billableHours}h billable)`; } displayText += `\n`; displayText += `**Date:** ${startDate}`; if (actualEndDate !== startDate) { displayText += ` to ${actualEndDate}`; } displayText += `\n`; if (description) { displayText += `**Description:** ${description}\n`; } displayText += `**Worklog ID:** ${worklog.tempoWorklogId || worklog.id}\n`; displayText += `**Time Spent:** ${worklog.timeSpent}\n`; // Add some helpful information displayText += `\n### Details\n`; displayText += `- Created at: ${new Date().toISOString()}\n`; displayText += `- Total seconds: ${worklog.timeSpentSeconds}\n`; displayText += `- Billable seconds: ${worklog.billableSeconds}\n`; return { content: [ { type: "text", text: displayText } ], isError: false }; } catch (error) { let errorMessage = error instanceof Error ? error.message : String(error); // Provide more helpful error messages for common issues if (errorMessage.includes('not found')) { errorMessage += `\n\nTip: Make sure the issue key '${input.issueKey}' exists and you have access to it.`; } else if (errorMessage.includes('Authentication failed')) { errorMessage += `\n\nTip: Check your Personal Access Token (PAT) in the TEMPO_PAT environment variable.`; } else if (errorMessage.includes('Access forbidden')) { errorMessage += `\n\nTip: Make sure you have permission to log time to this issue and that Tempo is properly configured.`; } return { content: [ { type: "text", text: `## Error Creating Worklog\n\n**Issue:** ${input.issueKey}\n**Hours:** ${input.hours}\n**Date:** ${input.startDate}\n\n**Error:** ${errorMessage}` } ], isError: true }; } }
- src/types/mcp.ts:15-22 (schema)Zod schema defining input validation for post_worklog tool parameters.export const PostWorklogInputSchema = z.object({ issueKey: z.string().min(1, "Issue key is required"), hours: z.number().min(0.1, "Hours must be at least 0.1").max(24, "Hours cannot exceed 24"), 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(), billable: z.boolean().optional(), description: z.string().optional(), });
- src/index.ts:226-229 (registration)MCP server registration: handles execution of post_worklog tool by parsing input schema and calling the handler function.case TOOL_NAMES.POST_WORKLOG: { const input = PostWorklogInputSchema.parse(args); return await postWorklog(tempoClient, input); }
- src/index.ts:97-133 (registration)MCP server registration: declares post_worklog tool metadata including name, description, and input schema for ListTools requests.name: TOOL_NAMES.POST_WORKLOG, description: "Create a new worklog entry. For better results, consider using get_schedule first to verify working days and expected hours.", inputSchema: { type: "object", properties: { issueKey: { type: "string", description: "JIRA issue key (e.g., PROJ-1234)", }, hours: { type: "number", minimum: 0.1, maximum: 24, description: "Hours worked (decimal)", }, 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)", }, billable: { type: "boolean", description: "Whether the time is billable (default: true)", }, description: { type: "string", description: "Work description (optional)", }, }, required: ["issueKey", "hours", "startDate"], }, },
- src/types/mcp.ts:83-83 (helper)Tool name constant used for registration and identification.POST_WORKLOG: "post_worklog",