UNPKG

@aot-tech/clockify-mcp-server

Version:

MCP Server for Clockify time tracking integration with AI tools

203 lines (202 loc) 8.03 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createEntryToolWithErrorResponse = exports.listEntriesTool = exports.createEntryTool = exports.extractErrorMessage = void 0; const zod_1 = require("zod"); const api_1 = require("../config/api"); const entries_1 = require("../clockify-sdk/entries"); const extractErrorMessage = (error) => { if (error.name === 'AxiosError' || error.isAxiosError) { if (error.response?.data) { const { status, data } = error.response; if (data.message) { const errorCode = data.code ? ` (Code: ${data.code})` : ''; return `${data.message}${errorCode}`; } if (data.error) { return data.error; } if (data.errors && Array.isArray(data.errors)) { return data.errors.join(', '); } switch (status) { case 401: return 'Authentication failed. Please check your API credentials.'; case 403: return 'Access denied. Check your permissions for this resource.'; case 404: return 'Resource not found. Please verify the provided IDs.'; case 429: return 'Rate limit exceeded. Please try again later.'; case 500: return 'Internal server error. Please try again later.'; default: return `HTTP ${status}: ${JSON.stringify(data)}`; } } if (error.code) { switch (error.code) { case 'ERR_BAD_REQUEST': return 'Bad request. Please check your request parameters.'; case 'ECONNREFUSED': return 'Connection refused. Please check if the service is running.'; case 'ENOTFOUND': return 'Network error. Please check your internet connection.'; case 'ETIMEDOUT': return 'Request timeout. Please try again.'; default: return `Network error: ${error.code}`; } } } return error.message || 'An unexpected error occurred'; }; exports.extractErrorMessage = extractErrorMessage; exports.createEntryTool = { name: api_1.TOOLS_CONFIG.entries.create.name, description: api_1.TOOLS_CONFIG.entries.create.description, parameters: { workspaceId: zod_1.z .string() .describe('The id of the workspace that gonna be saved the time entry'), billable: zod_1.z .boolean() .describe('If the task is billable or not') .optional() .default(true), description: zod_1.z.string().describe('The description of the time entry'), start: zod_1.z.coerce.date().describe('The start of the time entry'), end: zod_1.z.coerce.date().describe('The end of the time entry'), projectId: zod_1.z .string() .optional() .describe('The id of the project associated with this time entry'), }, handler: async (params) => { try { const result = await entries_1.entriesService.create(params); const entryInfo = `Entry successfully created. ID: ${result.data.id} Name: ${result.data.description}`; return { content: [ { type: 'text', text: entryInfo, }, ], }; } catch (error) { const errorMessage = (0, exports.extractErrorMessage)(error); return { content: [ { type: 'text', text: `Error creating entry: ${errorMessage}`, }, ], }; } }, }; exports.listEntriesTool = { name: api_1.TOOLS_CONFIG.entries.list.name, description: api_1.TOOLS_CONFIG.entries.list.description, parameters: { workspaceId: zod_1.z .string() .describe('The id of the workspace that gonna search for the entries. This is required to get the time entries .use clockify_get_workspaces tool to get the workspace id'), userId: zod_1.z .string() .describe('REQUIRED: Exact Clockify user ID (UUID format like "507f1f77bcf86cd799439011"). NEVER use "all", "user", or any placeholder. Must be a real user ID from Clockify workspace.'), start: zod_1.z.coerce .date() .describe('Start time of the entry to search for'), end: zod_1.z.coerce .date() .describe('End time of the entry to search for'), page: zod_1.z.number().describe('The page number to fetch'), pageSize: zod_1.z.number().describe('The number of results per page'), }, handler: async (params) => { try { const result = await entries_1.entriesService.find(params); const formmatedResults = result?.entries?.data?.map((entry) => ({ id: entry?.id, description: entry?.description, timeInterval: entry?.timeInterval || '', billable: entry?.billable || false, hourlyRate: entry?.hourlyRate || 0, costRate: entry?.costRate || 0, })); const formattedResultsWithHasNextPage = { ...formmatedResults, hasNextPage: result?.hasNextPage }; return { content: [ { type: 'text', text: `Clockify Time Entries ${JSON.stringify(formattedResultsWithHasNextPage, null, 2)}`, }, ], }; } catch (error) { const errorMessage = (0, exports.extractErrorMessage)(error); return { content: [ { type: 'text', text: `Error retrieving entries: ${errorMessage}`, }, ], }; } }, }; exports.createEntryToolWithErrorResponse = { name: api_1.TOOLS_CONFIG.entries.create.name, description: api_1.TOOLS_CONFIG.entries.create.description, parameters: { workspaceId: zod_1.z .string() .describe('The id of the workspace that gonna be saved the time entry'), billable: zod_1.z .boolean() .describe('If the task is billable or not') .optional() .default(true), description: zod_1.z.string().describe('The description of the time entry'), start: zod_1.z.coerce.date().describe('The start of the time entry'), end: zod_1.z.coerce.date().describe('The end of the time entry'), projectId: zod_1.z .string() .optional() .describe('The id of the project associated with this time entry'), }, handler: async (params) => { try { const result = await entries_1.entriesService.create(params); const entryInfo = `Entry successfully created. ID: ${result.data.id} Name: ${result.data.description}`; return { content: [ { type: 'text', text: entryInfo, }, ], }; } catch (error) { const errorMessage = (0, exports.extractErrorMessage)(error); return { content: [ { type: 'text', text: `Error creating entry: ${errorMessage}`, }, ], isError: true, }; } }, };