@themkn/clockify-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/SECURITY.md +20 -0
  4. package/config.example.json +4 -0
  5. package/dist/clockify/client.d.ts +53 -0
  6. package/dist/clockify/client.js +181 -0
  7. package/dist/clockify/client.js.map +1 -0
  8. package/dist/clockify/errors.d.ts +7 -0
  9. package/dist/clockify/errors.js +15 -0
  10. package/dist/clockify/errors.js.map +1 -0
  11. package/dist/clockify/types.d.ts +111 -0
  12. package/dist/clockify/types.js +2 -0
  13. package/dist/clockify/types.js.map +1 -0
  14. package/dist/config.d.ts +9 -0
  15. package/dist/config.js +49 -0
  16. package/dist/config.js.map +1 -0
  17. package/dist/index.d.ts +2 -0
  18. package/dist/index.js +24 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/server.d.ts +24 -0
  21. package/dist/server.js +63 -0
  22. package/dist/server.js.map +1 -0
  23. package/dist/tools/clients.d.ts +2 -0
  24. package/dist/tools/clients.js +60 -0
  25. package/dist/tools/clients.js.map +1 -0
  26. package/dist/tools/coerce.d.ts +11 -0
  27. package/dist/tools/coerce.js +43 -0
  28. package/dist/tools/coerce.js.map +1 -0
  29. package/dist/tools/meta.d.ts +2 -0
  30. package/dist/tools/meta.js +21 -0
  31. package/dist/tools/meta.js.map +1 -0
  32. package/dist/tools/projects.d.ts +2 -0
  33. package/dist/tools/projects.js +101 -0
  34. package/dist/tools/projects.js.map +1 -0
  35. package/dist/tools/shape.d.ts +92 -0
  36. package/dist/tools/shape.js +50 -0
  37. package/dist/tools/shape.js.map +1 -0
  38. package/dist/tools/tags.d.ts +2 -0
  39. package/dist/tools/tags.js +54 -0
  40. package/dist/tools/tags.js.map +1 -0
  41. package/dist/tools/tasks.d.ts +2 -0
  42. package/dist/tools/tasks.js +78 -0
  43. package/dist/tools/tasks.js.map +1 -0
  44. package/dist/tools/timeEntries.d.ts +2 -0
  45. package/dist/tools/timeEntries.js +161 -0
  46. package/dist/tools/timeEntries.js.map +1 -0
  47. package/package.json +39 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 mkn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,105 @@
1
+ # clockify-mcp
2
+
3
+ An [MCP server](https://modelcontextprotocol.io) that lets Claude manage your
4
+ [Clockify](https://clockify.me) account: time entries (full CRUD plus
5
+ start/stop timer), projects, tasks, tags, and clients.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ npm install -g @themkn/clockify-mcp
11
+ ```
12
+
13
+ The binary is called `clockify-mcp` regardless of the scoped package name.
14
+
15
+ (Or point Claude's MCP config at the built JS locally — see below.)
16
+
17
+ ## Configure
18
+
19
+ The server reads a JSON config from `~/.clockify-mcp/config.json`. Create it
20
+ with permission `600`:
21
+
22
+ ```sh
23
+ mkdir -p ~/.clockify-mcp
24
+ chmod 700 ~/.clockify-mcp
25
+ cat > ~/.clockify-mcp/config.json <<'EOF'
26
+ {
27
+ "apiKey": "YOUR_CLOCKIFY_PERSONAL_API_KEY",
28
+ "workspaceId": "YOUR_WORKSPACE_ID"
29
+ }
30
+ EOF
31
+ chmod 600 ~/.clockify-mcp/config.json
32
+ ```
33
+
34
+ - **Get an API key:** in Clockify, open *Profile settings → API → Generate*.
35
+ - **Workspace id:** visible in the Clockify URL once you select a workspace.
36
+
37
+ The server will refuse to start if the config file is group- or world-readable.
38
+
39
+ ## Hook into Claude Code
40
+
41
+ Add an entry to your Claude MCP config (typically `~/.config/claude/mcp.json`
42
+ or the per-project `.claude/mcp.json`):
43
+
44
+ ```json
45
+ {
46
+ "mcpServers": {
47
+ "clockify": {
48
+ "command": "clockify-mcp"
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ## Tools
55
+
56
+ Grouped by resource:
57
+
58
+ | Resource | Tools |
59
+ | ------------ | ----- |
60
+ | Time entries | `list_time_entries`, `get_time_entry`, `create_time_entry`, `update_time_entry`, `delete_time_entry`, `start_timer`, `stop_timer`, `get_running_timer` |
61
+ | Projects | `list_projects`, `get_project`, `create_project`, `update_project`, `delete_project` |
62
+ | Tasks | `list_tasks`, `create_task`, `update_task`, `delete_task` |
63
+ | Tags | `list_tags`, `create_tag`, `update_tag`, `delete_tag` |
64
+ | Clients | `list_clients`, `create_client`, `update_client`, `delete_client` |
65
+ | Meta | `get_current_user`, `get_workspace` |
66
+
67
+ Notes:
68
+
69
+ - All timestamps are ISO 8601 (`YYYY-MM-DDTHH:mm:ssZ`). Claude interprets
70
+ relative phrases like "yesterday 9am" before calling the tool.
71
+ - Tools accept **IDs**, not names. Claude lists projects/tasks/tags/clients
72
+ first, picks the matching id, then acts.
73
+ - `delete_project` tries a hard delete; if Clockify refuses because the
74
+ project has time entries, the server archives it instead. The response
75
+ reports `{ "action": "deleted" }` or `{ "action": "archived" }`.
76
+
77
+ ## Security
78
+
79
+ - The API key in `~/.clockify-mcp/config.json` grants **full access** to your
80
+ Clockify account. This server cannot narrow that scope — Clockify's API does
81
+ not offer read-only personal tokens. Rotate the key if you suspect
82
+ exposure.
83
+ - The key is never logged or returned in any tool response. Errors are
84
+ scrubbed defensively before being surfaced.
85
+ - The only network destination is `https://api.clockify.me`; there is no
86
+ telemetry.
87
+ - Run the server as your user — never via `sudo`.
88
+
89
+ Report vulnerabilities per `SECURITY.md`.
90
+
91
+ ## Development
92
+
93
+ ```sh
94
+ npm install
95
+ npm run typecheck
96
+ npm test
97
+ npm run build
98
+ ```
99
+
100
+ Optional: add `npm run test:live` later for integration tests against a real
101
+ workspace (not run in CI).
102
+
103
+ ## License
104
+
105
+ MIT — see `LICENSE`.
package/SECURITY.md ADDED
@@ -0,0 +1,20 @@
1
+ # Security Policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Please report security issues privately through GitHub's
6
+ [private vulnerability reporting](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
7
+ on this repository: open the **Security** tab and click **Report a vulnerability**.
8
+ Do not open a public GitHub issue for suspected vulnerabilities.
9
+
10
+ We aim to respond within 72 hours and to publish a fix for confirmed issues
11
+ within 14 days.
12
+
13
+ ## Scope
14
+
15
+ This server handles a Clockify personal API key with full account access.
16
+ Issues of particular interest:
17
+ - Leaks of the API key in logs, errors, or tool responses
18
+ - Bypasses of the config-file permission check
19
+ - Any code path that calls out to a destination other than `api.clockify.me`
20
+ - Input-validation bypasses in tool handlers
@@ -0,0 +1,4 @@
1
+ {
2
+ "apiKey": "REPLACE_WITH_YOUR_CLOCKIFY_API_KEY",
3
+ "workspaceId": "REPLACE_WITH_YOUR_WORKSPACE_ID"
4
+ }
@@ -0,0 +1,53 @@
1
+ import type { ClockifyUser, CreateClientBody, CreateProjectBody, CreateTagBody, CreateTaskBody, CreateTimeEntryBody, ListProjectsQuery, ListTimeEntriesQuery, RawClient, RawProject, RawTag, RawTask, RawTimeEntry, UpdateClientBody, UpdateProjectBody, UpdateTagBody, UpdateTaskBody, UpdateTimeEntryBody } from "./types.js";
2
+ export interface RequestOptions {
3
+ method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
4
+ query?: Record<string, string | number | boolean | undefined>;
5
+ body?: unknown;
6
+ }
7
+ export declare class ClockifyClient {
8
+ private readonly apiKey;
9
+ constructor(apiKey: string);
10
+ getCurrentUser(): Promise<ClockifyUser>;
11
+ listTimeEntries(workspaceId: string, userId: string, q?: ListTimeEntriesQuery): Promise<RawTimeEntry[]>;
12
+ getTimeEntry(workspaceId: string, id: string): Promise<RawTimeEntry>;
13
+ createTimeEntry(workspaceId: string, body: CreateTimeEntryBody): Promise<RawTimeEntry>;
14
+ updateTimeEntry(workspaceId: string, id: string, body: UpdateTimeEntryBody): Promise<RawTimeEntry>;
15
+ deleteTimeEntry(workspaceId: string, id: string): Promise<void>;
16
+ listProjects(workspaceId: string, q?: ListProjectsQuery): Promise<RawProject[]>;
17
+ getProject(workspaceId: string, id: string): Promise<RawProject>;
18
+ createProject(workspaceId: string, body: CreateProjectBody): Promise<RawProject>;
19
+ updateProject(workspaceId: string, id: string, body: UpdateProjectBody): Promise<RawProject>;
20
+ deleteProject(workspaceId: string, id: string): Promise<void>;
21
+ archiveProject(workspaceId: string, id: string): Promise<RawProject>;
22
+ listTasks(workspaceId: string, projectId: string, q?: {
23
+ name?: string;
24
+ page?: number;
25
+ pageSize?: number;
26
+ }): Promise<RawTask[]>;
27
+ createTask(workspaceId: string, projectId: string, body: CreateTaskBody): Promise<RawTask>;
28
+ updateTask(workspaceId: string, projectId: string, id: string, body: UpdateTaskBody): Promise<RawTask>;
29
+ deleteTask(workspaceId: string, projectId: string, id: string): Promise<void>;
30
+ listTags(workspaceId: string, q?: {
31
+ name?: string;
32
+ archived?: boolean;
33
+ page?: number;
34
+ pageSize?: number;
35
+ }): Promise<RawTag[]>;
36
+ createTag(workspaceId: string, body: CreateTagBody): Promise<RawTag>;
37
+ updateTag(workspaceId: string, id: string, body: UpdateTagBody): Promise<RawTag>;
38
+ deleteTag(workspaceId: string, id: string): Promise<void>;
39
+ listClients(workspaceId: string, q?: {
40
+ name?: string;
41
+ archived?: boolean;
42
+ page?: number;
43
+ pageSize?: number;
44
+ }): Promise<RawClient[]>;
45
+ createClient(workspaceId: string, body: CreateClientBody): Promise<RawClient>;
46
+ updateClient(workspaceId: string, id: string, body: UpdateClientBody): Promise<RawClient>;
47
+ deleteClient(workspaceId: string, id: string): Promise<void>;
48
+ stopRunningTimer(workspaceId: string, userId: string, end: string): Promise<RawTimeEntry>;
49
+ request<T>(path: string, opts?: RequestOptions): Promise<T>;
50
+ private buildUrl;
51
+ private parseError;
52
+ private wrapNetworkError;
53
+ }
@@ -0,0 +1,181 @@
1
+ import { ClockifyError } from "./errors.js";
2
+ const BASE_URL = "https://api.clockify.me/api/v1";
3
+ export class ClockifyClient {
4
+ apiKey;
5
+ constructor(apiKey) {
6
+ this.apiKey = apiKey;
7
+ if (!apiKey)
8
+ throw new Error("ClockifyClient requires an API key");
9
+ }
10
+ async getCurrentUser() {
11
+ return this.request("/user");
12
+ }
13
+ async listTimeEntries(workspaceId, userId, q = {}) {
14
+ return this.request(`/workspaces/${encode(workspaceId)}/user/${encode(userId)}/time-entries`, {
15
+ query: {
16
+ start: q.start,
17
+ end: q.end,
18
+ project: q.project,
19
+ description: q.description,
20
+ "in-progress": q.inProgress,
21
+ page: q.page,
22
+ "page-size": q.pageSize,
23
+ hydrated: true,
24
+ },
25
+ });
26
+ }
27
+ async getTimeEntry(workspaceId, id) {
28
+ return this.request(`/workspaces/${encode(workspaceId)}/time-entries/${encode(id)}`, { query: { hydrated: true } });
29
+ }
30
+ async createTimeEntry(workspaceId, body) {
31
+ return this.request(`/workspaces/${encode(workspaceId)}/time-entries`, { method: "POST", body });
32
+ }
33
+ async updateTimeEntry(workspaceId, id, body) {
34
+ return this.request(`/workspaces/${encode(workspaceId)}/time-entries/${encode(id)}`, { method: "PUT", body });
35
+ }
36
+ async deleteTimeEntry(workspaceId, id) {
37
+ await this.request(`/workspaces/${encode(workspaceId)}/time-entries/${encode(id)}`, { method: "DELETE" });
38
+ }
39
+ async listProjects(workspaceId, q = {}) {
40
+ return this.request(`/workspaces/${encode(workspaceId)}/projects`, {
41
+ query: {
42
+ name: q.name,
43
+ clients: q.clientIds?.join(","),
44
+ archived: q.archived,
45
+ page: q.page,
46
+ "page-size": q.pageSize,
47
+ },
48
+ });
49
+ }
50
+ async getProject(workspaceId, id) {
51
+ return this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(id)}`);
52
+ }
53
+ async createProject(workspaceId, body) {
54
+ return this.request(`/workspaces/${encode(workspaceId)}/projects`, { method: "POST", body });
55
+ }
56
+ async updateProject(workspaceId, id, body) {
57
+ return this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(id)}`, { method: "PUT", body });
58
+ }
59
+ async deleteProject(workspaceId, id) {
60
+ await this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(id)}`, { method: "DELETE" });
61
+ }
62
+ async archiveProject(workspaceId, id) {
63
+ return this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(id)}`, { method: "PATCH", body: { archived: true } });
64
+ }
65
+ async listTasks(workspaceId, projectId, q = {}) {
66
+ return this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(projectId)}/tasks`, { query: { name: q.name, page: q.page, "page-size": q.pageSize } });
67
+ }
68
+ async createTask(workspaceId, projectId, body) {
69
+ return this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(projectId)}/tasks`, { method: "POST", body });
70
+ }
71
+ async updateTask(workspaceId, projectId, id, body) {
72
+ return this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(projectId)}/tasks/${encode(id)}`, { method: "PUT", body });
73
+ }
74
+ async deleteTask(workspaceId, projectId, id) {
75
+ await this.request(`/workspaces/${encode(workspaceId)}/projects/${encode(projectId)}/tasks/${encode(id)}`, { method: "DELETE" });
76
+ }
77
+ async listTags(workspaceId, q = {}) {
78
+ return this.request(`/workspaces/${encode(workspaceId)}/tags`, { query: { name: q.name, archived: q.archived, page: q.page, "page-size": q.pageSize } });
79
+ }
80
+ async createTag(workspaceId, body) {
81
+ return this.request(`/workspaces/${encode(workspaceId)}/tags`, { method: "POST", body });
82
+ }
83
+ async updateTag(workspaceId, id, body) {
84
+ return this.request(`/workspaces/${encode(workspaceId)}/tags/${encode(id)}`, { method: "PUT", body });
85
+ }
86
+ async deleteTag(workspaceId, id) {
87
+ await this.request(`/workspaces/${encode(workspaceId)}/tags/${encode(id)}`, { method: "DELETE" });
88
+ }
89
+ async listClients(workspaceId, q = {}) {
90
+ return this.request(`/workspaces/${encode(workspaceId)}/clients`, { query: { name: q.name, archived: q.archived, page: q.page, "page-size": q.pageSize } });
91
+ }
92
+ async createClient(workspaceId, body) {
93
+ return this.request(`/workspaces/${encode(workspaceId)}/clients`, { method: "POST", body });
94
+ }
95
+ async updateClient(workspaceId, id, body) {
96
+ return this.request(`/workspaces/${encode(workspaceId)}/clients/${encode(id)}`, { method: "PUT", body });
97
+ }
98
+ async deleteClient(workspaceId, id) {
99
+ await this.request(`/workspaces/${encode(workspaceId)}/clients/${encode(id)}`, { method: "DELETE" });
100
+ }
101
+ async stopRunningTimer(workspaceId, userId, end) {
102
+ return this.request(`/workspaces/${encode(workspaceId)}/user/${encode(userId)}/time-entries`, { method: "PATCH", body: { end } });
103
+ }
104
+ async request(path, opts = {}) {
105
+ const url = this.buildUrl(path, opts.query);
106
+ const init = {
107
+ method: opts.method ?? "GET",
108
+ headers: {
109
+ "X-Api-Key": this.apiKey,
110
+ "Content-Type": "application/json",
111
+ Accept: "application/json",
112
+ },
113
+ body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
114
+ };
115
+ let res;
116
+ try {
117
+ res = await fetch(url, init);
118
+ }
119
+ catch (err) {
120
+ throw this.wrapNetworkError(err);
121
+ }
122
+ if (!res.ok) {
123
+ throw await this.parseError(res);
124
+ }
125
+ if (res.status === 204 || res.headers.get("content-length") === "0") {
126
+ return undefined;
127
+ }
128
+ const ctype = res.headers.get("content-type") ?? "";
129
+ if (ctype.includes("application/json")) {
130
+ return (await res.json());
131
+ }
132
+ return undefined;
133
+ }
134
+ buildUrl(path, query) {
135
+ const u = new URL(BASE_URL + path);
136
+ if (query) {
137
+ for (const [k, v] of Object.entries(query)) {
138
+ if (v === undefined)
139
+ continue;
140
+ u.searchParams.set(k, String(v));
141
+ }
142
+ }
143
+ return u.toString();
144
+ }
145
+ async parseError(res) {
146
+ let body;
147
+ try {
148
+ body = await res.json();
149
+ }
150
+ catch {
151
+ body = undefined;
152
+ }
153
+ if (isClockifyErrorBody(body)) {
154
+ return new ClockifyError(res.status, String(body.code), sanitize(body.message, this.apiKey));
155
+ }
156
+ return new ClockifyError(res.status, undefined, `HTTP ${res.status} ${res.statusText}`.trim());
157
+ }
158
+ wrapNetworkError(err) {
159
+ const raw = err instanceof Error ? err.message : String(err);
160
+ return new ClockifyError(0, undefined, `network error: ${sanitize(raw, this.apiKey)}`);
161
+ }
162
+ }
163
+ function isClockifyErrorBody(body) {
164
+ return (typeof body === "object" &&
165
+ body !== null &&
166
+ "message" in body &&
167
+ typeof body.message === "string");
168
+ }
169
+ /** Defensive scrubber: strip the API key if it ever appears in a message. */
170
+ function sanitize(message, apiKey) {
171
+ if (!apiKey)
172
+ return message;
173
+ return message.split(apiKey).join("[redacted]");
174
+ }
175
+ function encode(segment) {
176
+ if (!segment || segment.includes("/") || segment.includes("?") || segment.includes("#")) {
177
+ throw new Error(`Invalid path segment: ${JSON.stringify(segment)}`);
178
+ }
179
+ return encodeURIComponent(segment);
180
+ }
181
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/clockify/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ export declare class ClockifyError extends Error {
2
+ readonly status: number;
3
+ readonly code: string | undefined;
4
+ constructor(status: number, code: string | undefined, message: string);
5
+ /** Human-facing one-liner used in MCP tool error responses. */
6
+ toUserMessage(): string;
7
+ }
@@ -0,0 +1,15 @@
1
+ export class ClockifyError extends Error {
2
+ status;
3
+ code;
4
+ constructor(status, code, message) {
5
+ super(message);
6
+ this.status = status;
7
+ this.code = code;
8
+ this.name = "ClockifyError";
9
+ }
10
+ /** Human-facing one-liner used in MCP tool error responses. */
11
+ toUserMessage() {
12
+ return `Clockify: ${this.message} (HTTP ${this.status})`;
13
+ }
14
+ }
15
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/clockify/errors.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,aAAc,SAAQ,KAAK;IAEpB;IACA;IAFlB,YACkB,MAAc,EACd,IAAwB,EACxC,OAAe;QAEf,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,WAAM,GAAN,MAAM,CAAQ;QACd,SAAI,GAAJ,IAAI,CAAoB;QAIxC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;IAED,+DAA+D;IAC/D,aAAa;QACX,OAAO,aAAa,IAAI,CAAC,OAAO,UAAU,IAAI,CAAC,MAAM,GAAG,CAAC;IAC3D,CAAC;CACF"}
@@ -0,0 +1,111 @@
1
+ export interface ClockifyUser {
2
+ id: string;
3
+ email: string;
4
+ name: string;
5
+ activeWorkspace: string;
6
+ defaultWorkspace: string;
7
+ }
8
+ export interface RawTimeEntry {
9
+ id: string;
10
+ description?: string;
11
+ timeInterval?: {
12
+ start?: string;
13
+ end?: string | null;
14
+ duration?: string | null;
15
+ };
16
+ projectId?: string | null;
17
+ project?: {
18
+ id?: string;
19
+ name?: string;
20
+ } | null;
21
+ taskId?: string | null;
22
+ tagIds?: string[] | null;
23
+ billable?: boolean;
24
+ }
25
+ export interface CreateTimeEntryBody {
26
+ start: string;
27
+ end?: string;
28
+ description?: string;
29
+ projectId?: string;
30
+ taskId?: string;
31
+ tagIds?: string[];
32
+ billable?: boolean;
33
+ }
34
+ export type UpdateTimeEntryBody = Partial<CreateTimeEntryBody>;
35
+ export interface ListTimeEntriesQuery {
36
+ start?: string;
37
+ end?: string;
38
+ project?: string;
39
+ description?: string;
40
+ inProgress?: boolean;
41
+ page?: number;
42
+ pageSize?: number;
43
+ }
44
+ export interface RawProject {
45
+ id: string;
46
+ name: string;
47
+ clientId?: string | null;
48
+ clientName?: string | null;
49
+ archived?: boolean;
50
+ billable?: boolean;
51
+ color?: string | null;
52
+ note?: string | null;
53
+ }
54
+ export interface CreateProjectBody {
55
+ name: string;
56
+ clientId?: string;
57
+ color?: string;
58
+ billable?: boolean;
59
+ isPublic?: boolean;
60
+ note?: string;
61
+ }
62
+ export type UpdateProjectBody = Partial<CreateProjectBody>;
63
+ export interface ListProjectsQuery {
64
+ name?: string;
65
+ clientIds?: string[];
66
+ archived?: boolean;
67
+ page?: number;
68
+ pageSize?: number;
69
+ }
70
+ export interface RawTask {
71
+ id: string;
72
+ name: string;
73
+ projectId: string;
74
+ status?: string;
75
+ assigneeIds?: string[];
76
+ estimate?: string | null;
77
+ }
78
+ export interface CreateTaskBody {
79
+ name: string;
80
+ assigneeIds?: string[];
81
+ estimate?: string;
82
+ status?: "ACTIVE" | "DONE";
83
+ }
84
+ export type UpdateTaskBody = Partial<CreateTaskBody>;
85
+ export interface RawTag {
86
+ id: string;
87
+ name: string;
88
+ archived?: boolean;
89
+ }
90
+ export interface CreateTagBody {
91
+ name: string;
92
+ }
93
+ export type UpdateTagBody = Partial<{
94
+ name: string;
95
+ archived: boolean;
96
+ }>;
97
+ export interface RawClient {
98
+ id: string;
99
+ name: string;
100
+ archived?: boolean;
101
+ note?: string | null;
102
+ }
103
+ export interface CreateClientBody {
104
+ name: string;
105
+ note?: string;
106
+ }
107
+ export type UpdateClientBody = Partial<{
108
+ name: string;
109
+ note: string;
110
+ archived: boolean;
111
+ }>;
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/clockify/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export declare const DEFAULT_CONFIG_PATH: string;
3
+ declare const ConfigSchema: z.ZodObject<{
4
+ apiKey: z.ZodString;
5
+ workspaceId: z.ZodString;
6
+ }, z.core.$strip>;
7
+ export type Config = z.infer<typeof ConfigSchema>;
8
+ export declare function loadConfig(path?: string): Config;
9
+ export {};
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ import { readFileSync, statSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { z } from "zod";
5
+ export const DEFAULT_CONFIG_PATH = join(homedir(), ".clockify-mcp", "config.json");
6
+ const ConfigSchema = z.object({
7
+ apiKey: z.string().min(1, "apiKey must be a non-empty string"),
8
+ workspaceId: z.string().min(1, "workspaceId must be a non-empty string"),
9
+ });
10
+ export function loadConfig(path = DEFAULT_CONFIG_PATH) {
11
+ let stat;
12
+ try {
13
+ stat = statSync(path);
14
+ }
15
+ catch (err) {
16
+ if (err.code === "ENOENT") {
17
+ throw new Error(`Config not found at ${path}. Create it with mode 0600, e.g.\n` +
18
+ ` mkdir -p ~/.clockify-mcp && chmod 700 ~/.clockify-mcp\n` +
19
+ ` echo '{"apiKey":"...","workspaceId":"..."}' > ${path}\n` +
20
+ ` chmod 600 ${path}`);
21
+ }
22
+ throw err;
23
+ }
24
+ if ((stat.mode & 0o077) !== 0) {
25
+ const current = (stat.mode & 0o777).toString(8);
26
+ throw new Error(`Config file ${path} has insecure permission ${current}. Run: chmod 600 ${path}`);
27
+ }
28
+ let raw;
29
+ try {
30
+ raw = readFileSync(path, "utf8");
31
+ }
32
+ catch (err) {
33
+ throw new Error(`Unable to read ${path}: ${err.message}`);
34
+ }
35
+ let parsed;
36
+ try {
37
+ parsed = JSON.parse(raw);
38
+ }
39
+ catch (err) {
40
+ throw new Error(`Failed to parse ${path}: ${err.message}`);
41
+ }
42
+ const result = ConfigSchema.safeParse(parsed);
43
+ if (!result.success) {
44
+ const msg = result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
45
+ throw new Error(`Invalid config (${path}): ${msg}`);
46
+ }
47
+ return result.data;
48
+ }
49
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,mBAAmB,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,eAAe,EAAE,aAAa,CAAC,CAAC;AAEnF,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5B,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,mCAAmC,CAAC;IAC9D,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,wCAAwC,CAAC;CACzE,CAAC,CAAC;AAIH,MAAM,UAAU,UAAU,CAAC,OAAe,mBAAmB;IAC3D,IAAI,IAAI,CAAC;IACT,IAAI,CAAC;QACH,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IACxB,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CACb,uBAAuB,IAAI,oCAAoC;gBAC7D,2DAA2D;gBAC3D,mDAAmD,IAAI,IAAI;gBAC3D,eAAe,IAAI,EAAE,CACxB,CAAC;QACJ,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;QAChD,MAAM,IAAI,KAAK,CACb,eAAe,IAAI,4BAA4B,OAAO,oBAAoB,IAAI,EAAE,CACjF,CAAC;IACJ,CAAC;IAED,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,kBAAkB,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACvE,CAAC;IAED,IAAI,MAAe,CAAC;IACpB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;IACxE,CAAC;IAED,MAAM,MAAM,GAAG,YAAY,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAC9C,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3F,MAAM,IAAI,KAAK,CAAC,mBAAmB,IAAI,MAAM,GAAG,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { loadConfig } from "./config.js";
3
+ import { ClockifyClient } from "./clockify/client.js";
4
+ import { runServer } from "./server.js";
5
+ import { clientTools } from "./tools/clients.js";
6
+ import { metaTools } from "./tools/meta.js";
7
+ import { projectTools } from "./tools/projects.js";
8
+ import { tagTools } from "./tools/tags.js";
9
+ import { taskTools } from "./tools/tasks.js";
10
+ import { timeEntryTools } from "./tools/timeEntries.js";
11
+ async function main() {
12
+ const config = loadConfig();
13
+ const client = new ClockifyClient(config.apiKey);
14
+ const user = await client.getCurrentUser();
15
+ const tools = [
16
+ ...metaTools, ...timeEntryTools, ...projectTools, ...taskTools, ...tagTools, ...clientTools,
17
+ ];
18
+ await runServer({ config, user, client, tools });
19
+ }
20
+ main().catch((err) => {
21
+ process.stderr.write(`clockify-mcp: ${err.message}\n`);
22
+ process.exit(1);
23
+ });
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,SAAS,EAAuB,MAAM,aAAa,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAC3C,OAAO,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACjD,MAAM,IAAI,GAAG,MAAM,MAAM,CAAC,cAAc,EAAE,CAAC;IAE3C,MAAM,KAAK,GAA8B;QACvC,GAAG,SAAS,EAAE,GAAG,cAAc,EAAE,GAAG,YAAY,EAAE,GAAG,SAAS,EAAE,GAAG,QAAQ,EAAE,GAAG,WAAW;KAC5F,CAAC;IAEF,MAAM,SAAS,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;AACnD,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAU,EAAE,EAAE;IAC1B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,iBAAiB,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}