@pavi_thran_7/jira-mcp-server
Version:
MCP server for Jira integration with ticket creation and time logging
216 lines (190 loc) • 5.89 kB
JavaScript
/**
* @fileoverview Work logging tools for Jira MCP server
*/
/**
* @typedef {import('../types/jira.js').LogWorkParams} LogWorkParams
* @typedef {import('../types/jira.js').McpToolResult} McpToolResult
* @typedef {import('../jira-client.js').JiraClient} JiraClient
*/
/**
* Log work against a Jira ticket
* @param {JiraClient} jiraClient - Jira client instance
* @param {LogWorkParams} params - Work logging parameters
* @returns {Promise<McpToolResult>} Work logging result
*/
export async function logWork(jiraClient, params) {
try {
// Validate required parameters
if (!params.issueKey || !params.timeSpent) {
return {
content: [
{
type: 'text',
text: 'Error: Missing required parameters. Please provide issueKey and timeSpent.',
},
],
isError: true,
};
}
// Validate time format
const timeRegex = /^(\d+[wdhm]\s*)+$/i;
if (!timeRegex.test(params.timeSpent.trim())) {
return {
content: [
{
type: 'text',
text: 'Error: Invalid time format. Use formats like "2h 30m", "1d", "45m", "1w 2d 3h 30m".',
},
],
isError: true,
};
}
// Check if issue exists
const issueResult = await jiraClient.getIssue(params.issueKey);
if (!issueResult.success) {
return {
content: [
{
type: 'text',
text: `Error: Issue '${params.issueKey}' not found or not accessible: ${issueResult.error}`,
},
],
isError: true,
};
}
const issue = issueResult.data;
// Build worklog data
const worklogData = {
timeSpent: params.timeSpent,
comment: params.comment || 'Work logged via MCP',
};
// Add start time if provided
if (params.started) {
try {
const startDate = new Date(params.started);
if (isNaN(startDate.getTime())) {
return {
content: [
{
type: 'text',
text: 'Error: Invalid started date format. Please use ISO format (e.g., "2023-12-01T09:00:00.000Z").',
},
],
isError: true,
};
}
worklogData.started = startDate.toISOString();
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error: Invalid started date: ${error.message}`,
},
],
isError: true,
};
}
} else {
// Default to current time
worklogData.started = new Date().toISOString();
}
// Add billing account if provided
if (params.billingAccountId) {
// Note: Billing account handling depends on Jira configuration
// This is a placeholder for billing account integration
worklogData.attributes = {
_BillingAccount_: params.billingAccountId,
};
}
// Create the worklog
const worklogResult = await jiraClient.addWorklog(params.issueKey, worklogData);
if (!worklogResult.success) {
return {
content: [
{
type: 'text',
text: `Error logging work: ${worklogResult.error}`,
},
],
isError: true,
};
}
const worklog = worklogResult.data;
const issueUrl = `${jiraClient.baseUrl}/browse/${params.issueKey}`;
// Format the response
const startedTime = new Date(worklog.started).toLocaleString();
const billingInfo = params.billingAccountId
? `\n- Billing Account: ${params.billingAccountId}`
: '';
return {
content: [
{
type: 'text',
text: `⏰ Successfully logged work!
**Work Log Details:**
- Issue: ${params.issueKey} - ${issue.fields.summary}
- Time Spent: ${params.timeSpent}
- Started: ${startedTime}
- Comment: ${params.comment || 'Work logged via MCP'}${billingInfo}
- Work Log ID: ${worklog.id}
**Issue URL:** ${issueUrl}
The work has been logged and will be reflected in your time tracking reports.`,
},
],
isError: false,
};
} catch (error) {
return {
content: [
{
type: 'text',
text: `Error logging work: ${error.message}`,
},
],
isError: true,
};
}
}
/**
* Parse time string into seconds
* @param {string} timeString - Time string (e.g., "2h 30m")
* @returns {number} Time in seconds
*/
function parseTimeToSeconds(timeString) {
const timeRegex = /(\d+)([wdhm])/gi;
let totalSeconds = 0;
let match;
const multipliers = {
w: 7 * 24 * 60 * 60, // week
d: 24 * 60 * 60, // day
h: 60 * 60, // hour
m: 60, // minute
};
while ((match = timeRegex.exec(timeString)) !== null) {
const value = parseInt(match[1]);
const unit = match[2].toLowerCase();
totalSeconds += value * (multipliers[unit] || 0);
}
return totalSeconds;
}
/**
* Format seconds into human-readable time
* @param {number} seconds - Time in seconds
* @returns {string} Formatted time string
*/
function formatSecondsToTime(seconds) {
const weeks = Math.floor(seconds / (7 * 24 * 60 * 60));
seconds %= 7 * 24 * 60 * 60;
const days = Math.floor(seconds / (24 * 60 * 60));
seconds %= 24 * 60 * 60;
const hours = Math.floor(seconds / (60 * 60));
seconds %= 60 * 60;
const minutes = Math.floor(seconds / 60);
const parts = [];
if (weeks > 0) parts.push(`${weeks}w`);
if (days > 0) parts.push(`${days}d`);
if (hours > 0) parts.push(`${hours}h`);
if (minutes > 0) parts.push(`${minutes}m`);
return parts.join(' ') || '0m';
}