@procurementexpress.com/mcp 1.0.0 → 2.0.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/.claude/skills/bump-version/SKILL.md +77 -0
- package/.claude/skills/commit/SKILL.md +73 -0
- package/.claude/skills/npm-publish/SKILL.md +65 -0
- package/.claude/skills/pex-approval-flows/SKILL.md +122 -0
- package/.claude/skills/pex-approval-flows/references/conditions.md +90 -0
- package/.claude/skills/pex-auth/SKILL.md +80 -0
- package/.claude/skills/pex-budgets/SKILL.md +73 -0
- package/.claude/skills/pex-companies/SKILL.md +113 -0
- package/.claude/skills/pex-departments/SKILL.md +61 -0
- package/.claude/skills/pex-invoices/SKILL.md +125 -0
- package/.claude/skills/pex-invoices/references/line-items.md +55 -0
- package/.claude/skills/pex-payments/SKILL.md +79 -0
- package/.claude/skills/pex-purchase-orders/SKILL.md +167 -0
- package/.claude/skills/pex-purchase-orders/references/line-items.md +53 -0
- package/.claude/skills/pex-purchase-orders/references/workflows.md +74 -0
- package/.claude/skills/pex-settings/SKILL.md +128 -0
- package/.claude/skills/pex-suppliers/SKILL.md +113 -0
- package/README.md +118 -25
- package/dist/api-client.d.ts +1 -0
- package/dist/api-client.js +3 -0
- package/dist/tools/approval-flows.js +130 -25
- package/dist/tools/budgets.js +30 -20
- package/dist/tools/comments.js +4 -4
- package/dist/tools/companies.js +57 -7
- package/dist/tools/departments.js +6 -6
- package/dist/tools/invoices.js +100 -31
- package/dist/tools/payments.js +45 -13
- package/dist/tools/products.js +10 -5
- package/dist/tools/purchase-orders.js +178 -35
- package/dist/tools/supplementary.js +57 -14
- package/dist/tools/suppliers.js +38 -19
- package/dist/tools/tax-rates.js +15 -9
- package/dist/tools/users.js +8 -5
- package/dist/tools/webhooks.js +59 -9
- package/package.json +5 -4
|
@@ -1,81 +1,188 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { jsonResponse, textResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
|
+
const customFieldValueSchema = z.object({
|
|
4
|
+
id: z.number().int().optional().describe("Custom field value ID (for updates)"),
|
|
5
|
+
value: z.string().describe("Custom field value"),
|
|
6
|
+
custom_field_id: z.number().int().describe("Custom field ID"),
|
|
7
|
+
});
|
|
3
8
|
const lineItemSchema = z.object({
|
|
9
|
+
id: z.number().int().optional().describe("Line item ID (for updates)"),
|
|
4
10
|
description: z.string().describe("Item description"),
|
|
5
11
|
quantity: z.number().describe("Quantity"),
|
|
6
12
|
unit_price: z.number().describe("Unit price"),
|
|
7
13
|
budget_id: z.number().int().optional().describe("Budget ID"),
|
|
8
|
-
vat: z.number().optional().describe("VAT
|
|
9
|
-
item_number: z.string().optional().describe("Item number"),
|
|
10
|
-
sequence_no: z.number().int().optional().describe("Sequence number"),
|
|
14
|
+
vat: z.number().optional().describe("VAT/tax percentage"),
|
|
11
15
|
tax_rate_id: z.number().int().optional().describe("Tax rate ID"),
|
|
16
|
+
item_number: z.string().optional().describe("Item number"),
|
|
17
|
+
sequence_no: z.number().int().optional().describe("Sequence number for ordering"),
|
|
18
|
+
department_id: z.number().int().optional().describe("Department ID for the line item"),
|
|
19
|
+
product_id: z.number().int().optional().describe("Product ID"),
|
|
20
|
+
chart_of_account_id: z.number().int().optional().describe("Chart of account ID (GL code)"),
|
|
21
|
+
qbo_customer_id: z.number().int().optional().describe("QuickBooks customer ID"),
|
|
22
|
+
quickbooks_class_id: z.number().int().optional().describe("QuickBooks class ID"),
|
|
23
|
+
qbo_line_description: z.string().optional().describe("QuickBooks line description override"),
|
|
24
|
+
archived: z.boolean().optional().describe("Whether the line item is archived"),
|
|
25
|
+
_destroy: z.boolean().optional().describe("Set true to remove this line item on update"),
|
|
26
|
+
custom_field_values_attributes: z.array(customFieldValueSchema).optional().describe("Custom field values for this line item"),
|
|
12
27
|
});
|
|
13
28
|
export function registerPurchaseOrderTools(server, apiClient) {
|
|
14
29
|
server.registerTool("list_purchase_orders", {
|
|
15
|
-
description: "List purchase orders with pagination and
|
|
30
|
+
description: "List purchase orders with pagination, search, and filters. Returns paginated results with meta info (current_page, next_page, prev_page, total_pages, total_count).",
|
|
16
31
|
inputSchema: {
|
|
17
|
-
|
|
18
|
-
search: z.string().optional().describe("Search term"),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
32
|
+
page: z.number().int().positive().optional().describe("Page number (default: 1). Use 'orders_page' as alias."),
|
|
33
|
+
search: z.string().optional().describe("Search term — matches PO number, supplier name, notes, line item descriptions"),
|
|
34
|
+
status: z
|
|
35
|
+
.enum(["draft", "pending", "approved", "rejected", "cancelled", "paid"])
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Filter by PO status"),
|
|
38
|
+
delivery_status: z
|
|
39
|
+
.enum(["not_delivered", "partially_delivered", "complete_delivered"])
|
|
40
|
+
.optional()
|
|
41
|
+
.describe("Filter by delivery status"),
|
|
42
|
+
payment_status: z
|
|
43
|
+
.enum(["unpaid", "partially_paid", "paid", "invoice_received"])
|
|
44
|
+
.optional()
|
|
45
|
+
.describe("Filter by payment status"),
|
|
46
|
+
supplier_id: z.number().int().optional().describe("Filter by supplier ID"),
|
|
47
|
+
requester_id: z.number().int().optional().describe("Filter by creator/requester user ID"),
|
|
48
|
+
budget_id: z.number().int().optional().describe("Filter by budget ID"),
|
|
49
|
+
filter_dept_id: z.number().int().optional().describe("Filter by department ID"),
|
|
50
|
+
approver_id: z.number().int().optional().describe("Filter by approver user ID"),
|
|
51
|
+
archived: z.boolean().optional().describe("Filter archived POs (default: false)"),
|
|
52
|
+
date_filter: z
|
|
53
|
+
.enum(["current_month", "current_year", "last_month", "last_year"])
|
|
54
|
+
.optional()
|
|
55
|
+
.describe("Predefined date range filter"),
|
|
56
|
+
from: z.string().optional().describe("Custom date range start (format depends on company date_format setting)"),
|
|
57
|
+
to: z.string().optional().describe("Custom date range end"),
|
|
58
|
+
updated_after: z.string().optional().describe("ISO datetime — only return POs updated after this timestamp (includes line item and custom field changes)"),
|
|
59
|
+
sort: z.string().optional().describe("Sort column (e.g. 'submitted_on', 'total_gross_amount', 'suppliers.name')"),
|
|
60
|
+
direction: z.enum(["asc", "desc"]).optional().describe("Sort direction (default: desc)"),
|
|
61
|
+
requests: z.boolean().optional().describe("Set true to include pending approval requests"),
|
|
62
|
+
bell: z.boolean().optional().describe("Set true with requests=true to show only bell notification items"),
|
|
22
63
|
},
|
|
23
64
|
}, withErrorHandling(async (args) => {
|
|
24
65
|
const params = new URLSearchParams();
|
|
25
|
-
if (args.
|
|
26
|
-
params.set("orders_page", String(args.
|
|
66
|
+
if (args.page)
|
|
67
|
+
params.set("orders_page", String(args.page));
|
|
27
68
|
if (args.search)
|
|
28
69
|
params.set("search", args.search);
|
|
29
|
-
if (args.
|
|
30
|
-
params.set("
|
|
70
|
+
if (args.status)
|
|
71
|
+
params.set("status", args.status);
|
|
72
|
+
if (args.delivery_status)
|
|
73
|
+
params.set("delivery_status", args.delivery_status);
|
|
74
|
+
if (args.payment_status)
|
|
75
|
+
params.set("payment_status", args.payment_status);
|
|
76
|
+
if (args.supplier_id)
|
|
77
|
+
params.set("supplier_id", String(args.supplier_id));
|
|
78
|
+
if (args.requester_id)
|
|
79
|
+
params.set("requester_id", String(args.requester_id));
|
|
80
|
+
if (args.budget_id)
|
|
81
|
+
params.set("budget_id", String(args.budget_id));
|
|
82
|
+
if (args.filter_dept_id)
|
|
83
|
+
params.set("filter_dept_id", String(args.filter_dept_id));
|
|
84
|
+
if (args.approver_id)
|
|
85
|
+
params.set("approver_id", String(args.approver_id));
|
|
86
|
+
if (args.archived !== undefined)
|
|
87
|
+
params.set("archived", String(args.archived));
|
|
88
|
+
if (args.date_filter)
|
|
89
|
+
params.set("date_filter", args.date_filter);
|
|
90
|
+
if (args.from)
|
|
91
|
+
params.set("from", args.from);
|
|
92
|
+
if (args.to)
|
|
93
|
+
params.set("to", args.to);
|
|
94
|
+
if (args.updated_after)
|
|
95
|
+
params.set("updated_after", args.updated_after);
|
|
31
96
|
if (args.sort)
|
|
32
97
|
params.set("sort", args.sort);
|
|
33
98
|
if (args.direction)
|
|
34
99
|
params.set("direction", args.direction);
|
|
100
|
+
if (args.requests)
|
|
101
|
+
params.set("requests", "true");
|
|
102
|
+
if (args.bell)
|
|
103
|
+
params.set("bell", "true");
|
|
35
104
|
const query = params.toString();
|
|
36
105
|
const path = `${apiClient.buildPath("/purchase_orders")}${query ? `?${query}` : ""}`;
|
|
37
106
|
const result = await apiClient.get(path);
|
|
38
107
|
return jsonResponse(result);
|
|
39
108
|
}));
|
|
40
109
|
server.registerTool("get_purchase_order", {
|
|
41
|
-
description: "Get purchase order details including line items, comments, approvals, and status flags",
|
|
110
|
+
description: "Get purchase order details including line items, comments, approvals, and status flags. The ID parameter accepts a numeric ID, approval-key, or slug.",
|
|
42
111
|
inputSchema: {
|
|
43
|
-
id: z.
|
|
112
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
44
113
|
},
|
|
45
114
|
}, withErrorHandling(async (args) => {
|
|
46
115
|
const po = await apiClient.get(apiClient.buildPath(`/purchase_orders/${args.id}`));
|
|
47
116
|
return jsonResponse(po);
|
|
48
117
|
}));
|
|
49
118
|
server.registerTool("create_purchase_order", {
|
|
50
|
-
description: "Create a new purchase order. Use commit='Send' to submit or 'Draft' to save as draft",
|
|
119
|
+
description: "Create a new purchase order. Use commit='Send' to submit for approval or 'Draft' to save as draft. At least one line item is required.",
|
|
51
120
|
inputSchema: {
|
|
52
121
|
commit: z.enum(["Send", "Draft"]).describe("'Send' to submit for approval, 'Draft' to save as draft"),
|
|
53
122
|
department_id: z.number().int().optional().describe("Department ID"),
|
|
54
123
|
creator_id: z.number().int().describe("Creator user ID"),
|
|
55
|
-
supplier_id: z.number().int().optional().describe("
|
|
56
|
-
supplier_name: z.string().optional().describe("Supplier name (
|
|
57
|
-
|
|
58
|
-
|
|
124
|
+
supplier_id: z.number().int().optional().describe("Existing supplier ID"),
|
|
125
|
+
supplier_name: z.string().optional().describe("Supplier name (used when supplier_id is not provided)"),
|
|
126
|
+
new_supplier_name: z.string().optional().describe("Create a new supplier with this name"),
|
|
127
|
+
currency_id: z.number().int().optional().describe("Currency ID (defaults to company/user currency)"),
|
|
128
|
+
iso_code: z.string().optional().describe("Currency ISO code (e.g. 'USD') — alternative to currency_id"),
|
|
129
|
+
notes: z.string().optional().describe("PO notes/description"),
|
|
130
|
+
submitted_on: z.string().optional().describe("Submission date (format depends on company date_format)"),
|
|
131
|
+
on_behalf_of: z.number().int().optional().describe("Create PO on behalf of this user ID (companyadmin only, user must be active employee)"),
|
|
59
132
|
line_items: z.array(lineItemSchema).min(1).describe("Line items (at least one required)"),
|
|
60
|
-
approver_list: z.array(z.number().int()).optional().describe("Approver IDs"),
|
|
133
|
+
approver_list: z.array(z.number().int()).optional().describe("Approver user IDs"),
|
|
134
|
+
custom_field_values_attributes: z.array(customFieldValueSchema).optional().describe("PO-level custom field values"),
|
|
61
135
|
},
|
|
62
136
|
}, withErrorHandling(async (args) => {
|
|
63
|
-
const { commit, line_items, approver_list, ...poData } = args;
|
|
137
|
+
const { commit, line_items, approver_list, iso_code, on_behalf_of, custom_field_values_attributes, ...poData } = args;
|
|
64
138
|
const body = {
|
|
65
139
|
commit,
|
|
66
140
|
purchase_order: {
|
|
67
141
|
...poData,
|
|
142
|
+
...(iso_code ? { iso_code } : {}),
|
|
143
|
+
...(on_behalf_of ? { on_behalf_of } : {}),
|
|
68
144
|
purchase_order_items_attributes: line_items,
|
|
69
145
|
...(approver_list ? { approver_list } : {}),
|
|
146
|
+
...(custom_field_values_attributes ? { custom_field_values_attributes } : {}),
|
|
70
147
|
},
|
|
71
148
|
};
|
|
72
149
|
const po = await apiClient.post(apiClient.buildPath("/purchase_orders"), body);
|
|
73
150
|
return jsonResponse(po);
|
|
74
151
|
}));
|
|
152
|
+
server.registerTool("update_purchase_order", {
|
|
153
|
+
description: "Update an existing purchase order. Use commit='Send' to submit a draft for approval. The ID accepts numeric ID, approval-key, or slug.",
|
|
154
|
+
inputSchema: {
|
|
155
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
156
|
+
commit: z.enum(["Send", "Draft"]).optional().describe("'Send' to submit draft for approval"),
|
|
157
|
+
department_id: z.number().int().optional().describe("Department ID"),
|
|
158
|
+
supplier_id: z.number().int().optional().describe("Supplier ID"),
|
|
159
|
+
supplier_name: z.string().optional().describe("Supplier name"),
|
|
160
|
+
new_supplier_name: z.string().optional().describe("Create a new supplier with this name"),
|
|
161
|
+
currency_id: z.number().int().optional().describe("Currency ID"),
|
|
162
|
+
notes: z.string().optional().describe("PO notes"),
|
|
163
|
+
submitted_on: z.string().optional().describe("Submission date"),
|
|
164
|
+
line_items: z.array(lineItemSchema).optional().describe("Line items (include id to update existing, _destroy to remove)"),
|
|
165
|
+
approver_list: z.array(z.number().int()).optional().describe("Approver user IDs"),
|
|
166
|
+
custom_field_values_attributes: z.array(customFieldValueSchema).optional().describe("PO-level custom field values"),
|
|
167
|
+
},
|
|
168
|
+
}, withErrorHandling(async (args) => {
|
|
169
|
+
const { id, commit, line_items, approver_list, custom_field_values_attributes, ...poData } = args;
|
|
170
|
+
const body = {
|
|
171
|
+
...(commit ? { commit } : {}),
|
|
172
|
+
purchase_order: {
|
|
173
|
+
...poData,
|
|
174
|
+
...(line_items ? { purchase_order_items_attributes: line_items } : {}),
|
|
175
|
+
...(approver_list ? { approver_list } : {}),
|
|
176
|
+
...(custom_field_values_attributes ? { custom_field_values_attributes } : {}),
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
const po = await apiClient.put(apiClient.buildPath(`/purchase_orders/${id}`), body);
|
|
180
|
+
return jsonResponse(po);
|
|
181
|
+
}));
|
|
75
182
|
server.registerTool("approve_purchase_order", {
|
|
76
183
|
description: "Approve a purchase order using the accept token from the approver request",
|
|
77
184
|
inputSchema: {
|
|
78
|
-
id: z.
|
|
185
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
79
186
|
token: z.string().describe("Accept token from the approver request"),
|
|
80
187
|
},
|
|
81
188
|
}, withErrorHandling(async (args) => {
|
|
@@ -85,7 +192,7 @@ export function registerPurchaseOrderTools(server, apiClient) {
|
|
|
85
192
|
server.registerTool("reject_purchase_order", {
|
|
86
193
|
description: "Reject a purchase order using the reject token from the approver request",
|
|
87
194
|
inputSchema: {
|
|
88
|
-
id: z.
|
|
195
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
89
196
|
token: z.string().describe("Reject token from the approver request"),
|
|
90
197
|
},
|
|
91
198
|
}, withErrorHandling(async (args) => {
|
|
@@ -93,50 +200,68 @@ export function registerPurchaseOrderTools(server, apiClient) {
|
|
|
93
200
|
return jsonResponse(result);
|
|
94
201
|
}));
|
|
95
202
|
server.registerTool("override_and_approve_purchase_order", {
|
|
96
|
-
description: "Override and approve a purchase order (finance
|
|
203
|
+
description: "Override and approve a purchase order (finance role required, no token needed)",
|
|
97
204
|
inputSchema: {
|
|
98
|
-
id: z.
|
|
205
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
99
206
|
},
|
|
100
207
|
}, withErrorHandling(async (args) => {
|
|
101
208
|
const result = await apiClient.get(apiClient.buildPath(`/purchase_orders/${args.id}/override_and_approve`));
|
|
102
209
|
return jsonResponse(result);
|
|
103
210
|
}));
|
|
104
211
|
server.registerTool("cancel_purchase_order", {
|
|
105
|
-
description: "Cancel a purchase order",
|
|
212
|
+
description: "Cancel a purchase order (requires cancel permission)",
|
|
106
213
|
inputSchema: {
|
|
107
|
-
id: z.
|
|
214
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
108
215
|
},
|
|
109
216
|
}, withErrorHandling(async (args) => {
|
|
110
217
|
const result = await apiClient.post(apiClient.buildPath(`/purchase_orders/${args.id}/cancel`));
|
|
111
218
|
return jsonResponse(result);
|
|
112
219
|
}));
|
|
113
220
|
server.registerTool("archive_purchase_order", {
|
|
114
|
-
description: "
|
|
221
|
+
description: "Toggle archive status of a purchase order (finance role required). Calling again will dearchive.",
|
|
115
222
|
inputSchema: {
|
|
116
|
-
id: z.
|
|
223
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
117
224
|
},
|
|
118
225
|
}, withErrorHandling(async (args) => {
|
|
119
226
|
const result = await apiClient.post(apiClient.buildPath(`/purchase_orders/${args.id}/archive`));
|
|
120
227
|
return jsonResponse(result);
|
|
121
228
|
}));
|
|
229
|
+
server.registerTool("delete_purchase_order", {
|
|
230
|
+
description: "Permanently delete a purchase order (requires destroy permission)",
|
|
231
|
+
inputSchema: {
|
|
232
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
233
|
+
},
|
|
234
|
+
}, withErrorHandling(async (args) => {
|
|
235
|
+
const result = await apiClient.delete(apiClient.buildPath(`/purchase_orders/${args.id}/delete`));
|
|
236
|
+
return jsonResponse(result);
|
|
237
|
+
}));
|
|
238
|
+
server.registerTool("generate_purchase_order_pdf", {
|
|
239
|
+
description: "Generate a PDF for a purchase order and return a download link",
|
|
240
|
+
inputSchema: {
|
|
241
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
242
|
+
},
|
|
243
|
+
}, withErrorHandling(async (args) => {
|
|
244
|
+
const result = await apiClient.get(apiClient.buildPath(`/purchase_orders/${args.id}/generate_pdf`));
|
|
245
|
+
return jsonResponse(result);
|
|
246
|
+
}));
|
|
122
247
|
server.registerTool("get_pending_request_count", {
|
|
123
|
-
description: "Get the count of pending approval requests",
|
|
248
|
+
description: "Get the count of pending approval requests for the current user",
|
|
124
249
|
inputSchema: {},
|
|
125
250
|
}, withErrorHandling(async () => {
|
|
126
251
|
const result = await apiClient.get(apiClient.buildPath("/purchase_orders/pending_request_count"));
|
|
127
252
|
return textResponse(`Pending requests: ${result.total_pending_request}`);
|
|
128
253
|
}));
|
|
129
254
|
server.registerTool("receive_purchase_order_items", {
|
|
130
|
-
description: "Mark items as received for a purchase order",
|
|
255
|
+
description: "Mark line items as received (partial or full delivery) for a purchase order",
|
|
131
256
|
inputSchema: {
|
|
132
|
-
id: z.
|
|
257
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
133
258
|
items: z
|
|
134
259
|
.array(z.object({
|
|
135
|
-
id: z.number().int().describe("PO item ID"),
|
|
260
|
+
id: z.number().int().describe("PO line item ID"),
|
|
136
261
|
quantity: z.number().describe("Quantity received"),
|
|
137
262
|
}))
|
|
138
263
|
.describe("Items with received quantities"),
|
|
139
|
-
delivered_on: z.string().describe("Delivery date"),
|
|
264
|
+
delivered_on: z.string().describe("Delivery date (format depends on company date_format)"),
|
|
140
265
|
notes: z.string().optional().describe("Delivery notes"),
|
|
141
266
|
},
|
|
142
267
|
}, withErrorHandling(async (args) => {
|
|
@@ -149,4 +274,22 @@ export function registerPurchaseOrderTools(server, apiClient) {
|
|
|
149
274
|
});
|
|
150
275
|
return jsonResponse(result);
|
|
151
276
|
}));
|
|
277
|
+
server.registerTool("cancel_receiving_items", {
|
|
278
|
+
description: "Cancel all received deliveries for a purchase order, reverting to not delivered status",
|
|
279
|
+
inputSchema: {
|
|
280
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
281
|
+
},
|
|
282
|
+
}, withErrorHandling(async (args) => {
|
|
283
|
+
const result = await apiClient.post(apiClient.buildPath(`/purchase_orders/${args.id}/cancel_receiving_items`));
|
|
284
|
+
return jsonResponse(result);
|
|
285
|
+
}));
|
|
286
|
+
server.registerTool("complete_purchase_order_delivery", {
|
|
287
|
+
description: "Mark a purchase order as fully delivered",
|
|
288
|
+
inputSchema: {
|
|
289
|
+
id: z.string().describe("Purchase Order ID, approval-key, or slug"),
|
|
290
|
+
},
|
|
291
|
+
}, withErrorHandling(async (args) => {
|
|
292
|
+
const result = await apiClient.post(apiClient.buildPath(`/purchase_orders/${args.id}/complete_delivery`));
|
|
293
|
+
return jsonResponse(result);
|
|
294
|
+
}));
|
|
152
295
|
}
|
|
@@ -2,47 +2,92 @@ import { z } from "zod";
|
|
|
2
2
|
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
3
|
export function registerSupplementaryTools(server, apiClient) {
|
|
4
4
|
server.registerTool("list_chart_of_accounts", {
|
|
5
|
-
description: "List chart of accounts with optional search",
|
|
5
|
+
description: "List chart of accounts (GL codes) with pagination and optional search",
|
|
6
6
|
inputSchema: {
|
|
7
|
-
search: z.string().optional().describe("Search
|
|
7
|
+
search: z.string().optional().describe("Search by account name or code"),
|
|
8
|
+
page: z.number().int().positive().optional().describe("Page number (default: 1)"),
|
|
9
|
+
per_page: z.number().int().positive().optional().describe("Results per page (default: company setting)"),
|
|
8
10
|
},
|
|
9
11
|
}, withErrorHandling(async (args) => {
|
|
10
12
|
const params = new URLSearchParams();
|
|
11
13
|
if (args.search)
|
|
12
14
|
params.set("search", args.search);
|
|
15
|
+
if (args.page)
|
|
16
|
+
params.set("page", String(args.page));
|
|
17
|
+
if (args.per_page)
|
|
18
|
+
params.set("per_page", String(args.per_page));
|
|
13
19
|
const query = params.toString();
|
|
14
20
|
const path = `${apiClient.buildPath("/chart_of_accounts")}${query ? `?${query}` : ""}`;
|
|
15
21
|
const result = await apiClient.get(path);
|
|
16
22
|
return jsonResponse(result);
|
|
17
23
|
}));
|
|
24
|
+
server.registerTool("get_chart_of_account", {
|
|
25
|
+
description: "Get a specific chart of account by ID",
|
|
26
|
+
inputSchema: {
|
|
27
|
+
id: z.number().int().positive().describe("Chart of Account ID"),
|
|
28
|
+
},
|
|
29
|
+
}, withErrorHandling(async (args) => {
|
|
30
|
+
const result = await apiClient.get(apiClient.buildPath(`/chart_of_accounts/${args.id}`));
|
|
31
|
+
return jsonResponse(result);
|
|
32
|
+
}));
|
|
18
33
|
server.registerTool("list_qbo_customers", {
|
|
19
|
-
description: "List QuickBooks customers with optional search",
|
|
34
|
+
description: "List QuickBooks customers with pagination and optional search",
|
|
20
35
|
inputSchema: {
|
|
21
|
-
search: z.string().optional().describe("Search
|
|
36
|
+
search: z.string().optional().describe("Search by customer name"),
|
|
37
|
+
page: z.number().int().positive().optional().describe("Page number (default: 1)"),
|
|
38
|
+
per_page: z.number().int().positive().optional().describe("Results per page"),
|
|
22
39
|
},
|
|
23
40
|
}, withErrorHandling(async (args) => {
|
|
24
41
|
const params = new URLSearchParams();
|
|
25
42
|
if (args.search)
|
|
26
43
|
params.set("search", args.search);
|
|
44
|
+
if (args.page)
|
|
45
|
+
params.set("page", String(args.page));
|
|
46
|
+
if (args.per_page)
|
|
47
|
+
params.set("per_page", String(args.per_page));
|
|
27
48
|
const query = params.toString();
|
|
28
49
|
const path = `${apiClient.buildPath("/qbo_customers")}${query ? `?${query}` : ""}`;
|
|
29
50
|
const result = await apiClient.get(path);
|
|
30
51
|
return jsonResponse(result);
|
|
31
52
|
}));
|
|
53
|
+
server.registerTool("get_qbo_customer", {
|
|
54
|
+
description: "Get a specific QuickBooks customer by ID",
|
|
55
|
+
inputSchema: {
|
|
56
|
+
id: z.number().int().positive().describe("QBO Customer ID"),
|
|
57
|
+
},
|
|
58
|
+
}, withErrorHandling(async (args) => {
|
|
59
|
+
const result = await apiClient.get(apiClient.buildPath(`/qbo_customers/${args.id}`));
|
|
60
|
+
return jsonResponse(result);
|
|
61
|
+
}));
|
|
32
62
|
server.registerTool("list_qbo_classes", {
|
|
33
|
-
description: "List QuickBooks classes with optional search",
|
|
63
|
+
description: "List QuickBooks classes with pagination and optional search",
|
|
34
64
|
inputSchema: {
|
|
35
|
-
search: z.string().optional().describe("Search
|
|
65
|
+
search: z.string().optional().describe("Search by class name"),
|
|
66
|
+
page: z.number().int().positive().optional().describe("Page number (default: 1)"),
|
|
67
|
+
per_page: z.number().int().positive().optional().describe("Results per page"),
|
|
36
68
|
},
|
|
37
69
|
}, withErrorHandling(async (args) => {
|
|
38
70
|
const params = new URLSearchParams();
|
|
39
71
|
if (args.search)
|
|
40
72
|
params.set("search", args.search);
|
|
73
|
+
if (args.page)
|
|
74
|
+
params.set("page", String(args.page));
|
|
75
|
+
if (args.per_page)
|
|
76
|
+
params.set("per_page", String(args.per_page));
|
|
41
77
|
const query = params.toString();
|
|
42
78
|
const path = `${apiClient.buildPath("/qbo_classes")}${query ? `?${query}` : ""}`;
|
|
43
79
|
const result = await apiClient.get(path);
|
|
44
80
|
return jsonResponse(result);
|
|
45
81
|
}));
|
|
82
|
+
server.registerTool("get_qbo_class", {
|
|
83
|
+
description: "Get a specific QuickBooks class by ID",
|
|
84
|
+
inputSchema: {
|
|
85
|
+
id: z.number().int().positive().describe("QuickBooks Class ID"),
|
|
86
|
+
},
|
|
87
|
+
}, withErrorHandling(async (args) => {
|
|
88
|
+
const result = await apiClient.get(apiClient.buildPath(`/qbo_classes/${args.id}`));
|
|
89
|
+
return jsonResponse(result);
|
|
90
|
+
}));
|
|
46
91
|
server.registerTool("list_send_to_supplier_templates", {
|
|
47
92
|
description: "List email templates for sending POs to suppliers",
|
|
48
93
|
inputSchema: {},
|
|
@@ -51,16 +96,14 @@ export function registerSupplementaryTools(server, apiClient) {
|
|
|
51
96
|
return jsonResponse(templates);
|
|
52
97
|
}));
|
|
53
98
|
server.registerTool("forward_purchase_order", {
|
|
54
|
-
description: "Forward a purchase order to supplier(s) via email",
|
|
99
|
+
description: "Forward a purchase order to supplier(s) via email. The PO PDF is attached automatically.",
|
|
55
100
|
inputSchema: {
|
|
56
101
|
purchase_order_id: z.number().int().positive().describe("Purchase Order ID"),
|
|
57
|
-
emails: z.
|
|
58
|
-
cc: z.string().
|
|
59
|
-
|
|
60
|
-
email_subject: z.string().optional().describe("Email subject"),
|
|
61
|
-
|
|
62
|
-
save_template: z.boolean().optional().describe("Save as new template"),
|
|
63
|
-
is_default: z.boolean().optional().describe("Mark as default template"),
|
|
102
|
+
emails: z.string().describe("Comma-separated recipient email addresses"),
|
|
103
|
+
cc: z.string().optional().describe("CC email address (defaults to PO creator's email)"),
|
|
104
|
+
note: z.string().optional().describe("Email body / note text"),
|
|
105
|
+
email_subject: z.string().optional().describe("Email subject line"),
|
|
106
|
+
uploads: z.array(z.number().int()).optional().describe("Upload IDs to attach (must belong to this PO)"),
|
|
64
107
|
},
|
|
65
108
|
}, withErrorHandling(async (args) => {
|
|
66
109
|
const { purchase_order_id, ...data } = args;
|
package/dist/tools/suppliers.js
CHANGED
|
@@ -2,27 +2,45 @@ import { z } from "zod";
|
|
|
2
2
|
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
3
|
export function registerSupplierTools(server, apiClient) {
|
|
4
4
|
server.registerTool("list_suppliers", {
|
|
5
|
-
description: "List suppliers with
|
|
5
|
+
description: "List suppliers. When page param is provided, returns paginated response with meta (20 per page). Without page param, returns all suppliers as an array. Supports search by name and filtering by department/archived status.",
|
|
6
6
|
inputSchema: {
|
|
7
|
-
page: z.number().int().positive().optional().describe("Page number"),
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
page: z.number().int().positive().optional().describe("Page number — enables pagination (20 per page)"),
|
|
8
|
+
search: z.string().optional().describe("Search suppliers by name"),
|
|
9
|
+
department_id: z.number().int().optional().describe("Filter by department ID (includes suppliers without departments)"),
|
|
10
|
+
archived: z.boolean().optional().describe("Filter by archived status (default: false)"),
|
|
11
|
+
show_mappings: z.boolean().optional().describe("Include third-party ID mappings in response"),
|
|
11
12
|
},
|
|
12
13
|
}, withErrorHandling(async (args) => {
|
|
13
14
|
const params = new URLSearchParams();
|
|
14
15
|
if (args.page)
|
|
15
16
|
params.set("page", String(args.page));
|
|
17
|
+
if (args.search)
|
|
18
|
+
params.set("search", args.search);
|
|
16
19
|
if (args.department_id)
|
|
17
20
|
params.set("department_id", String(args.department_id));
|
|
18
21
|
if (args.archived !== undefined)
|
|
19
22
|
params.set("archived", String(args.archived));
|
|
23
|
+
if (args.show_mappings)
|
|
24
|
+
params.set("show_mappings", "true");
|
|
25
|
+
const query = params.toString();
|
|
26
|
+
const path = `${apiClient.buildPath("/suppliers")}${query ? `?${query}` : ""}`;
|
|
27
|
+
const result = await apiClient.get(path);
|
|
28
|
+
return jsonResponse(result);
|
|
29
|
+
}));
|
|
30
|
+
server.registerTool("get_top_suppliers", {
|
|
31
|
+
description: "Get the user's most frequently used suppliers",
|
|
32
|
+
inputSchema: {
|
|
33
|
+
top: z.number().int().positive().optional().describe("Number of top suppliers to return (default: 5)"),
|
|
34
|
+
archived: z.boolean().optional().describe("Include archived suppliers"),
|
|
35
|
+
},
|
|
36
|
+
}, withErrorHandling(async (args) => {
|
|
37
|
+
const params = new URLSearchParams();
|
|
20
38
|
if (args.top)
|
|
21
39
|
params.set("top", String(args.top));
|
|
40
|
+
if (args.archived !== undefined)
|
|
41
|
+
params.set("archived", String(args.archived));
|
|
22
42
|
const query = params.toString();
|
|
23
|
-
const path =
|
|
24
|
-
? `${apiClient.buildPath("/suppliers/top")}?${query}`
|
|
25
|
-
: `${apiClient.buildPath("/suppliers")}${query ? `?${query}` : ""}`;
|
|
43
|
+
const path = `${apiClient.buildPath("/suppliers/top")}${query ? `?${query}` : ""}`;
|
|
26
44
|
const result = await apiClient.get(path);
|
|
27
45
|
return jsonResponse(result);
|
|
28
46
|
}));
|
|
@@ -36,24 +54,22 @@ export function registerSupplierTools(server, apiClient) {
|
|
|
36
54
|
return jsonResponse(supplier);
|
|
37
55
|
}));
|
|
38
56
|
server.registerTool("create_supplier", {
|
|
39
|
-
description: "Create a new supplier (name must be unique)",
|
|
57
|
+
description: "Create a new supplier (name must be unique within the company). If company has supplier approval enabled, creates a pending approval request instead.",
|
|
40
58
|
inputSchema: {
|
|
41
59
|
name: z.string().describe("Supplier name (must be unique)"),
|
|
42
|
-
email: z.string().
|
|
60
|
+
email: z.string().optional().describe("Supplier email"),
|
|
43
61
|
address: z.string().optional().describe("Address"),
|
|
44
62
|
notes: z.string().optional().describe("Notes"),
|
|
45
|
-
payment_details: z.string().optional().describe("Payment details"),
|
|
63
|
+
payment_details: z.string().optional().describe("Payment details/bank info"),
|
|
46
64
|
phone_number: z.string().optional().describe("Phone number"),
|
|
47
65
|
tax_number: z.string().optional().describe("Tax number"),
|
|
48
|
-
contact_person: z.string().optional().describe("Contact person"),
|
|
49
|
-
|
|
66
|
+
contact_person: z.string().optional().describe("Contact person name"),
|
|
67
|
+
uei: z.string().optional().describe("Unique Entity Identifier (UEI) for SAM.gov"),
|
|
68
|
+
cage_code: z.string().optional().describe("CAGE code for government contracting"),
|
|
69
|
+
department_ids: z.array(z.number().int()).optional().describe("Department IDs to restrict supplier to"),
|
|
50
70
|
},
|
|
51
71
|
}, withErrorHandling(async (args) => {
|
|
52
|
-
const
|
|
53
|
-
const body = { supplier: supplierData };
|
|
54
|
-
if (department_ids)
|
|
55
|
-
body.department_ids = department_ids;
|
|
56
|
-
const supplier = await apiClient.post(apiClient.buildPath("/suppliers"), body);
|
|
72
|
+
const supplier = await apiClient.post(apiClient.buildPath("/suppliers"), { supplier: args });
|
|
57
73
|
return jsonResponse(supplier);
|
|
58
74
|
}));
|
|
59
75
|
server.registerTool("update_supplier", {
|
|
@@ -61,7 +77,7 @@ export function registerSupplierTools(server, apiClient) {
|
|
|
61
77
|
inputSchema: {
|
|
62
78
|
id: z.number().int().positive().describe("Supplier ID"),
|
|
63
79
|
name: z.string().optional().describe("Supplier name"),
|
|
64
|
-
email: z.string().
|
|
80
|
+
email: z.string().optional().describe("Email"),
|
|
65
81
|
address: z.string().optional().describe("Address"),
|
|
66
82
|
notes: z.string().optional().describe("Notes"),
|
|
67
83
|
payment_details: z.string().optional().describe("Payment details"),
|
|
@@ -69,6 +85,9 @@ export function registerSupplierTools(server, apiClient) {
|
|
|
69
85
|
archived: z.boolean().optional().describe("Archive status"),
|
|
70
86
|
tax_number: z.string().optional().describe("Tax number"),
|
|
71
87
|
contact_person: z.string().optional().describe("Contact person"),
|
|
88
|
+
uei: z.string().optional().describe("Unique Entity Identifier (UEI)"),
|
|
89
|
+
cage_code: z.string().optional().describe("CAGE code"),
|
|
90
|
+
department_ids: z.array(z.number().int()).optional().describe("Department IDs"),
|
|
72
91
|
},
|
|
73
92
|
}, withErrorHandling(async (args) => {
|
|
74
93
|
const { id, ...data } = args;
|
package/dist/tools/tax-rates.js
CHANGED
|
@@ -2,10 +2,17 @@ import { z } from "zod";
|
|
|
2
2
|
import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
|
|
3
3
|
export function registerTaxRateTools(server, apiClient) {
|
|
4
4
|
server.registerTool("list_tax_rates", {
|
|
5
|
-
description: "List
|
|
6
|
-
inputSchema: {
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
description: "List tax rates for the current company, filtered by archived status",
|
|
6
|
+
inputSchema: {
|
|
7
|
+
archived: z.boolean().optional().describe("Filter by archived status (default: false)"),
|
|
8
|
+
},
|
|
9
|
+
}, withErrorHandling(async (args) => {
|
|
10
|
+
const params = new URLSearchParams();
|
|
11
|
+
if (args.archived !== undefined)
|
|
12
|
+
params.set("archived", String(args.archived));
|
|
13
|
+
const query = params.toString();
|
|
14
|
+
const path = `${apiClient.buildPath("/tax_rates")}${query ? `?${query}` : ""}`;
|
|
15
|
+
const taxRates = await apiClient.get(path);
|
|
9
16
|
return jsonResponse(taxRates);
|
|
10
17
|
}));
|
|
11
18
|
server.registerTool("get_tax_rate", {
|
|
@@ -18,11 +25,10 @@ export function registerTaxRateTools(server, apiClient) {
|
|
|
18
25
|
return jsonResponse(taxRate);
|
|
19
26
|
}));
|
|
20
27
|
server.registerTool("create_tax_rate", {
|
|
21
|
-
description: "Create a new tax rate",
|
|
28
|
+
description: "Create a new tax rate for the current company",
|
|
22
29
|
inputSchema: {
|
|
23
|
-
name: z.string().describe("Tax rate name"),
|
|
24
|
-
value: z.number().describe("Tax rate value (
|
|
25
|
-
company_id: z.number().int().describe("Company ID"),
|
|
30
|
+
name: z.string().describe("Tax rate name (e.g. 'VAT 20%')"),
|
|
31
|
+
value: z.number().describe("Tax rate percentage value (e.g. 20 for 20%)"),
|
|
26
32
|
},
|
|
27
33
|
}, withErrorHandling(async (args) => {
|
|
28
34
|
const taxRate = await apiClient.post(apiClient.buildPath("/tax_rates"), { tax_rate: args });
|
|
@@ -33,7 +39,7 @@ export function registerTaxRateTools(server, apiClient) {
|
|
|
33
39
|
inputSchema: {
|
|
34
40
|
id: z.number().int().positive().describe("Tax Rate ID"),
|
|
35
41
|
name: z.string().optional().describe("Tax rate name"),
|
|
36
|
-
value: z.number().optional().describe("Tax rate value"),
|
|
42
|
+
value: z.number().optional().describe("Tax rate percentage value"),
|
|
37
43
|
archived: z.boolean().optional().describe("Archive status"),
|
|
38
44
|
},
|
|
39
45
|
}, withErrorHandling(async (args) => {
|
package/dist/tools/users.js
CHANGED
|
@@ -9,26 +9,29 @@ export function registerUserTools(server, apiClient) {
|
|
|
9
9
|
return jsonResponse(user);
|
|
10
10
|
}));
|
|
11
11
|
server.registerTool("update_current_user", {
|
|
12
|
-
description: "Update the current user's profile
|
|
12
|
+
description: "Update the current user's profile. Include password_confirmation when changing password.",
|
|
13
13
|
inputSchema: {
|
|
14
14
|
email: z.string().email().optional().describe("New email address"),
|
|
15
|
-
name: z.string().optional().describe("
|
|
16
|
-
|
|
15
|
+
name: z.string().optional().describe("Full name"),
|
|
16
|
+
first_name: z.string().optional().describe("First name"),
|
|
17
|
+
last_name: z.string().optional().describe("Last name"),
|
|
18
|
+
phone_number: z.string().optional().describe("Phone number"),
|
|
17
19
|
password: z.string().optional().describe("New password"),
|
|
20
|
+
password_confirmation: z.string().optional().describe("Password confirmation (required when changing password)"),
|
|
18
21
|
},
|
|
19
22
|
}, withErrorHandling(async (args) => {
|
|
20
23
|
const user = await apiClient.put(apiClient.buildPath("/currentuser"), args);
|
|
21
24
|
return jsonResponse(user);
|
|
22
25
|
}));
|
|
23
26
|
server.registerTool("list_currencies", {
|
|
24
|
-
description: "List enabled
|
|
27
|
+
description: "List currencies enabled for the current company (company default currency listed first)",
|
|
25
28
|
inputSchema: {},
|
|
26
29
|
}, withErrorHandling(async () => {
|
|
27
30
|
const currencies = await apiClient.get(apiClient.buildPath("/currencies"));
|
|
28
31
|
return jsonResponse(currencies);
|
|
29
32
|
}));
|
|
30
33
|
server.registerTool("list_all_currencies", {
|
|
31
|
-
description: "List all available currencies globally",
|
|
34
|
+
description: "List all available currencies globally, sorted by name (or by most popular if company ID is set)",
|
|
32
35
|
inputSchema: {},
|
|
33
36
|
}, withErrorHandling(async () => {
|
|
34
37
|
const currencies = await apiClient.get(apiClient.buildPath("/all_currencies"));
|