# Directory Structure ``` ├── .env.example ├── .gitignore ├── .nvmrc ├── package-lock.json ├── package.json ├── README.md ├── smithery.yaml ├── src │ ├── clockify-sdk │ │ ├── entries.ts │ │ ├── projects.ts │ │ ├── users.ts │ │ └── workspaces.ts │ ├── config │ │ └── api.ts │ ├── index.ts │ ├── tools │ │ ├── entries.ts │ │ ├── projects.ts │ │ ├── users.ts │ │ └── workspaces.ts │ ├── types │ │ └── index.ts │ └── validation │ ├── entries │ │ ├── create-entry-schema.ts │ │ ├── delete-entry-schema.ts │ │ ├── edit-entry-schema.ts │ │ └── find-entry-schema.ts │ └── projects │ └── find-project-schema.ts ├── test │ ├── entries.test.ts │ ├── setup.ts │ ├── users.test.ts │ └── workspaces.test.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- ``` v20.17.0 ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules .env dist .dist .smithery ``` -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` CLOCKIFY_API_URL=https://api.clockify.me/api/v1 CLOCKIFY_API_TOKEN= # Just used in tests TEST_WORKSPACE_ID= TEST_USER_ID= TEST_PROJECT_ID= ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # Clockify MCP Server [](https://smithery.ai/server/@https-eduardo/clockify-mcp-server) This MCP Server integrates with AI Tools to manage your time entries in Clockify, so you can register your time entries just sending an prompt to LLM. ## Next implementations - Implement tags for entries ## Using in Claude Desktop ### Installing via Smithery To install clockify-mcp-server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@https-eduardo/clockify-mcp-server): ```bash npx -y @smithery/cli install @https-eduardo/clockify-mcp-server --client claude ``` ### Installing Manually First, install tsx globally `npm i -g tsx` Then insert the MCP server in `claude_desktop_config` ```json { "mcpServers": { "clockify-time-entries": { "command": "tsx", "args": ["ABSOLUTE_PATH/src/index.ts", "--local"], "env": { "CLOCKIFY_API_URL": "https://api.clockify.me/api/v1", "CLOCKIFY_API_TOKEN": "YOUR_CLOCKIFY_API_TOKEN_HERE" } } } } ``` ``` -------------------------------------------------------------------------------- /smithery.yaml: -------------------------------------------------------------------------------- ```yaml runtime: typescript ``` -------------------------------------------------------------------------------- /src/validation/projects/find-project-schema.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; export const FindProjectSchema = z.object({ workspaceId: z.string(), }); ``` -------------------------------------------------------------------------------- /src/validation/entries/delete-entry-schema.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; export const DeleteEntrySchema = z.object({ workspaceId: z.string(), timeEntryId: z.string(), }); ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "outDir": "./dist", "target": "ESNext", "module": "commonjs", "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "strict": true, "skipLibCheck": true } } ``` -------------------------------------------------------------------------------- /src/validation/entries/create-entry-schema.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; export const CreateEntrySchema = z.object({ workspaceId: z.string(), billable: z.boolean(), description: z.string(), start: z.coerce.date(), end: z.coerce.date(), projectId: z.string().optional(), }); ``` -------------------------------------------------------------------------------- /src/clockify-sdk/users.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from "axios"; import { api } from "../config/api"; function UsersService(api: AxiosInstance) { async function getCurrent() { return api.get("user"); } return { getCurrent }; } export const usersService = UsersService(api); ``` -------------------------------------------------------------------------------- /src/validation/entries/find-entry-schema.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; export const FindEntrySchema = z.object({ workspaceId: z.string(), userId: z.string(), description: z.string().optional(), start: z.coerce.date().optional(), end: z.coerce.date().optional(), project: z.string().optional(), }); ``` -------------------------------------------------------------------------------- /src/clockify-sdk/workspaces.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from "axios"; import { api } from "../config/api"; function WorkspacesService(api: AxiosInstance) { async function fetchAll() { return api.get(`workspaces`); } return { fetchAll }; } export const workspacesService = WorkspacesService(api); ``` -------------------------------------------------------------------------------- /src/clockify-sdk/projects.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from "axios"; import { api } from "../config/api"; function ProjectsService(api: AxiosInstance) { async function fetchAll(workspaceId: string) { return api.get(`workspaces/${workspaceId}/projects?archived=false`); } return { fetchAll }; } export const projectsService = ProjectsService(api); ``` -------------------------------------------------------------------------------- /src/validation/entries/edit-entry-schema.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; export const EditEntrySchema = z.object({ workspaceId: z.string(), timeEntryId: z.string(), billable: z.boolean().optional(), description: z.string().optional(), start: z.union([z.coerce.date(), z.undefined()]).optional(), end: z.union([z.coerce.date(), z.undefined()]).optional(), projectId: z.string().optional(), }); ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "clockify-mcp-server", "version": "1.1.1", "main": "index.js", "module": "./src/index.ts", "type": "module", "files": [ "dist" ], "scripts": { "dev": "npx @smithery/cli dev", "build": "npx @smithery/cli build" }, "license": "MIT", "engines": { "node": ">=20.0.0" }, "devDependencies": { "@smithery/cli": "^1.2.4", "@types/node": "^20.19.7", "ts-node-dev": "^2.0.0", "tsx": "^4.20.3", "typescript": "^5.8.3" }, "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", "axios": "^1.8.4", "dotenv": "^16.5.0", "zod": "^3.24.2" } } ``` -------------------------------------------------------------------------------- /test/users.test.ts: -------------------------------------------------------------------------------- ```typescript import { after, describe, it } from "node:test"; import { createMcpClient, TEST_WORKSPACE_ID, TEST_USER_ID } from "./setup"; import assert from "node:assert"; import { ClockifyUser, McpResponse } from "../src/types"; describe("Users MCP Tests", async () => { const client = await createMcpClient(); after(async () => { await client.close(); }); it("Retrieve current user info", async () => { const response = (await client.callTool({ name: "get-current-user", })) as McpResponse; const user: ClockifyUser = JSON.parse(response.content[0].text as string); assert(user.id === TEST_USER_ID); }); }); ``` -------------------------------------------------------------------------------- /test/setup.ts: -------------------------------------------------------------------------------- ```typescript import dotenv from "dotenv"; dotenv.config(); import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; export const TEST_WORKSPACE_ID = process.env.TEST_WORKSPACE_ID; export const TEST_USER_ID = process.env.TEST_USER_ID; export const TEST_PROJECT_ID = process.env.TEST_PROJECT_ID; export async function createMcpClient() { const transport = new StdioClientTransport({ command: "ts-node", args: ["src/index.ts"], }); const client = new Client({ name: "clockify-test-mcp-client", version: "1.1.1", }); await client.connect(transport); return client; } ``` -------------------------------------------------------------------------------- /src/tools/users.ts: -------------------------------------------------------------------------------- ```typescript import { TOOLS_CONFIG } from "../config/api"; import { usersService } from "../clockify-sdk/users"; import { ClockifyUser, McpResponse, McpToolConfigWithoutParameters, } from "../types"; export const getCurrentUserTool: McpToolConfigWithoutParameters = { name: TOOLS_CONFIG.users.current.name, description: TOOLS_CONFIG.users.current.description, handler: async (): Promise<McpResponse> => { const response = await usersService.getCurrent(); const user: ClockifyUser = { id: response.data.id, name: response.data.name, email: response.data.email, }; return { content: [ { type: "text", text: JSON.stringify(user), }, ], }; }, }; ``` -------------------------------------------------------------------------------- /src/tools/workspaces.ts: -------------------------------------------------------------------------------- ```typescript import { TOOLS_CONFIG } from "../config/api"; import { workspacesService } from "../clockify-sdk/workspaces"; import { ClockifyWorkspace, McpResponse, McpToolConfigWithoutParameters, } from "../types"; export const findWorkspacesTool: McpToolConfigWithoutParameters = { name: TOOLS_CONFIG.workspaces.list.name, description: TOOLS_CONFIG.workspaces.list.description, handler: async (): Promise<McpResponse> => { const response = await workspacesService.fetchAll(); const workspaces = response.data.map((workspace: ClockifyWorkspace) => ({ name: workspace.name, id: workspace.id, })); return { content: [ { type: "text", text: JSON.stringify(workspaces), }, ], }; }, }; ``` -------------------------------------------------------------------------------- /test/workspaces.test.ts: -------------------------------------------------------------------------------- ```typescript import { after, describe, it } from "node:test"; import assert from "node:assert"; import { createMcpClient, TEST_WORKSPACE_ID } from "./setup"; import { ClockifyWorkspace, McpResponse } from "../src/types"; describe("Workspaces MCP Tests", async () => { const client = await createMcpClient(); after(async () => { await client.close(); }); it("should list all user workspaces", async () => { const result = (await client.callTool({ name: "get-workspaces", })) as McpResponse; const workspaces: ClockifyWorkspace[] = JSON.parse( result.content[0].text as string ); assert(workspaces.length > 0); assert( workspaces.find( (workspace: ClockifyWorkspace) => workspace.id === TEST_WORKSPACE_ID ) ); }); }); ``` -------------------------------------------------------------------------------- /src/tools/projects.ts: -------------------------------------------------------------------------------- ```typescript import { projectsService } from "../clockify-sdk/projects"; import { TOOLS_CONFIG } from "../config/api"; import { z } from "zod"; import { McpResponse, McpToolConfig, TFindProjectSchema } from "../types"; export const findProjectTool: McpToolConfig = { name: TOOLS_CONFIG.projects.list.name, description: TOOLS_CONFIG.projects.list.description, parameters: { workspaceId: z .string() .describe( "The ID of the workspace that you need to get the projects from" ), }, handler: async ({ workspaceId, }: TFindProjectSchema): Promise<McpResponse> => { if (!workspaceId && typeof workspaceId === "string") throw new Error("Workspace ID required to fetch projects"); const response = await projectsService.fetchAll(workspaceId as string); const projects = response.data.map((project: any) => ({ name: project.name, clientName: project.clientName, id: project.id, })); return { content: [ { type: "text", text: JSON.stringify(projects), }, ], }; }, }; ``` -------------------------------------------------------------------------------- /src/config/api.ts: -------------------------------------------------------------------------------- ```typescript import axios from "axios"; export const api = axios.create({ baseURL: process.env.CLOCKIFY_API_URL || 'https://api.clockify.me/api/v1', headers: { "X-Api-Key": `${process.env.CLOCKIFY_API_TOKEN}`, }, }); export const SERVER_CONFIG = { name: "Clockify MCP Server", version: "1.0.0", description: "A service that integrates with Clockify API to manage time entries", }; export const TOOLS_CONFIG = { workspaces: { list: { name: "get-workspaces", description: "Get user available workspaces id and name, a workspace is required to manage time entries", }, }, projects: { list: { name: "get-projects", description: "Get workspace projects id and name, the projects can be associated with time entries", }, }, users: { current: { name: "get-current-user", description: "Get the current user id and name, to search for entries is required to have the user id", }, }, entries: { create: { name: "create-time-entry", description: "Register a new time entry of a task or break in a workspace", }, list: { name: "list-time-entries", description: "Get registered time entries from a workspace", }, delete: { name: "delete-time-entry", description: "Delete a specific time entry from a workspace", }, edit: { name: "edit-time-entry", description: "Edit an existing time entry in a workspace", }, }, }; ``` -------------------------------------------------------------------------------- /src/clockify-sdk/entries.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosInstance } from "axios"; import { api } from "../config/api"; import { TCreateEntrySchema, TFindEntrySchema, TDeleteEntrySchema, TEditEntrySchema, } from "../types"; import { URLSearchParams } from "node:url"; function EntriesService(api: AxiosInstance) { async function create(entry: TCreateEntrySchema) { const body = { ...entry, workspaceId: undefined, }; return api.post(`workspaces/${entry.workspaceId}/time-entries`, body); } async function find(filters: TFindEntrySchema) { const searchParams = new URLSearchParams(); if (filters.description) searchParams.append("description", filters.description); if (filters.start) searchParams.append("start", filters.start.toISOString()); if (filters.end) searchParams.append("end", filters.end.toISOString()); if (filters.project) searchParams.append("project", filters.project); return api.get( `https://api.clockify.me/api/v1/workspaces/${filters.workspaceId}/user/${ filters.userId }/time-entries?${searchParams.toString()}` ); } async function deleteEntry(params: TDeleteEntrySchema) { return api.delete( `workspaces/${params.workspaceId}/time-entries/${params.timeEntryId}` ); } async function update(params: TEditEntrySchema) { const body = { ...params, workspaceId: undefined, timeEntryId: undefined, }; return api.put( `workspaces/${params.workspaceId}/time-entries/${params.timeEntryId}`, body ); } async function getById(workspaceId: string, timeEntryId: string) { return api.get(`workspaces/${workspaceId}/time-entries/${timeEntryId}`); } return { create, find, deleteEntry, update, getById }; } export const entriesService = EntriesService(api); ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import dotenv from "dotenv"; dotenv.config(); import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { api, SERVER_CONFIG } from "./config/api"; import { createEntryTool, deleteEntryTool, editEntryTool, listEntriesTool, } from "./tools/entries"; import { findProjectTool } from "./tools/projects"; import { getCurrentUserTool } from "./tools/users"; import { findWorkspacesTool } from "./tools/workspaces"; import { z } from "zod"; import { argv } from "process"; export const configSchema = z.object({ clockifyApiToken: z.string().describe("Clockify API Token"), }); const server = new McpServer(SERVER_CONFIG); export default function createStatelessServer({ config, }: { config: z.infer<typeof configSchema>; }) { api.defaults.headers.Authorization = `Bearer ${config.clockifyApiToken}`; server.tool( createEntryTool.name, createEntryTool.description, createEntryTool.parameters, createEntryTool.handler ); server.tool( findProjectTool.name, findProjectTool.description, findProjectTool.parameters, findProjectTool.handler ); server.tool( listEntriesTool.name, listEntriesTool.description, listEntriesTool.parameters, listEntriesTool.handler ); server.tool( getCurrentUserTool.name, getCurrentUserTool.description, getCurrentUserTool.handler ); server.tool( findWorkspacesTool.name, findWorkspacesTool.description, findWorkspacesTool.handler ); server.tool( deleteEntryTool.name, deleteEntryTool.description, deleteEntryTool.parameters, deleteEntryTool.handler ); server.tool( editEntryTool.name, editEntryTool.description, editEntryTool.parameters, editEntryTool.handler ); return server.server; } (() => { if (argv.find((flag) => flag === "--local")) { createStatelessServer({ config: { clockifyApiToken: process.env.CLOCKIFY_API_TOKEN as string, }, }); const transport = new StdioServerTransport(); server.connect(transport); } })(); ``` -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; import { CreateEntrySchema } from "../validation/entries/create-entry-schema"; import { FindEntrySchema } from "../validation/entries/find-entry-schema"; import { DeleteEntrySchema } from "../validation/entries/delete-entry-schema"; import { EditEntrySchema } from "../validation/entries/edit-entry-schema"; import { ReadResourceTemplateCallback, ResourceMetadata, ResourceTemplate, } from "@modelcontextprotocol/sdk/server/mcp"; import { FindProjectSchema } from "../validation/projects/find-project-schema"; export type TCreateEntrySchema = z.infer<typeof CreateEntrySchema>; export type TFindEntrySchema = z.infer<typeof FindEntrySchema>; export type TDeleteEntrySchema = z.infer<typeof DeleteEntrySchema>; export type TEditEntrySchema = z.infer<typeof EditEntrySchema>; export type TFindProjectSchema = z.infer<typeof FindProjectSchema>; export interface ClockifyWorkspace { id: string; name: string; } export interface ClockifyUser { id: string; name: string; email: string; } export interface McpToolConfig { name: string; description: string; parameters: Record<string, any>; handler: (params: any) => Promise<McpResponse>; } export type McpToolConfigWithoutParameters = Omit<McpToolConfig, "parameters">; export interface McpTextContent { type: "text"; text: string; [key: string]: unknown; } export interface McpImageContent { type: "image"; data: string; mimeType: string; [key: string]: unknown; } export interface McpResourceConfig { name: string; template: ResourceTemplate; metadata: ResourceMetadata; handler: ReadResourceTemplateCallback; } export interface McpResourceContent { type: "resource"; resource: | { text: string; uri: string; mimeType?: string; [key: string]: unknown; } | { uri: string; blob: string; mimeType?: string; [key: string]: unknown; }; [key: string]: unknown; } export type McpContent = McpTextContent | McpImageContent | McpResourceContent; export interface McpResponse { content: McpContent[]; _meta?: Record<string, unknown>; isError?: boolean; [key: string]: unknown; } ``` -------------------------------------------------------------------------------- /test/entries.test.ts: -------------------------------------------------------------------------------- ```typescript import { after, describe, it } from "node:test"; import { createMcpClient, TEST_WORKSPACE_ID, TEST_PROJECT_ID } from "./setup"; import { McpResponse } from "../src/types"; import assert from "node:assert"; let createdEntryId: string; describe("Entries MCP Tests", async () => { const client = await createMcpClient(); after(async () => { await client.close(); }); it("Create a billable time entry without project", async () => { const dateOneHourBefore = new Date(); dateOneHourBefore.setHours(dateOneHourBefore.getHours() - 1); const currentDate = new Date(); const response = (await client.callTool({ name: "create-time-entry", arguments: { workspaceId: TEST_WORKSPACE_ID, billable: true, description: "MCP Test Entry", start: dateOneHourBefore, end: currentDate, }, })) as McpResponse; assert( (response.content[0].text as string).startsWith( "Time entry created successfully" ) ); }); it("Create a time entry with project", async () => { const dateTwoHoursBefore = new Date(); dateTwoHoursBefore.setHours(dateTwoHoursBefore.getHours() - 2); const dateOneHourBefore = new Date(); dateOneHourBefore.setHours(dateOneHourBefore.getHours() - 1); const response = (await client.callTool({ name: "create-time-entry", arguments: { workspaceId: TEST_WORKSPACE_ID, billable: false, description: "MCP Test Entry with Project", start: dateTwoHoursBefore, end: dateOneHourBefore, projectId: TEST_PROJECT_ID, }, })) as McpResponse; assert( (response.content[0].text as string).startsWith( "Time entry created successfully" ) ); const match = (response.content[0].text as string).match(/ID: ([^\s]+)/); if (match) { createdEntryId = match[1]; } }); it("Edit a time entry", async () => { if (!createdEntryId) { throw new Error("No entry ID available for editing"); } const response = (await client.callTool({ name: "edit-time-entry", arguments: { workspaceId: TEST_WORKSPACE_ID, timeEntryId: createdEntryId, description: "MCP Test Entry Edited", billable: false, }, })) as McpResponse; assert( (response.content[0].text as string).startsWith( "Time entry updated successfully" ) ); }); it("Delete a time entry", async () => { if (!createdEntryId) { throw new Error("No entry ID available for deletion"); } const response = (await client.callTool({ name: "delete-time-entry", arguments: { workspaceId: TEST_WORKSPACE_ID, timeEntryId: createdEntryId, }, })) as McpResponse; assert( (response.content[0].text as string).startsWith("Time entry with ID") ); assert( (response.content[0].text as string).includes("was deleted successfully") ); }); }); ``` -------------------------------------------------------------------------------- /src/tools/entries.ts: -------------------------------------------------------------------------------- ```typescript import { z } from "zod"; import { TOOLS_CONFIG } from "../config/api"; import { entriesService } from "../clockify-sdk/entries"; import { McpResponse, McpToolConfig, TCreateEntrySchema, TFindEntrySchema, TDeleteEntrySchema, TEditEntrySchema, } from "../types"; export const createEntryTool: McpToolConfig = { name: TOOLS_CONFIG.entries.create.name, description: TOOLS_CONFIG.entries.create.description, parameters: { workspaceId: z .string() .describe("The id of the workspace that gonna be saved the time entry"), billable: z .boolean() .describe("If the task is billable or not") .optional() .default(true), description: z.string().describe("The description of the time entry"), start: z.coerce.date().describe("The start of the time entry"), end: z.coerce.date().describe("The end of the time entry"), projectId: z .string() .optional() .describe("The id of the project associated with this time entry"), }, handler: async (params: TCreateEntrySchema): Promise<McpResponse> => { try { const result = await entriesService.create(params); const entryInfo = `Time entry created successfully. ID: ${result.data.id} Name: ${result.data.description}`; return { content: [ { type: "text", text: entryInfo, }, ], }; } catch (error: any) { throw new Error(`Failed to create entry: ${error.message}`); } }, }; export const listEntriesTool: McpToolConfig = { name: TOOLS_CONFIG.entries.list.name, description: TOOLS_CONFIG.entries.list.description, parameters: { workspaceId: z .string() .describe("The id of the workspace that gonna search for the entries"), userId: z .string() .describe( "The id of the user that gonna have the entries searched, default is the current user id" ), description: z .string() .optional() .describe("The time entry description to search for"), start: z.coerce .date() .optional() .describe("Start time of the entry to search for"), end: z.coerce .date() .optional() .describe("End time of the entry to search for"), project: z .string() .optional() .describe("The id of the project to search for entries"), }, handler: async (params: TFindEntrySchema) => { try { const result = await entriesService.find(params); const formmatedResults = result.data.map((entry: any) => ({ id: entry.id, description: entry.description, duration: entry.duration, start: entry.start, end: entry.end, })); return { content: [ { type: "text", text: JSON.stringify(formmatedResults), }, ], }; } catch (error: any) { throw new Error(`Failed to retrieve entries: ${error.message}`); } }, }; export const deleteEntryTool: McpToolConfig = { name: TOOLS_CONFIG.entries.delete.name, description: TOOLS_CONFIG.entries.delete.description, parameters: { workspaceId: z .string() .describe("The id of the workspace where the time entry is located"), timeEntryId: z.string().describe("The id of the time entry to be deleted"), }, handler: async (params: TDeleteEntrySchema): Promise<McpResponse> => { try { await entriesService.deleteEntry(params); return { content: [ { type: "text", text: `Time entry with ID ${params.timeEntryId} was deleted successfully.`, }, ], }; } catch (error: any) { throw new Error(`Failed to delete entry: ${error.message}`); } }, }; export const editEntryTool: McpToolConfig = { name: TOOLS_CONFIG.entries.edit.name, description: TOOLS_CONFIG.entries.edit.description, parameters: { workspaceId: z .string() .describe("The id of the workspace where the time entry is located"), timeEntryId: z.string().describe("The id of the time entry to be edited"), billable: z.boolean().describe("If the task is billable or not").optional(), description: z .string() .describe("The description of the time entry") .optional(), start: z.coerce.date().describe("The start of the time entry").optional(), end: z.coerce.date().describe("The end of the time entry").optional(), projectId: z .string() .optional() .describe("The id of the project associated with this time entry"), }, handler: async (params: TEditEntrySchema): Promise<McpResponse> => { try { let start = params.start; if (!start) { const current = await entriesService.getById( params.workspaceId, params.timeEntryId ); start = new Date(current.data.timeInterval.start); } const result = await entriesService.update({ ...params, start, }); const entryInfo = `Time entry updated successfully. ID: ${result.data.id} Name: ${result.data.description}`; return { content: [ { type: "text", text: entryInfo, }, ], }; } catch (error: any) { throw new Error(`Failed to edit entry: ${error.message}`); } }, }; ```