clokio-mcp 0.27.0 → 0.28.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 +4 -4
- package/dist/tools/attendance.js +30 -11
- package/dist/tools/projects.js +68 -3
- package/dist/tools/tasks.js +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -65,7 +65,7 @@ acting. An explicit code on a tool call always takes precedence.
|
|
|
65
65
|
|
|
66
66
|
Optional: set `CLOKIO_TOOLSETS` to register only the tool groups you need, e.g.
|
|
67
67
|
`CLOKIO_TOOLSETS=tasks`. This cuts the per-session tool-list cost - a
|
|
68
|
-
task-focused session drops from
|
|
68
|
+
task-focused session drops from 129 tools to 42. Optional groups are `tasks`,
|
|
69
69
|
`employees`, `attendance` (which includes leave, time entries and logging time), `docs` and
|
|
70
70
|
`tickets`;
|
|
71
71
|
combine them with commas. If your key cannot open documents (no acts-as
|
|
@@ -76,13 +76,13 @@ through it.
|
|
|
76
76
|
|
|
77
77
|
## Tools
|
|
78
78
|
|
|
79
|
-
|
|
79
|
+
129 tools across seven areas. Read-only tools carry the MCP `readOnlyHint`; writes
|
|
80
80
|
are marked mutating (deletes are `destructive`).
|
|
81
81
|
|
|
82
82
|
| Area | Tools |
|
|
83
83
|
|------|-------|
|
|
84
|
-
| **Tasks** | list (with `compact` / `sort` / `updated_since` / `updated_before` / `recurring`, no due date, no assignee, a custom field value, title-only search), a project's task summary (counts by status / priority / label / assignee, overdue and stale lists, per option of a release dropdown: `clokio_get_project_task_summary`), a project's activity log (`clokio_list_project_activity`), get (with `comments_limit`; names the ticket a task was opened from), create, update (incl. recurring series: set a rule, pause/resume, stop; labels: replace, add, remove; parent task; assignees in the same call), delete, bulk update (status, priority, due date, estimate, add / remove labels), set assignees (a shared board's client people as `{employee_code, organization_id}`), list a board's people, list/add/edit comments, list attachments (with `is_latest` + `latest_only`), upload, download one (save to disk, or read PDF text / ZIP entries / images / text inline), download all to a folder, read images pasted into descriptions and comments, activity (with organisations, paged), list/add/remove dependencies, set custom field (an option by value or label), set custom fields on many tasks (`clokio_bulk_set_task_custom_field`, partial success); `clokio_update_task` takes `custom_fields` too |
|
|
85
|
-
| **Projects & clients** | list projects, project statuses (and create / edit them), project custom fields (and create / edit them, e.g. a release dropdown), project contacts, get client, add client contact |
|
|
84
|
+
| **Tasks** | list (with `compact` / `fields` / `sort` / `updated_since` / `updated_before` / `recurring`, no due date, no assignee, a custom field value, title-only search), a project's task summary (counts by status / priority / label / assignee, overdue and stale lists, per option of a release dropdown: `clokio_get_project_task_summary`), a project's activity log (`clokio_list_project_activity`), get (with `comments_limit`; names the ticket a task was opened from), create, update (incl. recurring series: set a rule, pause/resume, stop; labels: replace, add, remove; parent task; assignees in the same call), delete, bulk update (status, priority, due date, estimate, add / remove labels), set assignees (a shared board's client people as `{employee_code, organization_id}`), list a board's people, list/add/edit comments, list attachments (with `is_latest` + `latest_only`), upload, download one (save to disk, or read PDF text / ZIP entries / images / text inline), download all to a folder, read images pasted into descriptions and comments, activity (with organisations, paged), list/add/remove dependencies, set custom field (an option by value or label), set custom fields on many tasks (`clokio_bulk_set_task_custom_field`, partial success); `clokio_update_task` takes `custom_fields` too |
|
|
85
|
+
| **Projects & clients** | list projects, project statuses (and create / edit / delete them; a status in use moves its tasks to `move_to`), project custom fields (and create / edit / delete them, e.g. a release dropdown; a delete removes the stored values too), project contacts, get client, add client contact |
|
|
86
86
|
| **Reference** | task statuses, task labels, locations, list webhooks (read-only), whoami |
|
|
87
87
|
| **Employees** | list (name search), lookup by PIN, task-stats, inbox (mentions, new assignments, others' status changes; with the tickets toolset, also the acting employee's open tickets), create, set status |
|
|
88
88
|
| **Attendance** | daily, range, per-employee, monthly summary, clock in/out, break start/end, time entries (local times in the person's zone; edit / delete your own), leaves (with `days`, half days counted 0.5), days of leave taken per type and year (`clokio_get_leave_days_taken`; the old name `clokio_get_leave_balances` still works; Clokio tracks no quota or remaining balance; leave and time-entry lists take `employee_code` and at most 92 days per call), **log time on a task**, start / stop / read the timer (see [Logging time](#logging-time)) |
|
package/dist/tools/attendance.js
CHANGED
|
@@ -56,7 +56,10 @@ export function entryClock(e, opts = {}) {
|
|
|
56
56
|
*/
|
|
57
57
|
export function timeEntryLine(e, opts = {}) {
|
|
58
58
|
const id = e?.id === undefined || e?.id === null ? '(no id: server older than 2026-10-16)' : `#${e.id}`;
|
|
59
|
-
|
|
59
|
+
// An entry may be on a project with no task (task #5775): name the project then.
|
|
60
|
+
const task = e?.task
|
|
61
|
+
? `task #${e.task.id} ${e.task.title}`
|
|
62
|
+
: e?.project?.name ? `no task, project ${e.project.name}` : 'no task';
|
|
60
63
|
return `${id} ${e?.date ?? '-'} ${entryClock(e, opts)} ${e?.work_hour ?? 0}h | ${e?.employee_code ?? '-'} | ${task} | ${e?.description ?? ''}`;
|
|
61
64
|
}
|
|
62
65
|
const ZONE_NOTE = "the person's CURRENT location zone, which clokio_update_time_entry reads date/start/end in (after a location change it may differ from where the entry was logged)";
|
|
@@ -405,19 +408,26 @@ export function registerAttendanceTools(server, config) {
|
|
|
405
408
|
'Needs the time_entries:write scope: Full access includes it; a key with chosen permissions must list it.';
|
|
406
409
|
const taskRule = 'The task must be one that person can view in your organisation, on an active project; a board another organisation ' +
|
|
407
410
|
'shared with you is refused (404).';
|
|
411
|
+
const targetRule = 'WHAT THE TIME IS ON, like the web Timer page (both optional): task_id, project_id, both, or neither. ' +
|
|
412
|
+
'Neither = a personal entry with no project (not billable). project_id alone = on that project with no task. ' +
|
|
413
|
+
"Both = the task decides and project_id must be the task's own project (refused otherwise). " +
|
|
414
|
+
"A project must be an active project of your organisation (clokio_list_projects). Entries hold no client: to log time for a client, use one of the client's projects. " +
|
|
415
|
+
taskRule;
|
|
408
416
|
registerTool(server, config, {
|
|
409
417
|
name: 'clokio_log_time',
|
|
410
|
-
description: 'Log a finished time entry on a task. ' + actingPerson + ' ' +
|
|
418
|
+
description: 'Log a finished time entry, on a task, a project, or neither. ' + actingPerson + ' ' +
|
|
411
419
|
'Times are local to that person (their location timezone, else UTC). Give EITHER date + start + end ' +
|
|
412
420
|
'(an end at or before start runs past midnight), OR date + start + duration_minutes, OR duration_minutes alone ' +
|
|
413
421
|
'(the entry then ENDS NOW and started that many minutes ago; date must be today or omitted). ' +
|
|
414
422
|
'At most 12 hours (720 minutes) per entry; nothing may start or end more than 10 minutes in the future; ' +
|
|
415
423
|
'an entry overlapping another time entry of the same person (including their running timer, and an overnight entry from the day before) is refused. ' +
|
|
416
|
-
'A duration counts real minutes across a DST change. ' +
|
|
417
|
-
"The project's billable flag is inherited and the description defaults to the task title. " +
|
|
424
|
+
'A duration counts real minutes across a DST change. ' + targetRule + ' ' +
|
|
425
|
+
"The project's billable flag is inherited (no project: not billable) and the description defaults to the task title (empty without a task). " +
|
|
418
426
|
ENTRY_LINE_NOTE,
|
|
419
427
|
schema: {
|
|
420
|
-
task_id: z.number().int().positive(),
|
|
428
|
+
task_id: z.number().int().positive().optional().describe('Optional. The task the time is on'),
|
|
429
|
+
project_id: z.number().int().positive().optional()
|
|
430
|
+
.describe("Optional. A project of your organisation; with task_id it must be the task's project"),
|
|
421
431
|
date: calendarDate('date').optional()
|
|
422
432
|
.describe('YYYY-MM-DD, the day the entry STARTS. Required with start'),
|
|
423
433
|
start: z.string().regex(/^\d{2}:\d{2}$/, 'start must be HH:MM').optional().describe('HH:MM, local time'),
|
|
@@ -441,6 +451,7 @@ export function registerAttendanceTools(server, config) {
|
|
|
441
451
|
method: 'POST',
|
|
442
452
|
body: {
|
|
443
453
|
task_id: args.task_id,
|
|
454
|
+
project_id: args.project_id,
|
|
444
455
|
date: args.date,
|
|
445
456
|
start: args.start,
|
|
446
457
|
end: args.end,
|
|
@@ -452,12 +463,16 @@ export function registerAttendanceTools(server, config) {
|
|
|
452
463
|
});
|
|
453
464
|
registerTool(server, config, {
|
|
454
465
|
name: 'clokio_start_timer',
|
|
455
|
-
description:
|
|
466
|
+
description: 'Start a running timer on a task, a project, or neither (a personal timer). ' + actingPerson + ' ' + targetRule + ' ' +
|
|
456
467
|
'If a timer is already running the call is refused (409) and the running entry is returned; ' +
|
|
457
|
-
'pass force: true to stop that timer and start this one. The description defaults to the task title. ' +
|
|
468
|
+
'pass force: true to stop that timer and start this one. The description defaults to the task title (empty without a task). ' +
|
|
469
|
+
'clokio_stop_timer stops any timer; the web Timer page asks for a project and a description before its own Stop, ' +
|
|
470
|
+
'so give a personal timer a description (and stop it here) if the person may stop it on the web. ' +
|
|
458
471
|
ENTRY_LINE_NOTE,
|
|
459
472
|
schema: {
|
|
460
|
-
task_id: z.number().int().positive(),
|
|
473
|
+
task_id: z.number().int().positive().optional().describe('Optional. The task the timer is on'),
|
|
474
|
+
project_id: z.number().int().positive().optional()
|
|
475
|
+
.describe("Optional. A project of your organisation; with task_id it must be the task's project"),
|
|
461
476
|
description: z.string().max(255).optional(),
|
|
462
477
|
force: z.boolean().optional().describe('Stop a running timer first instead of being refused'),
|
|
463
478
|
},
|
|
@@ -466,7 +481,7 @@ export function registerAttendanceTools(server, config) {
|
|
|
466
481
|
try {
|
|
467
482
|
return withEntryLine(await request(cfg, '/timer/start', {
|
|
468
483
|
method: 'POST',
|
|
469
|
-
body: { task_id: args.task_id, description: args.description, force: args.force },
|
|
484
|
+
body: { task_id: args.task_id, project_id: args.project_id, description: args.description, force: args.force },
|
|
470
485
|
}));
|
|
471
486
|
}
|
|
472
487
|
catch (e) {
|
|
@@ -502,7 +517,10 @@ export function registerAttendanceTools(server, config) {
|
|
|
502
517
|
registerTool(server, config, {
|
|
503
518
|
name: 'clokio_update_time_entry',
|
|
504
519
|
description: "Correct one of the time entries of the API key's ACTING person (another person's entry is \"Not found\"). " +
|
|
505
|
-
"Send only what changes: task_id (the entry moves to that task's project
|
|
520
|
+
"Send only what changes: task_id (the entry moves to that task's project; null unlinks the task and keeps the project), " +
|
|
521
|
+
'project_id (an active project of your organisation, or null to clear it; billable follows the project). ' +
|
|
522
|
+
"On an entry that is on a task, project_id must be the task's own project: to move it elsewhere send task_id: null with it. " +
|
|
523
|
+
'description (null or empty resets it to the task title, empty without a task), ' +
|
|
506
524
|
'and/or the time: date (YYYY-MM-DD), start, end (HH:MM, LOCAL to that person) or duration_minutes (alone it keeps the start and sets the length; start alone moves the entry and keeps its length). ' +
|
|
507
525
|
'Parts you leave out keep their current values. The same checks as clokio_log_time: at most 12 hours, nothing in the future, ' +
|
|
508
526
|
'no overlap with another entry of that person (their running timer included); an entry on an inactive project cannot change its time. ' +
|
|
@@ -510,7 +528,8 @@ export function registerAttendanceTools(server, config) {
|
|
|
510
528
|
'Needs time_entries:write.',
|
|
511
529
|
schema: {
|
|
512
530
|
id: z.number().int().positive().describe('The entry id (clokio_list_time_entries)'),
|
|
513
|
-
task_id: z.number().int().positive().optional(),
|
|
531
|
+
task_id: z.number().int().positive().nullable().optional().describe('A task to link, or null to unlink the task'),
|
|
532
|
+
project_id: z.number().int().positive().nullable().optional().describe('A project of your organisation, or null to clear it'),
|
|
514
533
|
date: calendarDate('date').optional(),
|
|
515
534
|
start: z.string().regex(/^\d{2}:\d{2}$/, 'start must be HH:MM').optional().describe('HH:MM, local time'),
|
|
516
535
|
end: z.string().regex(/^\d{2}:\d{2}$/, 'end must be HH:MM').optional().describe('HH:MM, local time. Not with duration_minutes'),
|
package/dist/tools/projects.js
CHANGED
|
@@ -32,7 +32,8 @@ export function registerProjectTools(server, config) {
|
|
|
32
32
|
});
|
|
33
33
|
// ── Board configuration writes (0.27.0, backend #5874 part 4) ──
|
|
34
34
|
// projects:write. Every rule is the web editor's own copy on the server;
|
|
35
|
-
// nothing is judged here beyond the shapes.
|
|
35
|
+
// nothing is judged here beyond the shapes. Delete since 0.27.1 (backend
|
|
36
|
+
// #5874 QA round 1), with the web's delete rules on the server.
|
|
36
37
|
const fieldOptions = z
|
|
37
38
|
.array(z.object({
|
|
38
39
|
value: z.string().min(1).max(100).describe('The stored value tasks hold; keep it stable'),
|
|
@@ -158,8 +159,8 @@ export function registerProjectTools(server, config) {
|
|
|
158
159
|
mutates: true,
|
|
159
160
|
description: 'Edit a project status by its slug (clokio_get_project_statuses): rename (the slug stays), recolour, ' +
|
|
160
161
|
'move (position, 0-based; the completed status stays last) or change category. A status cannot become ' +
|
|
161
|
-
'a second completed one, and the completed one cannot become active.
|
|
162
|
-
'
|
|
162
|
+
'a second completed one, and the completed one cannot become active. To remove a status use ' +
|
|
163
|
+
'clokio_delete_project_status. Needs the projects:write scope.',
|
|
163
164
|
schema: {
|
|
164
165
|
project_id: z.number().int(),
|
|
165
166
|
slug: z.string().regex(/^[A-Za-z0-9_-]{1,100}$/, 'slug: letters, digits, _ or -'),
|
|
@@ -184,6 +185,70 @@ export function registerProjectTools(server, config) {
|
|
|
184
185
|
return request(cfg, `/projects/${args.project_id}/statuses/${args.slug}`, { method: 'PATCH', body });
|
|
185
186
|
},
|
|
186
187
|
});
|
|
188
|
+
registerTool(server, config, {
|
|
189
|
+
name: 'clokio_delete_project_status',
|
|
190
|
+
mutates: 'destructive',
|
|
191
|
+
description: 'Delete a project status by its slug (clokio_get_project_statuses), with the web editor\'s rules. The ' +
|
|
192
|
+
'DEFAULT status (where new tasks start) and the COMPLETED status cannot be deleted. When tasks still use ' +
|
|
193
|
+
'the status, move_to is REQUIRED: another slug of the same project, of the SAME kind (an active status ' +
|
|
194
|
+
'for an active one); every task on the deleted status ' +
|
|
195
|
+
'(deleted tasks included) moves there in the same step, and each move is written to that task\'s ' +
|
|
196
|
+
'activity log, credited to the actor. Without move_to the server answers how many tasks use it and ' +
|
|
197
|
+
'deletes nothing. A project still using the organisation\'s statuses gets its own copy only when the ' +
|
|
198
|
+
'delete happens, so no other project changes. Needs the projects:write scope.',
|
|
199
|
+
schema: {
|
|
200
|
+
project_id: z.number().int(),
|
|
201
|
+
slug: z.string().regex(/^[A-Za-z0-9_-]{1,100}$/, 'slug: letters, digits, _ or -'),
|
|
202
|
+
move_to: z
|
|
203
|
+
.string()
|
|
204
|
+
.regex(/^[A-Za-z0-9_-]{1,100}$/, 'move_to: a status slug')
|
|
205
|
+
.optional()
|
|
206
|
+
.describe('Where the tasks on this status go: another slug of the same project. Required when any task uses it'),
|
|
207
|
+
actor_employee_code: actorCode,
|
|
208
|
+
},
|
|
209
|
+
handler: (args, cfg) => {
|
|
210
|
+
if (args.move_to !== undefined && args.move_to === args.slug) {
|
|
211
|
+
throw new Error('move_to must be another status, not the one being deleted.');
|
|
212
|
+
}
|
|
213
|
+
const body = {};
|
|
214
|
+
if (args.move_to !== undefined)
|
|
215
|
+
body.move_to = args.move_to;
|
|
216
|
+
const actor = args.actor_employee_code ?? cfg.defaultActor;
|
|
217
|
+
if (actor)
|
|
218
|
+
body.actor_employee_code = actor;
|
|
219
|
+
return request(cfg, `/projects/${args.project_id}/statuses/${args.slug}`, { method: 'DELETE', body });
|
|
220
|
+
},
|
|
221
|
+
});
|
|
222
|
+
registerTool(server, config, {
|
|
223
|
+
name: 'clokio_delete_project_custom_field',
|
|
224
|
+
mutates: 'destructive',
|
|
225
|
+
description: 'Delete a project custom field by its field_key, with the web editor\'s rule: the field is removed ' +
|
|
226
|
+
'TOGETHER WITH every value stored for it on every task, and this cannot be undone; there is no archive. ' +
|
|
227
|
+
'A field that holds any value needs confirm: true: without it the server answers how many tasks hold a ' +
|
|
228
|
+
'value and deletes nothing. A field with no values deletes without confirm. The answer says how many ' +
|
|
229
|
+
'tasks held a value (affected_task_count). Check usage first with clokio_list_tasks custom_field or ' +
|
|
230
|
+
'clokio_get_project_task_summary group_by_custom_field when that matters. A private field answers not ' +
|
|
231
|
+
'found. Needs the projects:write scope.',
|
|
232
|
+
schema: {
|
|
233
|
+
project_id: z.number().int(),
|
|
234
|
+
field_key: z.string().regex(FIELD_KEY_PATTERN, 'field_key: letters, digits, _ or -'),
|
|
235
|
+
confirm: z
|
|
236
|
+
.boolean()
|
|
237
|
+
.optional()
|
|
238
|
+
.describe('true = delete the field AND every value stored for it. Required when the field holds any value; ' +
|
|
239
|
+
'omit it first to learn how many tasks hold one'),
|
|
240
|
+
actor_employee_code: actorCode,
|
|
241
|
+
},
|
|
242
|
+
handler: (args, cfg) => {
|
|
243
|
+
const body = {};
|
|
244
|
+
if (args.confirm !== undefined)
|
|
245
|
+
body.confirm = args.confirm;
|
|
246
|
+
const actor = args.actor_employee_code ?? cfg.defaultActor;
|
|
247
|
+
if (actor)
|
|
248
|
+
body.actor_employee_code = actor;
|
|
249
|
+
return request(cfg, `/projects/${args.project_id}/custom-fields/${args.field_key}`, { method: 'DELETE', body });
|
|
250
|
+
},
|
|
251
|
+
});
|
|
187
252
|
registerTool(server, config, {
|
|
188
253
|
name: 'clokio_get_project_contacts',
|
|
189
254
|
description: "The contacts for a project's client.",
|
package/dist/tools/tasks.js
CHANGED
|
@@ -337,6 +337,25 @@ export function withLinkedTicket(task) {
|
|
|
337
337
|
* 422 are refused here first, in the same words, so no request is spent on
|
|
338
338
|
* them.
|
|
339
339
|
*/
|
|
340
|
+
/**
|
|
341
|
+
* The keys `fields` may name on GET /tasks (0.27.1, backend #5874 QA round 1):
|
|
342
|
+
* the compact row's keys plus custom_fields. The server's own list is
|
|
343
|
+
* PublicTaskResource::SELECTABLE_FIELDS; an unknown name is its 422.
|
|
344
|
+
*/
|
|
345
|
+
export const TASK_LIST_FIELDS = [
|
|
346
|
+
'id',
|
|
347
|
+
'title',
|
|
348
|
+
'status',
|
|
349
|
+
'priority',
|
|
350
|
+
'due_date',
|
|
351
|
+
'parent_id',
|
|
352
|
+
'project_id',
|
|
353
|
+
'assignee_codes',
|
|
354
|
+
'external_assignees',
|
|
355
|
+
'labels',
|
|
356
|
+
'updated_at',
|
|
357
|
+
'custom_fields',
|
|
358
|
+
];
|
|
340
359
|
export function listFilterQuery(args) {
|
|
341
360
|
if (args.no_due_date === true && (args.due_before !== undefined || args.due_after !== undefined)) {
|
|
342
361
|
throw new Error('no_due_date=true cannot be combined with due_before or due_after: a task with no due date is never due before or after anything. Drop one of them.');
|
|
@@ -456,6 +475,14 @@ export function registerTaskTools(server, config) {
|
|
|
456
475
|
'and updated_at - dropping the HTML description and flattening the nested project/assignee ' +
|
|
457
476
|
'objects to ids. Resolve a project_id with clokio_list_projects, and fetch the one task you ' +
|
|
458
477
|
'actually need in full with clokio_get_task (its URL is /tasks/{id}/view).'),
|
|
478
|
+
fields: z
|
|
479
|
+
.array(z.enum(TASK_LIST_FIELDS))
|
|
480
|
+
.min(1)
|
|
481
|
+
.max(TASK_LIST_FIELDS.length)
|
|
482
|
+
.optional()
|
|
483
|
+
.describe('Return ONLY these keys per row, in this order (implies compact), e.g. ["id", "title", "status"]. ' +
|
|
484
|
+
'Allowed: ' + TASK_LIST_FIELDS.join(', ') + '. custom_fields is the full row\'s object of public ' +
|
|
485
|
+
'field values. The smallest scan there is.'),
|
|
459
486
|
sort: z
|
|
460
487
|
.enum([
|
|
461
488
|
'due_date',
|
|
@@ -497,6 +524,7 @@ export function registerTaskTools(server, config) {
|
|
|
497
524
|
cursor: args.cursor,
|
|
498
525
|
sort: args.sort,
|
|
499
526
|
compact: args.compact === undefined ? undefined : args.compact ? 1 : 0,
|
|
527
|
+
fields: args.fields === undefined ? undefined : [...new Set(args.fields)].join(','),
|
|
500
528
|
},
|
|
501
529
|
}),
|
|
502
530
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clokio-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.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": {
|