clokio-mcp 0.33.0 → 0.35.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 +3 -2
- package/dist/index.js +4 -1
- package/dist/tools/invoices.js +138 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -90,8 +90,8 @@ acting. An explicit code on a tool call always takes precedence.
|
|
|
90
90
|
Optional: set `CLOKIO_TOOLSETS` to register only the tool groups you need, e.g.
|
|
91
91
|
`CLOKIO_TOOLSETS=tasks`. This cuts the per-session tool-list cost - a
|
|
92
92
|
task-focused session drops from 137 tools to 48. Optional groups are `tasks`,
|
|
93
|
-
`employees`, `attendance` (which includes leave, time entries and logging time), `docs
|
|
94
|
-
`tickets
|
|
93
|
+
`employees`, `attendance` (which includes leave, time entries and logging time), `docs`,
|
|
94
|
+
`tickets` and `invoices` (read only: needs `invoices:read` and billing access);
|
|
95
95
|
combine them with commas. If your key cannot open documents (no acts-as
|
|
96
96
|
employee and no explicit docs scopes), leaving `docs` out saves 33 tools it
|
|
97
97
|
cannot use. The `projects` group (project/client lookups, task statuses
|
|
@@ -112,6 +112,7 @@ are marked mutating (deletes are `destructive`).
|
|
|
112
112
|
| **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)) |
|
|
113
113
|
| **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) |
|
|
114
114
|
| **Tickets** (see [Tickets](#tickets)) | list (filters incl. several labels, created dates, needs reply, sort + cursor), **open a new ticket by email or WhatsApp template** (sent, or as a draft a person sends; `Idempotency-Key` per call, `destructive`), a WhatsApp number's approved templates with the slots to fill, 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`), **files on an outgoing email** (`file_paths` or `attachment_ids` on a new email and a reply), internal note (with mentions), update state / assignee (resolve without the satisfaction survey), **a contact's client with its projects, open tasks and recently done tasks** ("what is the status of my task?"), add / remove one label, contacts list / get / create / update, inboxes, ticket labels (list / create), saved replies, download a file a customer sent, save / delete your own reply draft, list the tasks opened from a ticket, **open a task from a ticket** (with its files; `Idempotency-Key` per call), **reports** (overview, agents, satisfaction + its ratings, workload) |
|
|
115
|
+
| **Invoices** (`invoices` toolset, read only) | list invoices (client, number, months, several statuses, minimum total; rows carry billable hours), get one (with `include_employees` and `include_activities`; lines carry `task_state` and `missing_rate`), a task's billing (with a `note` when it bills nothing), a client's month-by-month billing summary, one month across all clients (**compact by default**, filters for clients, not invoiced, dismissed, minimum billable hours), what a client has unbilled in a month. Month rows carry `amount` (the invoice's own numbers, or the same estimate as the unbilled tool) beside the deprecated `estimated_amount`; `invoiced` / `total` count issued invoices only, drafts are `draft`; the No Client bucket is client id 0. Needs `invoices:read` and billing access for the key's person; "paid" is a status set in Clokio, not a payment record; totals are per currency; no iCount data beyond the number stored as `invoice_number` (the provider's document number when an invoicing integration finalized the invoice) |
|
|
115
116
|
|
|
116
117
|
### Conventions
|
|
117
118
|
|
package/dist/index.js
CHANGED
|
@@ -18,6 +18,7 @@ import { ClokioApiError, loadConfig, request } from './client.js';
|
|
|
18
18
|
import { VERSION } from './version.js';
|
|
19
19
|
import { registerTaskTools } from './tools/tasks.js';
|
|
20
20
|
import { registerProjectTools } from './tools/projects.js';
|
|
21
|
+
import { registerInvoiceTools } from './tools/invoices.js';
|
|
21
22
|
import { registerEmployeeTools } from './tools/employees.js';
|
|
22
23
|
import { registerAttendanceTools } from './tools/attendance.js';
|
|
23
24
|
import { registerDocTools } from './tools/docs.js';
|
|
@@ -36,7 +37,7 @@ import { registerTicketTools } from './tools/tickets.js';
|
|
|
36
37
|
* to resolve names to ids, so it is always registered. Naming a toolset that
|
|
37
38
|
* does not exist is refused at startup rather than silently ignored.
|
|
38
39
|
*/
|
|
39
|
-
const OPTIONAL_TOOLSETS = ['tasks', 'employees', 'attendance', 'docs', 'tickets'];
|
|
40
|
+
const OPTIONAL_TOOLSETS = ['tasks', 'employees', 'attendance', 'docs', 'tickets', 'invoices'];
|
|
40
41
|
function selectedToolsets() {
|
|
41
42
|
const raw = process.env.CLOKIO_TOOLSETS?.trim();
|
|
42
43
|
if (!raw) {
|
|
@@ -125,6 +126,8 @@ async function main() {
|
|
|
125
126
|
registerDocTools(server, config);
|
|
126
127
|
if (toolsets.has('tickets'))
|
|
127
128
|
registerTicketTools(server, config);
|
|
129
|
+
if (toolsets.has('invoices'))
|
|
130
|
+
registerInvoiceTools(server, config);
|
|
128
131
|
const transport = new StdioServerTransport();
|
|
129
132
|
await server.connect(transport);
|
|
130
133
|
// stderr is safe for logs on stdio transport (stdout carries the protocol).
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { request } from '../client.js';
|
|
3
|
+
import { registerTool } from './helpers.js';
|
|
4
|
+
/**
|
|
5
|
+
* The read-only invoices toolset (0.34.0; round 2 in 0.35.0). Every tool is a GET over the
|
|
6
|
+
* backend's /invoices family; nothing here creates, sends, marks paid or
|
|
7
|
+
* cancels an invoice.
|
|
8
|
+
*/
|
|
9
|
+
const ACCESS = 'Needs the invoices:read scope AND billing access for the key\'s person (otherwise 403 NO_BILLING_ACCESS). ' +
|
|
10
|
+
'Read only. These are Clokio\'s own invoices; no iCount data beyond the number stored as invoice_number. "paid" is a status someone set in Clokio, ' +
|
|
11
|
+
'not a payment record.';
|
|
12
|
+
const NO_CLIENT = 'The "No Client" bucket is client id 0 everywhere (client {id: 0, name: "No Client"}). ';
|
|
13
|
+
const AMOUNT = 'Each row has amount {source, subtotal, vat_amount, total, currency}: source "invoice" is the invoice\'s own numbers, ' +
|
|
14
|
+
'source "estimate" is exactly what clokio_get_client_unbilled answers for that client and month (VAT at the organisation rate). ' +
|
|
15
|
+
'Use amount, not estimated_amount: estimated_amount is the screen\'s figure, deprecated (the invoice total WITH VAT, or hours times rates ' +
|
|
16
|
+
'WITHOUT VAT, rounded differently). Compact rows (clokio_get_billing_month by default) carry only amount {source, total, currency}. ' +
|
|
17
|
+
'missing_rate is true when billable hours have no rate (amount 0). ';
|
|
18
|
+
const month = (what) => z.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/, 'month must be YYYY-MM (month 01-12)').describe(what);
|
|
19
|
+
const id = z.number().int().positive();
|
|
20
|
+
const includeEmployees = z
|
|
21
|
+
.boolean()
|
|
22
|
+
.optional()
|
|
23
|
+
.describe('true: each line also lists who worked it and for how many hours (include=employees); ' +
|
|
24
|
+
'a task line lists everyone who worked that task in the period, even when the task is split over several lines ' +
|
|
25
|
+
'(employees_scope "task"); a task-less line lists that line\'s people (employees_scope "line").');
|
|
26
|
+
export function registerInvoiceTools(server, config) {
|
|
27
|
+
registerTool(server, config, {
|
|
28
|
+
name: 'clokio_list_invoices',
|
|
29
|
+
description: 'List Clokio invoices, newest first, optionally by client, exact number, period months, status and minimum total. ' +
|
|
30
|
+
'Rows carry billable_hours (sum of the billable lines). invoice_number is the number Clokio stored at finalize: ' +
|
|
31
|
+
'INV-<year>-<seq> style without an invoicing integration, the provider\'s document number (plain digits) with one. ' +
|
|
32
|
+
'client_id 0 lists the invoices with no client. ' + NO_CLIENT +
|
|
33
|
+
'Paginated: {data, meta}. ' + ACCESS,
|
|
34
|
+
schema: {
|
|
35
|
+
client_id: z.number().int().min(0).optional().describe('Only this client; 0 = invoices with no client'),
|
|
36
|
+
number: z.string().min(1).max(50).optional().describe('Exact invoice number, e.g. INV-2026-0012 (Clokio\'s own) or 3529 (a provider document number)'),
|
|
37
|
+
from_month: month('First period month, YYYY-MM').optional(),
|
|
38
|
+
to_month: month('Last period month, YYYY-MM; at most 24 months after from_month').optional(),
|
|
39
|
+
status: z.array(z.enum(['draft', 'finalized', 'sent', 'paid', 'cancelled'])).min(1).optional()
|
|
40
|
+
.describe('One or more statuses'),
|
|
41
|
+
min_total: z.number().min(0).optional().describe('Only invoices whose total is at least this (in each invoice\'s own currency)'),
|
|
42
|
+
per_page: z.number().int().min(1).max(100).optional(),
|
|
43
|
+
page: z.number().int().min(1).optional(),
|
|
44
|
+
},
|
|
45
|
+
handler: (args, cfg) => request(cfg, '/invoices', {
|
|
46
|
+
query: {
|
|
47
|
+
client_id: args.client_id,
|
|
48
|
+
number: args.number,
|
|
49
|
+
from_month: args.from_month,
|
|
50
|
+
to_month: args.to_month,
|
|
51
|
+
status: args.status,
|
|
52
|
+
min_total: args.min_total,
|
|
53
|
+
per_page: args.per_page,
|
|
54
|
+
page: args.page,
|
|
55
|
+
},
|
|
56
|
+
}),
|
|
57
|
+
});
|
|
58
|
+
registerTool(server, config, {
|
|
59
|
+
name: 'clokio_get_invoice',
|
|
60
|
+
description: 'One invoice with its lines and activity_count (include_employees adds who worked each line; include_activities adds the activity trail). ' +
|
|
61
|
+
'Each line has task_state: "linked" (the task exists), "deleted" (the task was deleted or is gone; the stored title still shows), ' +
|
|
62
|
+
'"none" (no task), and missing_rate (billable hours with no price). ' + NO_CLIENT + ACCESS,
|
|
63
|
+
schema: {
|
|
64
|
+
id,
|
|
65
|
+
include_employees: includeEmployees,
|
|
66
|
+
include_activities: z.boolean().optional().describe('true: also return the activity trail (include=activities). Default false: only activity_count.'),
|
|
67
|
+
},
|
|
68
|
+
handler: (args, cfg) => {
|
|
69
|
+
const include = [args.include_employees ? 'employees' : null, args.include_activities ? 'activities' : null].filter(Boolean).join(',');
|
|
70
|
+
return request(cfg, `/invoices/${args.id}`, { query: { include: include || undefined } });
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
registerTool(server, config, {
|
|
74
|
+
name: 'clokio_get_task_billing',
|
|
75
|
+
description: "A task's billing history: its invoice lines (newest period first), totals by status, and its unbilled hours and months. Amounts are keyed by currency. " +
|
|
76
|
+
'note says why the hours may bill nothing: "no_client" (the project has no client), "project_not_billable", or null. ' + NO_CLIENT + ACCESS,
|
|
77
|
+
schema: { task_id: id },
|
|
78
|
+
handler: (args, cfg) => request(cfg, `/tasks/${args.task_id}/billing`),
|
|
79
|
+
});
|
|
80
|
+
registerTool(server, config, {
|
|
81
|
+
name: 'clokio_get_client_billing_summary',
|
|
82
|
+
description: "A client's month-by-month billing (every month in the span, default the last 12 full months, at most 24): " +
|
|
83
|
+
'per month the invoice, billable hours, amount and missing_rate; totals keyed by currency: invoiced counts ISSUED invoices only ' +
|
|
84
|
+
'(finalized + sent + paid), draft is the drafts\' total, plus marked_paid, outstanding and overdue_count. Money totals are per currency, never summed ' +
|
|
85
|
+
'across currencies. ' + AMOUNT + ACCESS,
|
|
86
|
+
schema: {
|
|
87
|
+
client_id: id,
|
|
88
|
+
from_month: month('First month, YYYY-MM').optional(),
|
|
89
|
+
to_month: month('Last month, YYYY-MM').optional(),
|
|
90
|
+
},
|
|
91
|
+
handler: (args, cfg) => request(cfg, `/clients/${args.client_id}/billing-summary`, {
|
|
92
|
+
query: { from_month: args.from_month, to_month: args.to_month },
|
|
93
|
+
}),
|
|
94
|
+
});
|
|
95
|
+
registerTool(server, config, {
|
|
96
|
+
name: 'clokio_get_billing_month',
|
|
97
|
+
description: 'One month across all clients, as the web /invoices screen: per client hours, amount and invoice, the client ids not yet invoiced (not_invoiced, 0 = No Client), and totals. ' +
|
|
98
|
+
'COMPACT BY DEFAULT: the full answer is large enough to overflow an agent, so this tool sends compact=1 unless compact is false ' +
|
|
99
|
+
'(compact rows: client, billable_hours, amount {source, total, currency}, invoice {id, status}, dismissed, missing_rate). ' +
|
|
100
|
+
'Filters client_ids, only_not_invoiced, exclude_dismissed, min_billable_hours; rows, not_invoiced and totals are computed over the FILTERED rows. ' +
|
|
101
|
+
'totals per currency: total = ISSUED invoices only (finalized + sent + paid), draft = the drafts, pending = finalized + sent, overdue_count. Money totals are per currency, never summed. ' +
|
|
102
|
+
AMOUNT + NO_CLIENT + ACCESS,
|
|
103
|
+
schema: {
|
|
104
|
+
month: month('The month, YYYY-MM; not in the future'),
|
|
105
|
+
client_ids: z.array(z.number().int().min(0)).min(1).max(500).optional().describe('Only these clients; 0 = the No Client bucket'),
|
|
106
|
+
only_not_invoiced: z.boolean().optional().describe('true: only clients with billable hours, no invoice and not dismissed'),
|
|
107
|
+
exclude_dismissed: z.boolean().optional().describe('true: leave dismissed clients out'),
|
|
108
|
+
min_billable_hours: z.number().min(0).optional().describe('Only clients with at least this many billable hours'),
|
|
109
|
+
compact: z.boolean().optional().describe('Default true (smaller rows). false: the full rows (total_hours, projects, estimated_amount, the full amount, the invoice number and total).'),
|
|
110
|
+
},
|
|
111
|
+
handler: (args, cfg) => request(cfg, '/invoices/month', {
|
|
112
|
+
query: {
|
|
113
|
+
month: args.month,
|
|
114
|
+
client_ids: args.client_ids,
|
|
115
|
+
only_not_invoiced: args.only_not_invoiced === undefined ? undefined : args.only_not_invoiced ? 1 : 0,
|
|
116
|
+
exclude_dismissed: args.exclude_dismissed === undefined ? undefined : args.exclude_dismissed ? 1 : 0,
|
|
117
|
+
min_billable_hours: args.min_billable_hours,
|
|
118
|
+
compact: args.compact === false ? 0 : 1,
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
121
|
+
});
|
|
122
|
+
registerTool(server, config, {
|
|
123
|
+
name: 'clokio_get_client_unbilled',
|
|
124
|
+
description: "What is still unbilled for a client in a month: the web invoice review's lines, from finished web-timer entries; " +
|
|
125
|
+
'non-billable ones appear with is_billable false and price 0; subtotal counts billable lines only. client_id 0 is the "No Client" bucket. If an invoice already exists for that month the ' +
|
|
126
|
+
'answer is existing_invoice and no lines. Amounts are in one currency per answer; each line carries its own. ' +
|
|
127
|
+
'Each line has missing_rate (billable hours with no rate). ' + NO_CLIENT +
|
|
128
|
+
ACCESS,
|
|
129
|
+
schema: {
|
|
130
|
+
client_id: z.number().int().min(0),
|
|
131
|
+
month: month('The month, YYYY-MM; not in the future'),
|
|
132
|
+
include_employees: includeEmployees,
|
|
133
|
+
},
|
|
134
|
+
handler: (args, cfg) => request(cfg, `/clients/${args.client_id}/unbilled`, {
|
|
135
|
+
query: { month: args.month, include: args.include_employees ? 'employees' : undefined },
|
|
136
|
+
}),
|
|
137
|
+
});
|
|
138
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clokio-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.35.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": {
|