@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.
- package/LICENSE +21 -0
- package/README.md +105 -0
- package/SECURITY.md +20 -0
- package/config.example.json +4 -0
- package/dist/clockify/client.d.ts +53 -0
- package/dist/clockify/client.js +181 -0
- package/dist/clockify/client.js.map +1 -0
- package/dist/clockify/errors.d.ts +7 -0
- package/dist/clockify/errors.js +15 -0
- package/dist/clockify/errors.js.map +1 -0
- package/dist/clockify/types.d.ts +111 -0
- package/dist/clockify/types.js +2 -0
- package/dist/clockify/types.js.map +1 -0
- package/dist/config.d.ts +9 -0
- package/dist/config.js +49 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.js +63 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/clients.d.ts +2 -0
- package/dist/tools/clients.js +60 -0
- package/dist/tools/clients.js.map +1 -0
- package/dist/tools/coerce.d.ts +11 -0
- package/dist/tools/coerce.js +43 -0
- package/dist/tools/coerce.js.map +1 -0
- package/dist/tools/meta.d.ts +2 -0
- package/dist/tools/meta.js +21 -0
- package/dist/tools/meta.js.map +1 -0
- package/dist/tools/projects.d.ts +2 -0
- package/dist/tools/projects.js +101 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/shape.d.ts +92 -0
- package/dist/tools/shape.js +50 -0
- package/dist/tools/shape.js.map +1 -0
- package/dist/tools/tags.d.ts +2 -0
- package/dist/tools/tags.js +54 -0
- package/dist/tools/tags.js.map +1 -0
- package/dist/tools/tasks.d.ts +2 -0
- package/dist/tools/tasks.js +78 -0
- package/dist/tools/tasks.js.map +1 -0
- package/dist/tools/timeEntries.d.ts +2 -0
- package/dist/tools/timeEntries.js +161 -0
- package/dist/tools/timeEntries.js.map +1 -0
- 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,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 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/clockify/types.ts"],"names":[],"mappings":""}
|
package/dist/config.d.ts
ADDED
|
@@ -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"}
|
package/dist/index.d.ts
ADDED
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"}
|