#
tokens: 6677/50000 27/27 files
lines: off (toggle) GitHub
raw markdown copy
# 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

[![smithery badge](https://smithery.ai/badge/@https-eduardo/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}`);
    }
  },
};

```