clokio-mcp 0.20.0 → 0.21.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/README.md CHANGED
@@ -64,7 +64,7 @@ acting. An explicit code on a tool call always takes precedence.
64
64
  Optional: set `CLOKIO_TOOLSETS` to register only the tool groups you need, e.g.
65
65
  `CLOKIO_TOOLSETS=tasks`. This cuts the per-session tool-list cost - a
66
66
  task-focused session drops from 98 tools to 32. Optional groups are `tasks`,
67
- `employees`, `attendance` (which includes leave and time entries), `docs` and
67
+ `employees`, `attendance` (which includes leave, time entries and logging time), `docs` and
68
68
  `tickets`;
69
69
  combine them with commas. If your key cannot open documents (no acts-as
70
70
  employee and no explicit docs scopes), leaving `docs` out saves 33 tools it
@@ -74,7 +74,7 @@ through it.
74
74
 
75
75
  ## Tools
76
76
 
77
- 98 tools across seven areas. Read-only tools carry the MCP `readOnlyHint`; writes
77
+ 102 tools across seven areas. Read-only tools carry the MCP `readOnlyHint`; writes
78
78
  are marked mutating (deletes are `destructive`).
79
79
 
80
80
  | Area | Tools |
@@ -83,7 +83,7 @@ are marked mutating (deletes are `destructive`).
83
83
  | **Projects & clients** | list projects, project statuses, project custom fields, project contacts, get client, add client contact |
84
84
  | **Reference** | task statuses, task labels, locations, list webhooks (read-only), whoami |
85
85
  | **Employees** | list (name search), lookup by PIN, task-stats, inbox (mentions, new assignments, others' status changes), create, set status |
86
- | **Attendance** | daily, range, per-employee, monthly summary, clock in/out, break start/end, time entries, leaves, leave balances |
86
+ | **Attendance** | daily, range, per-employee, monthly summary, clock in/out, break start/end, time entries, leaves, leave balances, **log time on a task**, start / stop / read the timer (see [Logging time](#logging-time)) |
87
87
  | **Documents** (see [Documents](#documents) for who a key acts as) | list, tree, search, get (Markdown / HTML / JSON), create, replace content, update meta, move, archive, restore, delete, templates + create from template, members (by `employee_code`) and teams (list/add/update/remove, shareable teams), public share link, comments (list/add/resolve/reopen), versions (list/get/restore), attachments (list/upload/download) |
88
88
  | **Tickets** (see [Tickets](#tickets)) | list (filters + cursor), get (with its newest messages; internal notes labelled, everything the customer wrote wrapped as untrusted), more messages, **reply to the customer** (fresh `Idempotency-Key` per call, `destructive`), internal note (with mentions), update state / assignee, add / remove one label, contacts list / get / update, inboxes, ticket labels, saved replies |
89
89
 
@@ -117,6 +117,28 @@ are marked mutating (deletes are `destructive`).
117
117
  returns every occurrence of one series.
118
118
  - An API key sees **public custom fields only**.
119
119
 
120
+ ## Logging time
121
+
122
+ `clokio_log_time`, `clokio_start_timer`, `clokio_stop_timer` and
123
+ `clokio_get_active_timer` (0.21.0) call `/api/v1/time-entries` and
124
+ `/api/v1/timer/*`. The time is **always the key's acting person's** (its
125
+ "acts as" employee, else the person who issued it); no tool takes an
126
+ employee and the server refuses one.
127
+
128
+ - **Scopes:** `time_entries:write` for the three writes, `time_entries:read`
129
+ for the running timer. `time_entries:write` must be ticked on the key
130
+ explicitly: full access (`*`) never grants it.
131
+ - `clokio_log_time` takes `task_id` and EITHER `date` + `start` + `end`,
132
+ `date` + `start` + `duration_minutes`, or `duration_minutes` alone (the
133
+ entry then ends now). Times are local to that person. At most 12 hours per
134
+ entry; nothing may start or end more than 10 minutes in the future; an
135
+ overlap with another time entry of the same person (their running timer and
136
+ an overnight entry from the day before included) is refused.
137
+ - `clokio_start_timer` is refused while a timer runs; `force: true` stops it
138
+ and starts the new one. `clokio_stop_timer` is refused when nothing runs.
139
+ - The task must be visible to that person, on an active project of your
140
+ organisation; a board another organisation shared with you is refused.
141
+
120
142
  ## Documents
121
143
 
122
144
  The Docs tools call `/api/v1/docs...` with the same `CLOKIO_API_KEY` as
@@ -183,6 +183,89 @@ export function registerAttendanceTools(server, config) {
183
183
  },
184
184
  }),
185
185
  });
186
+ // Logging time (backend task #5775). The time is ALWAYS the key's acting
187
+ // person's: none of these tools takes an employee, and the server refuses
188
+ // one in the body. Scope time_entries:write is explicit-only ("*" never
189
+ // grants it).
190
+ const actingPerson = "The time is ALWAYS recorded for the API key's ACTING person (its \"acts as\" employee, else the person who issued it), " +
191
+ 'never for anyone else; there is no way to log time for another employee. ' +
192
+ 'Needs the time_entries:write scope, ticked explicitly on the key: full access ("*") does NOT grant it.';
193
+ const taskRule = 'The task must be one that person can view in your organisation, on an active project; a board another organisation ' +
194
+ 'shared with you is refused (404).';
195
+ registerTool(server, config, {
196
+ name: 'clokio_log_time',
197
+ description: 'Log a finished time entry on a task. ' + actingPerson + ' ' +
198
+ 'Times are local to that person (their location timezone, else UTC). Give EITHER date + start + end ' +
199
+ '(an end at or before start runs past midnight), OR date + start + duration_minutes, OR duration_minutes alone ' +
200
+ '(the entry then ENDS NOW and started that many minutes ago; date must be today or omitted). ' +
201
+ 'At most 12 hours (720 minutes) per entry; nothing may start or end more than 10 minutes in the future; ' +
202
+ 'an entry overlapping another time entry of the same person (including their running timer, and an overnight entry from the day before) is refused. ' +
203
+ 'A duration counts real minutes across a DST change. ' + taskRule + ' ' +
204
+ "The project's billable flag is inherited and the description defaults to the task title.",
205
+ schema: {
206
+ task_id: z.number().int().positive(),
207
+ date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/, 'date must be YYYY-MM-DD').optional()
208
+ .describe('YYYY-MM-DD, the day the entry STARTS. Required with start'),
209
+ start: z.string().regex(/^\d{2}:\d{2}$/, 'start must be HH:MM').optional().describe('HH:MM, local time'),
210
+ end: z.string().regex(/^\d{2}:\d{2}$/, 'end must be HH:MM').optional()
211
+ .describe('HH:MM, local time. Give end OR duration_minutes, not both'),
212
+ duration_minutes: z.number().int().min(1).max(720).optional(),
213
+ description: z.string().max(255).optional(),
214
+ },
215
+ mutates: true,
216
+ handler: (args, cfg) => {
217
+ if ((args.end === undefined) === (args.duration_minutes === undefined)) {
218
+ throw new Error('Give exactly one of end or duration_minutes.');
219
+ }
220
+ if (args.start !== undefined && args.date === undefined) {
221
+ throw new Error('date is required with start.');
222
+ }
223
+ if (args.end !== undefined && args.start === undefined) {
224
+ throw new Error('start is required with end.');
225
+ }
226
+ return request(cfg, '/time-entries', {
227
+ method: 'POST',
228
+ body: {
229
+ task_id: args.task_id,
230
+ date: args.date,
231
+ start: args.start,
232
+ end: args.end,
233
+ duration_minutes: args.duration_minutes,
234
+ description: args.description,
235
+ },
236
+ });
237
+ },
238
+ });
239
+ registerTool(server, config, {
240
+ name: 'clokio_start_timer',
241
+ description: "Start a running timer on a task. " + actingPerson + ' ' + taskRule + ' ' +
242
+ 'If a timer is already running the call is refused (409) and the running entry is returned; ' +
243
+ 'pass force: true to stop that timer and start this one. The description defaults to the task title.',
244
+ schema: {
245
+ task_id: z.number().int().positive(),
246
+ description: z.string().max(255).optional(),
247
+ force: z.boolean().optional().describe('Stop a running timer first instead of being refused'),
248
+ },
249
+ mutates: true,
250
+ handler: (args, cfg) => request(cfg, '/timer/start', {
251
+ method: 'POST',
252
+ body: { task_id: args.task_id, description: args.description, force: args.force },
253
+ }),
254
+ });
255
+ registerTool(server, config, {
256
+ name: 'clokio_stop_timer',
257
+ description: "Stop the running timer of the API key's ACTING person (never anyone else's) and return the entry with its hours. " +
258
+ 'Refused (409) when nothing is running. Needs time_entries:write (not granted by "*").',
259
+ schema: {},
260
+ mutates: true,
261
+ handler: (_args, cfg) => request(cfg, '/timer/stop', { method: 'POST' }),
262
+ });
263
+ registerTool(server, config, {
264
+ name: 'clokio_get_active_timer',
265
+ description: "The running timer of the API key's ACTING person, or entry: null when none runs. Needs time_entries:read.",
266
+ schema: {},
267
+ handler: (_args, cfg) => request(cfg, '/timer/active'),
268
+ });
186
269
  registerTool(server, config, {
187
270
  name: 'clokio_list_leaves',
188
271
  description: 'Leave requests in a date range. from and to are both REQUIRED (YYYY-MM-DD, to >= from). ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clokio-mcp",
3
- "version": "0.20.0",
3
+ "version": "0.21.0",
4
4
  "description": "Model Context Protocol (MCP) server for Clokio - manage tasks, projects, clients, employees, attendance and documents from Claude and any MCP client.",
5
5
  "type": "module",
6
6
  "bin": {