Skip to main content
Glama
jira-worklogs.ts6.17 kB
/** * Consolidated Jira Worklogs Tool. * Combines all worklog (time tracking) operations into a single action-based tool. * @module tools/consolidated/jira-worklogs */ import { z } from 'zod'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { getWorklogs, addWorklog, updateWorklog, deleteWorklog, } from '../../jira/endpoints/worklogs.js'; import { encodeToon } from '../../formatters/toon.js'; import { adfToMarkdown, isAdfDocument } from '../../utils/adf.js'; import { createLogger } from '../../utils/logger.js'; import type { JiraWorklog } from '../../jira/types.js'; const logger = createLogger('tool-jira-worklogs'); /** * Schema for the jira_worklogs tool. */ const jiraWorklogsSchema = z.object({ action: z .enum(['list', 'add', 'update', 'delete']) .describe('The action to perform'), // Issue identification issueKey: z.string().describe('Issue key (e.g., "PROJ-123")'), // Worklog identification worklogId: z .string() .optional() .describe('Worklog ID - required for update, delete'), // Get options full: z .boolean() .optional() .default(false) .describe('Return full data instead of minimal fields'), // List pagination maxResults: z .number() .optional() .default(50) .describe('Maximum results (default: 50)'), startAt: z .number() .optional() .default(0) .describe('Starting index for pagination'), // Add/Update fields timeSpent: z .string() .optional() .describe('Time spent (e.g., "2h", "1d 4h", "30m") - required for add'), started: z .string() .optional() .describe('When work started (ISO datetime) - required for add'), comment: z.string().optional().describe('Worklog comment'), // Estimate adjustment options (for add action) adjustEstimate: z .enum(['new', 'leave', 'manual', 'auto']) .optional() .describe( 'How to adjust remaining estimate: new (set new value), leave (unchanged), manual (reduce by amount), auto (reduce by time spent)' ), newEstimate: z .string() .optional() .describe( 'New remaining estimate (e.g., "3h") - required when adjustEstimate is "new"' ), reduceBy: z .string() .optional() .describe( 'Amount to reduce remaining estimate by (e.g., "1h") - required when adjustEstimate is "manual"' ), }); type JiraWorklogsInput = z.infer<typeof jiraWorklogsSchema>; /** * Simplifies a worklog for TOON encoding. */ function simplifyWorklog(worklog: JiraWorklog): Record<string, unknown> { let commentText = ''; if (worklog.comment) { if (typeof worklog.comment === 'string') { commentText = worklog.comment; } else if (isAdfDocument(worklog.comment)) { commentText = adfToMarkdown(worklog.comment); } } return { id: worklog.id, author: worklog.author.displayName, timeSpent: worklog.timeSpent, started: worklog.started.split('T')[0], comment: commentText.substring(0, 100), }; } /** * Handler for the jira_worklogs tool. */ async function handleJiraWorklogs(input: JiraWorklogsInput): Promise<string> { const { action, issueKey } = input; switch (action) { case 'list': { const response = await getWorklogs( issueKey, input.startAt ?? 0, input.maxResults ?? 50 ); if (input.full) { return JSON.stringify(response, null, 2); } const simplified = response.values.map(simplifyWorklog); return encodeToon({ worklogs: simplified, total: response.total, hasMore: !response.isLast, }); } case 'add': { if (!input.timeSpent || !input.started) { throw new Error('timeSpent and started are required for add action'); } const worklog = await addWorklog( issueKey, input.timeSpent, input.started, input.comment, { adjustEstimate: input.adjustEstimate, newEstimate: input.newEstimate, reduceBy: input.reduceBy, } ); return encodeToon({ success: true, worklog: { id: worklog.id, timeSpent: worklog.timeSpent, started: worklog.started.split('T')[0], }, }); } case 'update': { if (!input.worklogId) { throw new Error('worklogId is required for update action'); } const worklog = await updateWorklog(issueKey, input.worklogId, { timeSpent: input.timeSpent, started: input.started, comment: input.comment, }); return encodeToon({ success: true, worklog: { id: worklog.id, timeSpent: worklog.timeSpent, }, }); } case 'delete': { if (!input.worklogId) { throw new Error('worklogId is required for delete action'); } await deleteWorklog(issueKey, input.worklogId); return encodeToon({ success: true, message: `Worklog ${input.worklogId} deleted`, }); } default: throw new Error(`Unknown action: ${action}`); } } /** * Registers the jira_worklogs tool with the MCP server. */ export function registerJiraWorklogsTool(server: McpServer): void { server.tool( 'jira_worklogs', `Manage Jira time tracking / worklogs. Actions: - list: List worklogs for an issue - add: Log time spent on an issue - update: Update a worklog entry - delete: Remove a worklog entry Time format examples: "30m", "2h", "1d", "1d 4h 30m"`, jiraWorklogsSchema.shape, async (params) => { try { const input = jiraWorklogsSchema.parse(params); const result = await handleJiraWorklogs(input); return { content: [{ type: 'text', text: result }] }; } catch (err) { logger.error( 'jira_worklogs error', err instanceof Error ? err : new Error(String(err)) ); const message = err instanceof Error ? err.message : 'Unknown error'; return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true, }; } } ); }

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/icy-r/jira-mcp'

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