mcp-intervals 1.5.1 → 1.6.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
@@ -111,16 +111,43 @@ Add to `~/.codeium/windsurf/mcp_config.json`:
111
111
 
112
112
  ## Available Tools
113
113
 
114
- | Tool | Description |
115
- | ------------------ | ---------------------------------------------------------------------------- |
116
- | `get_task` | Get task details by local ID or Intervals URL |
117
- | `update_task` | Update task status, assignee, priority, title, description, due date, owner |
118
- | `add_task_note` | Add a comment/note to a task (supports HTML) |
119
- | `get_task_notes` | Retrieve all comments/notes on a task |
120
- | `add_time_entry` | Add a time entry to a task (billable/unbillable with work type) |
121
- | `get_time_entries` | Retrieve time entries (filter by task, date range) |
122
- | `get_project` | Get project details (name, client, dates, budget) |
123
- | `get_milestone` | Get milestone details (title, due date, progress) |
114
+ 104 tools covering the full Intervals API. Tasks are addressed by the **local task ID** shown in the web UI (or a task URL for `get_task`); every other record uses its numeric API ID.
115
+
116
+ **Permissions:** some collection endpoints (`worktype`, `invoice`, `expense`, `module`, `payment`) return 403 depending on the user's permission group. Project-scoped alternatives exist: `get_project_worktypes` and `get_project_modules`.
117
+
118
+ | Area | Tools |
119
+ | ---- | ----- |
120
+ | Tasks | `get_task`, `get_tasks`, `create_task`, `update_task`, `delete_task`, `get_task_statuses`, `get_task_priorities` |
121
+ | Task notes | `get_task_notes`, `add_task_note`, `update_task_note`, `delete_task_note` |
122
+ | Task filters | `get_task_filters`, `get_task_filter`, `create_task_filter`, `update_task_filter` |
123
+ | Time | `add_time_entry`, `get_time_entries`, `update_time_entry`, `delete_time_entry`, `get_worktypes` |
124
+ | Timers | `get_timers`, `start_timer`, `stop_timer` |
125
+ | Expenses | `get_expenses`, `create_expense` |
126
+ | Projects | `get_project`, `get_projects`, `create_project`, `update_project` |
127
+ | Project team | `get_project_team`, `add_project_team_member`, `remove_project_team_member` |
128
+ | Project modules | `get_project_modules`, `add_project_module`, `update_project_module`, `remove_project_module` |
129
+ | Project work types | `get_project_worktypes`, `add_project_worktype`, `update_project_worktype`, `remove_project_worktype` |
130
+ | Project labels | `get_project_labels`, `get_project_label`, `create_project_label`, `update_project_label` |
131
+ | Project notes | `get_project_notes`, `get_project_note`, `create_project_note`, `update_project_note`, `delete_project_note` |
132
+ | Default modules | `get_modules`, `get_module`, `create_module`, `update_module` |
133
+ | Milestones | `get_milestone`, `get_milestones`, `create_milestone`, `update_milestone` |
134
+ | Milestone notes | `get_milestone_notes`, `create_milestone_note`, `update_milestone_note`, `delete_milestone_note` |
135
+ | Requests | `get_requests`, `get_request`, `create_request`, `update_request`, `delete_request` |
136
+ | Clients | `get_clients`, `get_client`, `create_client`, `update_client` |
137
+ | People | `get_people`, `get_person`, `get_me`, `get_groups`, `get_quota` |
138
+ | Contacts | `get_contact_types`, `get_contact_descriptors`, `get_person_contacts`, `create_person_contact`, `update_person_contact`, `delete_person_contact` |
139
+ | Invoices | `get_invoices`, `get_invoice`, `create_invoice`, `get_invoice_terms` |
140
+ | Invoice items | `get_invoice_items`, `create_invoice_item`, `update_invoice_item`, `delete_invoice_item` |
141
+ | Invoice notes | `get_invoice_notes`, `create_invoice_note`, `update_invoice_note`, `delete_invoice_note` |
142
+ | Payments | `get_payments`, `get_payment`, `create_payment`, `update_payment`, `delete_payment`, `get_payment_types` |
143
+ | Documents | `get_documents`, `get_document`, `download_document` (images inline, PDF text), `create_document`, `update_document` |
144
+
145
+ ### Notifications
146
+
147
+ Every write tool (create/update/delete) accepts two optional booleans:
148
+
149
+ - `send_notifications` - sends `X-Intervals-Send-Notifications: t`. **Emails are not sent unless this is true** (the API default).
150
+ - `disable_action_notes` - sends `X-Intervals-Disable-Action-Notes: t` to suppress the automatic action notes Intervals adds to tasks and milestones.
124
151
 
125
152
  ## Resources
126
153
 
package/dist/client.d.ts CHANGED
@@ -1,7 +1,18 @@
1
- export interface IntervalsRequestOptions {
1
+ export type QueryParams = Record<string, string | number | boolean | undefined>;
2
+ /**
3
+ * Opt-in notification controls for write requests. The Intervals API sends no
4
+ * emails by default, so nothing is added unless a flag is explicitly true.
5
+ */
6
+ export interface NotificationOptions {
7
+ /** Maps to `X-Intervals-Send-Notifications: t` (emails are NOT sent otherwise). */
8
+ sendNotifications?: boolean;
9
+ /** Maps to `X-Intervals-Disable-Action-Notes: t` (suppress automatic action notes). */
10
+ disableActionNotes?: boolean;
11
+ }
12
+ export interface IntervalsRequestOptions extends NotificationOptions {
2
13
  method?: "GET" | "POST" | "PUT" | "DELETE";
3
14
  body?: Record<string, unknown>;
4
- params?: Record<string, string | number | boolean>;
15
+ params?: QueryParams;
5
16
  }
6
17
  export declare class IntervalsClient {
7
18
  private baseUrl;
@@ -9,19 +20,19 @@ export declare class IntervalsClient {
9
20
  constructor(apiToken: string);
10
21
  private request;
11
22
  private requestBinary;
23
+ get(path: string, params?: QueryParams): Promise<Record<string, unknown>>;
24
+ post(path: string, body: Record<string, unknown>, notify?: NotificationOptions): Promise<Record<string, unknown>>;
25
+ put(path: string, body: Record<string, unknown>, notify?: NotificationOptions): Promise<Record<string, unknown>>;
26
+ delete(path: string, params?: QueryParams, notify?: NotificationOptions): Promise<Record<string, unknown>>;
12
27
  getTask(id: number): Promise<Record<string, unknown>>;
13
28
  getTaskByLocalId(localId: number): Promise<Record<string, unknown>>;
14
29
  resolveTaskId(localId: number): Promise<number>;
15
- updateTask(id: number, fields: Record<string, unknown>): Promise<Record<string, unknown>>;
16
- getTaskNotes(taskId: number): Promise<Record<string, unknown>>;
17
- addTaskNote(taskId: number, note: string, isPublic?: boolean): Promise<Record<string, unknown>>;
30
+ updateTask(id: number, fields: Record<string, unknown>, notify?: NotificationOptions): Promise<Record<string, unknown>>;
31
+ getTaskNotes(taskId: number, filters?: QueryParams): Promise<Record<string, unknown>>;
32
+ addTaskNote(taskId: number, note: string, isPublic?: boolean, notify?: NotificationOptions): Promise<Record<string, unknown>>;
18
33
  getProject(id: number): Promise<Record<string, unknown>>;
19
34
  getMilestone(id: number): Promise<Record<string, unknown>>;
20
- getDocuments(params?: {
21
- taskid?: number;
22
- projectid?: number;
23
- personid?: number;
24
- }): Promise<Record<string, unknown>>;
35
+ getDocuments(params?: QueryParams): Promise<Record<string, unknown>>;
25
36
  getDocument(id: number): Promise<Record<string, unknown>>;
26
37
  downloadDocument(id: number): Promise<{
27
38
  buffer: ArrayBuffer;
@@ -37,13 +48,10 @@ export declare class IntervalsClient {
37
48
  time: number;
38
49
  billable: boolean;
39
50
  description?: string;
40
- }): Promise<Record<string, unknown>>;
41
- getTimeEntries(params?: {
42
- taskid?: number;
43
51
  personid?: number;
44
- datebegin?: string;
45
- dateend?: string;
46
- }): Promise<Record<string, unknown>>;
52
+ moduleid?: number;
53
+ }, notify?: NotificationOptions): Promise<Record<string, unknown>>;
54
+ getTimeEntries(params?: QueryParams): Promise<Record<string, unknown>>;
47
55
  getMe(): Promise<{
48
56
  personid: number;
49
57
  }>;
package/dist/client.js CHANGED
@@ -1,3 +1,15 @@
1
+ function buildQueryString(params) {
2
+ if (!params)
3
+ return "";
4
+ const searchParams = new URLSearchParams();
5
+ for (const [key, value] of Object.entries(params)) {
6
+ if (value === undefined)
7
+ continue;
8
+ searchParams.set(key, String(value));
9
+ }
10
+ const query = searchParams.toString();
11
+ return query ? `?${query}` : "";
12
+ }
1
13
  export class IntervalsClient {
2
14
  baseUrl = "https://api.myintervals.com";
3
15
  authHeader;
@@ -6,19 +18,18 @@ export class IntervalsClient {
6
18
  "Basic " + Buffer.from(`${apiToken}:X`).toString("base64");
7
19
  }
8
20
  async request(path, options = {}) {
9
- const { method = "GET", body, params } = options;
10
- let url = `${this.baseUrl}${path}`;
11
- if (params) {
12
- const searchParams = new URLSearchParams();
13
- for (const [key, value] of Object.entries(params)) {
14
- searchParams.set(key, String(value));
15
- }
16
- url += `?${searchParams.toString()}`;
17
- }
21
+ const { method = "GET", body, params, sendNotifications, disableActionNotes, } = options;
22
+ const url = `${this.baseUrl}${path}${buildQueryString(params)}`;
18
23
  const headers = {
19
24
  Authorization: this.authHeader,
20
25
  Accept: "application/json",
21
26
  };
27
+ if (sendNotifications) {
28
+ headers["X-Intervals-Send-Notifications"] = "t";
29
+ }
30
+ if (disableActionNotes) {
31
+ headers["X-Intervals-Disable-Action-Notes"] = "t";
32
+ }
22
33
  const fetchOptions = { method, headers };
23
34
  if (body) {
24
35
  headers["Content-Type"] = "application/json";
@@ -29,29 +40,52 @@ export class IntervalsClient {
29
40
  const text = await response.text();
30
41
  throw new Error(`Intervals API error ${response.status}: ${text.slice(0, 500)}`);
31
42
  }
32
- return response.json();
43
+ // Successful writes (e.g. DELETE) may return an empty body.
44
+ const text = await response.text();
45
+ if (!text.trim())
46
+ return {};
47
+ return JSON.parse(text);
33
48
  }
34
49
  async requestBinary(path, params) {
35
- let url = `${this.baseUrl}${path}`;
36
- if (params) {
37
- const searchParams = new URLSearchParams();
38
- for (const [key, value] of Object.entries(params)) {
39
- searchParams.set(key, String(value));
40
- }
41
- url += `?${searchParams.toString()}`;
42
- }
50
+ const url = `${this.baseUrl}${path}${buildQueryString(params)}`;
43
51
  const response = await fetch(url, {
44
52
  method: "GET",
45
53
  headers: { Authorization: this.authHeader },
46
54
  });
47
55
  if (!response.ok) {
48
56
  const text = await response.text();
49
- throw new Error(`Intervals API error ${response.status}: ${text}`);
57
+ throw new Error(`Intervals API error ${response.status}: ${text.slice(0, 500)}`);
50
58
  }
51
59
  const buffer = await response.arrayBuffer();
52
60
  const contentType = response.headers.get("content-type") || "application/octet-stream";
53
61
  return { buffer, contentType };
54
62
  }
63
+ // --- Generic verbs (used by the declarative endpoint tools) ---
64
+ // Paths are given without the trailing slash, e.g. "/client" or "/client/12".
65
+ get(path, params) {
66
+ return this.request(`${path}/`, { params });
67
+ }
68
+ post(path, body, notify = {}) {
69
+ return this.request(`${path}/`, {
70
+ method: "POST",
71
+ body,
72
+ ...notify,
73
+ });
74
+ }
75
+ put(path, body, notify = {}) {
76
+ return this.request(`${path}/`, {
77
+ method: "PUT",
78
+ body,
79
+ ...notify,
80
+ });
81
+ }
82
+ delete(path, params, notify = {}) {
83
+ return this.request(`${path}/`, {
84
+ method: "DELETE",
85
+ params,
86
+ ...notify,
87
+ });
88
+ }
55
89
  // --- Task ---
56
90
  async getTask(id) {
57
91
  const data = await this.request(`/task/${id}/`);
@@ -68,19 +102,20 @@ export class IntervalsClient {
68
102
  const task = await this.getTaskByLocalId(localId);
69
103
  return Number(task.id);
70
104
  }
71
- async updateTask(id, fields) {
72
- const data = await this.request(`/task/${id}/`, { method: "PUT", body: fields });
105
+ async updateTask(id, fields, notify = {}) {
106
+ const data = await this.request(`/task/${id}/`, { method: "PUT", body: fields, ...notify });
73
107
  return data;
74
108
  }
75
109
  // --- Task Notes ---
76
- async getTaskNotes(taskId) {
77
- const data = await this.request(`/tasknote/`, { params: { taskid: taskId } });
110
+ async getTaskNotes(taskId, filters = {}) {
111
+ const data = await this.request(`/tasknote/`, { params: { ...filters, taskid: taskId } });
78
112
  return data;
79
113
  }
80
- async addTaskNote(taskId, note, isPublic = true) {
114
+ async addTaskNote(taskId, note, isPublic = true, notify = {}) {
81
115
  const data = await this.request(`/tasknote/`, {
82
116
  method: "POST",
83
117
  body: { taskid: taskId, note, public: isPublic },
118
+ ...notify,
84
119
  });
85
120
  return data;
86
121
  }
@@ -97,7 +132,7 @@ export class IntervalsClient {
97
132
  // --- Documents ---
98
133
  async getDocuments(params = {}) {
99
134
  const data = await this.request(`/document/`, {
100
- params: params,
135
+ params,
101
136
  });
102
137
  return data;
103
138
  }
@@ -123,10 +158,13 @@ export class IntervalsClient {
123
158
  return data;
124
159
  }
125
160
  // --- Time Entries ---
126
- async addTimeEntry(fields) {
127
- // First get the current user's person ID
128
- const me = await this.getMe();
129
- const personid = me.personid;
161
+ async addTimeEntry(fields, notify = {}) {
162
+ // Default to the current user's person ID
163
+ let personid = fields.personid;
164
+ if (personid === undefined) {
165
+ const me = await this.getMe();
166
+ personid = me.personid;
167
+ }
130
168
  if (typeof personid !== "number") {
131
169
  throw new Error("Could not determine current user's person ID from Intervals /me/ response.");
132
170
  }
@@ -139,13 +177,15 @@ export class IntervalsClient {
139
177
  date: fields.date,
140
178
  time: fields.time,
141
179
  billable: fields.billable,
180
+ ...(fields.moduleid !== undefined && { moduleid: fields.moduleid }),
142
181
  ...(fields.description && { description: fields.description }),
143
182
  },
183
+ ...notify,
144
184
  });
145
185
  return data;
146
186
  }
147
187
  async getTimeEntries(params = {}) {
148
- const data = await this.request(`/time/`, { params: params });
188
+ const data = await this.request(`/time/`, { params });
149
189
  return data;
150
190
  }
151
191
  // --- Me (current user) ---
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ async function startServer() {
11
11
  const { McpServer } = await import("@modelcontextprotocol/sdk/server/mcp.js");
12
12
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
13
13
  const { IntervalsClient } = await import("./client.js");
14
- const { registerTools } = await import("./tools.js");
14
+ const { registerTools } = await import("./tools/index.js");
15
15
  const { registerResources } = await import("./resources.js");
16
16
  const { createRequire } = await import("node:module");
17
17
  // Read version from package.json so it never drifts from the published one.
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { IntervalsClient } from "../client.js";
3
+ export declare function registerBillingTools(server: McpServer, client: IntervalsClient): void;
@@ -0,0 +1,243 @@
1
+ import { z } from "zod";
2
+ import { limitParam, offsetParam, registerEndpoints } from "./helpers.js";
3
+ const itemFields = {
4
+ invoiceid: z.number().describe("Invoice ID (use get_invoices)"),
5
+ name: z.string().describe("Line item name (max 255 characters)"),
6
+ amount: z.number().describe("Line item amount"),
7
+ description: z.string().describe("Line item description"),
8
+ quantity: z.number().describe("Quantity"),
9
+ rate: z.number().describe("Rate"),
10
+ };
11
+ const invoiceNoteFields = {
12
+ invoiceid: z.number().describe("Invoice ID (use get_invoices)"),
13
+ title: z.string().describe("Note title (max 255 characters)"),
14
+ note: z.string().describe("Note body"),
15
+ date: z.string().describe("Note date (YYYY-MM-DD HH:MM:SS)"),
16
+ };
17
+ const paymentFields = {
18
+ projectid: z.number().describe("Project ID (use get_projects)"),
19
+ date: z.string().describe("Payment date (YYYY-MM-DD)"),
20
+ amount: z.number().describe("Payment amount"),
21
+ typeid: z.number().describe("Payment type ID (use get_payment_types)"),
22
+ invoiceid: z.number().describe("Invoice ID to apply the payment to (use get_invoices)"),
23
+ reference: z.string().describe("Reference number (max 35 characters)"),
24
+ note: z.string().describe("Payment note (max 255 characters)"),
25
+ };
26
+ const specs = [
27
+ // --- Invoices ---
28
+ {
29
+ name: "get_invoices",
30
+ description: "List invoices, optionally filtered by client or status (draft, sent, paid).",
31
+ method: "GET",
32
+ path: "/invoice",
33
+ schema: {
34
+ clientid: z.number().optional().describe("Filter by client ID"),
35
+ status: z.string().optional().describe("Invoice status: draft, sent, or paid"),
36
+ limit: limitParam,
37
+ offset: offsetParam,
38
+ },
39
+ },
40
+ {
41
+ name: "get_invoice",
42
+ description: "Get full details of a single invoice by ID.",
43
+ method: "GET",
44
+ path: "/invoice",
45
+ idParam: "invoiceId",
46
+ schema: { invoiceId: z.number().describe("Invoice ID") },
47
+ },
48
+ {
49
+ name: "create_invoice",
50
+ description: "Create an invoice for a project. Requires project, date, term ID, and title.",
51
+ method: "POST",
52
+ path: "/invoice",
53
+ schema: {
54
+ projectid: z.number().describe("Project ID the invoice is for (use get_projects)"),
55
+ date: z.string().describe("Invoice date (YYYY-MM-DD)"),
56
+ termid: z.number().describe("Invoice term ID, e.g. Net 30 (use get_invoice_terms)"),
57
+ title: z.string().describe("Invoice title (max 255 characters)"),
58
+ description: z.string().optional().describe("Invoice description/notes"),
59
+ termother: z.string().optional().describe("Custom payment terms text (max 255 characters)"),
60
+ termotherdatedue: z.string().optional().describe("Custom due date when using termother (YYYY-MM-DD)"),
61
+ addressfrom: z.string().optional().describe("From address on the invoice"),
62
+ addressto: z.string().optional().describe("To address on the invoice"),
63
+ purchaseorder: z.string().optional().describe("Purchase order number (max 55 characters)"),
64
+ tax: z.number().optional().describe("Tax rate percentage"),
65
+ datebegin: z.string().optional().describe("Billing period start date (YYYY-MM-DD)"),
66
+ dateend: z.string().optional().describe("Billing period end date (YYYY-MM-DD)"),
67
+ },
68
+ },
69
+ {
70
+ name: "get_invoice_terms",
71
+ description: "List invoice payment terms (e.g. Net 30, Upon Receipt). Use the IDs when creating invoices.",
72
+ method: "GET",
73
+ path: "/invoiceterm",
74
+ schema: {},
75
+ },
76
+ // --- Invoice items ---
77
+ {
78
+ name: "get_invoice_items",
79
+ description: "List line items for an invoice.",
80
+ method: "GET",
81
+ path: "/invoiceitem",
82
+ schema: {
83
+ invoiceid: z.number().describe("Invoice ID to list items for"),
84
+ limit: limitParam,
85
+ offset: offsetParam,
86
+ },
87
+ },
88
+ {
89
+ name: "create_invoice_item",
90
+ description: "Add a line item to an invoice.",
91
+ method: "POST",
92
+ path: "/invoiceitem",
93
+ schema: {
94
+ invoiceid: itemFields.invoiceid,
95
+ name: itemFields.name,
96
+ amount: itemFields.amount,
97
+ description: itemFields.description.optional(),
98
+ quantity: itemFields.quantity.optional(),
99
+ rate: itemFields.rate.optional(),
100
+ },
101
+ },
102
+ {
103
+ name: "update_invoice_item",
104
+ description: "Update an invoice line item. Only custom line items support quantity, rate, and amount changes.",
105
+ method: "PUT",
106
+ path: "/invoiceitem",
107
+ idParam: "invoiceItemId",
108
+ schema: {
109
+ invoiceItemId: z.number().describe("Invoice item ID to update"),
110
+ invoiceid: itemFields.invoiceid.optional(),
111
+ name: itemFields.name.optional(),
112
+ amount: itemFields.amount.optional(),
113
+ description: itemFields.description.optional(),
114
+ quantity: itemFields.quantity.optional(),
115
+ rate: itemFields.rate.optional(),
116
+ },
117
+ },
118
+ {
119
+ name: "delete_invoice_item",
120
+ description: "Delete a custom invoice line item by ID. Permanently deletes; cannot be undone.",
121
+ method: "DELETE",
122
+ path: "/invoiceitem",
123
+ idParam: "invoiceItemId",
124
+ schema: { invoiceItemId: z.number().describe("Invoice item ID to delete") },
125
+ },
126
+ // --- Invoice notes ---
127
+ {
128
+ name: "get_invoice_notes",
129
+ description: "List invoice notes, optionally for a single invoice.",
130
+ method: "GET",
131
+ path: "/invoicenote",
132
+ schema: {
133
+ invoiceid: z.number().optional().describe("Filter by invoice ID"),
134
+ limit: limitParam,
135
+ offset: offsetParam,
136
+ },
137
+ },
138
+ {
139
+ name: "create_invoice_note",
140
+ description: "Add a note to an invoice.",
141
+ method: "POST",
142
+ path: "/invoicenote",
143
+ schema: {
144
+ invoiceid: invoiceNoteFields.invoiceid,
145
+ title: invoiceNoteFields.title,
146
+ note: invoiceNoteFields.note.optional(),
147
+ date: invoiceNoteFields.date.optional(),
148
+ },
149
+ },
150
+ {
151
+ name: "update_invoice_note",
152
+ description: "Update an invoice note. Only provide fields you want to change.",
153
+ method: "PUT",
154
+ path: "/invoicenote",
155
+ idParam: "invoiceNoteId",
156
+ schema: {
157
+ invoiceNoteId: z.number().describe("Invoice note ID to update"),
158
+ invoiceid: invoiceNoteFields.invoiceid.optional(),
159
+ title: invoiceNoteFields.title.optional(),
160
+ note: invoiceNoteFields.note.optional(),
161
+ date: invoiceNoteFields.date.optional(),
162
+ },
163
+ },
164
+ {
165
+ name: "delete_invoice_note",
166
+ description: "Delete an invoice note by ID. Permanently deletes; cannot be undone.",
167
+ method: "DELETE",
168
+ path: "/invoicenote",
169
+ idParam: "invoiceNoteId",
170
+ schema: { invoiceNoteId: z.number().describe("Invoice note ID to delete") },
171
+ },
172
+ // --- Payments ---
173
+ {
174
+ name: "get_payments",
175
+ description: "List payments, optionally filtered by project or invoice.",
176
+ method: "GET",
177
+ path: "/payment",
178
+ schema: {
179
+ projectid: z.number().optional().describe("Filter by project ID"),
180
+ invoiceid: z.number().optional().describe("Filter by invoice ID"),
181
+ limit: limitParam,
182
+ offset: offsetParam,
183
+ },
184
+ },
185
+ {
186
+ name: "get_payment",
187
+ description: "Get full details of a single payment by ID.",
188
+ method: "GET",
189
+ path: "/payment",
190
+ idParam: "paymentId",
191
+ schema: { paymentId: z.number().describe("Payment ID") },
192
+ },
193
+ {
194
+ name: "create_payment",
195
+ description: "Record a payment against a project.",
196
+ method: "POST",
197
+ path: "/payment",
198
+ schema: {
199
+ projectid: paymentFields.projectid,
200
+ date: paymentFields.date,
201
+ amount: paymentFields.amount,
202
+ typeid: paymentFields.typeid,
203
+ invoiceid: paymentFields.invoiceid.optional(),
204
+ reference: paymentFields.reference.optional(),
205
+ note: paymentFields.note.optional(),
206
+ },
207
+ },
208
+ {
209
+ name: "update_payment",
210
+ description: "Update a payment. Only provide fields you want to change.",
211
+ method: "PUT",
212
+ path: "/payment",
213
+ idParam: "paymentId",
214
+ schema: {
215
+ paymentId: z.number().describe("Payment ID to update"),
216
+ projectid: paymentFields.projectid.optional(),
217
+ date: paymentFields.date.optional(),
218
+ amount: paymentFields.amount.optional(),
219
+ typeid: paymentFields.typeid.optional(),
220
+ invoiceid: paymentFields.invoiceid.optional(),
221
+ reference: paymentFields.reference.optional(),
222
+ note: paymentFields.note.optional(),
223
+ },
224
+ },
225
+ {
226
+ name: "delete_payment",
227
+ description: "Delete a payment by ID (Administrator only). Permanently deletes; cannot be undone.",
228
+ method: "DELETE",
229
+ path: "/payment",
230
+ idParam: "paymentId",
231
+ schema: { paymentId: z.number().describe("Payment ID to delete") },
232
+ },
233
+ {
234
+ name: "get_payment_types",
235
+ description: "List payment types (e.g. Cash, Check, Credit Card). Use the IDs when creating payments.",
236
+ method: "GET",
237
+ path: "/paymenttype",
238
+ schema: {},
239
+ },
240
+ ];
241
+ export function registerBillingTools(server, client) {
242
+ registerEndpoints(server, client, specs);
243
+ }
@@ -0,0 +1,3 @@
1
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { IntervalsClient } from "../client.js";
3
+ export declare function registerCoreTools(server: McpServer, client: IntervalsClient): void;