@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.
Files changed (35) hide show
  1. package/.claude/skills/bump-version/SKILL.md +77 -0
  2. package/.claude/skills/commit/SKILL.md +73 -0
  3. package/.claude/skills/npm-publish/SKILL.md +65 -0
  4. package/.claude/skills/pex-approval-flows/SKILL.md +122 -0
  5. package/.claude/skills/pex-approval-flows/references/conditions.md +90 -0
  6. package/.claude/skills/pex-auth/SKILL.md +80 -0
  7. package/.claude/skills/pex-budgets/SKILL.md +73 -0
  8. package/.claude/skills/pex-companies/SKILL.md +113 -0
  9. package/.claude/skills/pex-departments/SKILL.md +61 -0
  10. package/.claude/skills/pex-invoices/SKILL.md +125 -0
  11. package/.claude/skills/pex-invoices/references/line-items.md +55 -0
  12. package/.claude/skills/pex-payments/SKILL.md +79 -0
  13. package/.claude/skills/pex-purchase-orders/SKILL.md +167 -0
  14. package/.claude/skills/pex-purchase-orders/references/line-items.md +53 -0
  15. package/.claude/skills/pex-purchase-orders/references/workflows.md +74 -0
  16. package/.claude/skills/pex-settings/SKILL.md +128 -0
  17. package/.claude/skills/pex-suppliers/SKILL.md +113 -0
  18. package/README.md +118 -25
  19. package/dist/api-client.d.ts +1 -0
  20. package/dist/api-client.js +3 -0
  21. package/dist/tools/approval-flows.js +130 -25
  22. package/dist/tools/budgets.js +30 -20
  23. package/dist/tools/comments.js +4 -4
  24. package/dist/tools/companies.js +57 -7
  25. package/dist/tools/departments.js +6 -6
  26. package/dist/tools/invoices.js +100 -31
  27. package/dist/tools/payments.js +45 -13
  28. package/dist/tools/products.js +10 -5
  29. package/dist/tools/purchase-orders.js +178 -35
  30. package/dist/tools/supplementary.js +57 -14
  31. package/dist/tools/suppliers.js +38 -19
  32. package/dist/tools/tax-rates.js +15 -9
  33. package/dist/tools/users.js +8 -5
  34. package/dist/tools/webhooks.js +59 -9
  35. package/package.json +5 -4
@@ -1,46 +1,88 @@
1
1
  import { z } from "zod";
2
2
  import { jsonResponse, 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 invoiceLineItemSchema = z.object({
4
- description: z.string().describe("Line item description"),
5
- unit_price: z.number().describe("Unit price"),
6
- quantity: z.number().describe("Quantity"),
7
- vat: z.number().optional().describe("VAT amount"),
9
+ id: z.number().int().optional().describe("Line item ID (for updates)"),
10
+ description: z.string().optional().describe("Line item description"),
11
+ unit_price: z.number().optional().describe("Unit price"),
12
+ quantity: z.number().optional().describe("Quantity"),
13
+ vat: z.number().optional().describe("VAT/tax percentage"),
8
14
  net_amount: z.number().optional().describe("Net amount"),
9
- purchase_order_id: z.number().int().optional().describe("Related PO ID"),
10
- purchase_order_item_id: z.number().int().optional().describe("Related PO item ID"),
15
+ sequence_no: z.number().int().optional().describe("Sequence number for ordering"),
16
+ tax_rate_id: z.number().int().optional().describe("Tax rate ID"),
17
+ chart_of_account_id: z.number().int().optional().describe("Chart of account ID (GL code)"),
18
+ qbo_customer_id: z.number().int().optional().describe("QuickBooks customer ID"),
19
+ quickbooks_class_id: z.number().int().optional().describe("QuickBooks class ID"),
20
+ qbo_line_description: z.string().optional().describe("QuickBooks line description override"),
21
+ purchase_order_id: z.number().int().optional().describe("Related purchase order ID"),
22
+ purchase_order_item_id: z.number().int().optional().describe("Related PO line item ID"),
23
+ billable_status: z.string().optional().describe("Billable status for QuickBooks"),
24
+ _destroy: z.boolean().optional().describe("Set true to remove this line item on update"),
25
+ custom_field_values_attributes: z.array(customFieldValueSchema).optional().describe("Custom field values for this line item"),
11
26
  });
12
27
  export function registerInvoiceTools(server, apiClient) {
13
28
  server.registerTool("list_invoices", {
14
- description: "List invoices with pagination and filters (100 per page)",
29
+ description: "List invoices with pagination and filters. Returns paginated results with meta info (current_page, next_page, prev_page, total_pages, total_count). Per-page count is controlled by company settings (default 10, allowed: 10, 20, 50, 100).",
15
30
  inputSchema: {
16
- page: z.number().int().positive().optional().describe("Page number"),
17
- archived: z.boolean().optional().describe("Filter by archived status"),
18
- requester_id: z.number().int().optional().describe("Filter by requester ID"),
19
- approver_id: z.number().int().optional().describe("Filter by approver ID"),
31
+ page: z.number().int().positive().optional().describe("Page number (default: 1)"),
32
+ per_page: z.number().int().optional().describe("Results per page (allowed: 10, 20, 50, 100)"),
33
+ search: z.string().optional().describe("Search term matches invoice number, supplier name"),
34
+ invoice_statuses_filter: z
35
+ .enum(["awaiting_review", "outstanding", "ready_to_pay", "settled", "cancelled"])
36
+ .optional()
37
+ .describe("Filter by invoice status"),
38
+ archived: z.boolean().optional().describe("Filter by archived status (default: false)"),
20
39
  supplier_id: z.number().int().optional().describe("Filter by supplier ID"),
21
- invoice_date_filter: z.string().optional().describe("Date filter (e.g. 'last 7days')"),
40
+ requester_id: z.number().int().optional().describe("Filter by requester/creator user ID"),
41
+ approver_id: z.number().int().optional().describe("Filter by approver user ID"),
42
+ department_id: z.number().int().optional().describe("Filter by department ID (via linked POs)"),
43
+ invoice_date_filter: z
44
+ .enum(["last 7days", "last 30days", "last 60days", "last 90days", "last 180days", "last 1year", "current_month", "current_year", "last_month", "last_year"])
45
+ .optional()
46
+ .describe("Predefined date range filter"),
47
+ sage_exported: z.boolean().optional().describe("Filter by Sage export status"),
48
+ sort: z.string().optional().describe("Sort column (e.g. 'invoices.created_at', 'invoices.issue_date')"),
49
+ direction: z.enum(["asc", "desc"]).optional().describe("Sort direction (default: desc)"),
22
50
  },
23
51
  }, withErrorHandling(async (args) => {
24
52
  const params = new URLSearchParams();
25
53
  if (args.page)
26
54
  params.set("page", String(args.page));
55
+ if (args.per_page)
56
+ params.set("per_page", String(args.per_page));
57
+ if (args.search)
58
+ params.set("search[query]", args.search);
59
+ if (args.invoice_statuses_filter)
60
+ params.set("invoice_statuses_filter", args.invoice_statuses_filter);
27
61
  if (args.archived !== undefined)
28
62
  params.set("archived", String(args.archived));
63
+ if (args.supplier_id)
64
+ params.set("supplier_id", String(args.supplier_id));
29
65
  if (args.requester_id)
30
66
  params.set("requester_id", String(args.requester_id));
31
67
  if (args.approver_id)
32
68
  params.set("approver_id", String(args.approver_id));
33
- if (args.supplier_id)
34
- params.set("supplier_id", String(args.supplier_id));
69
+ if (args.department_id)
70
+ params.set("department_id", String(args.department_id));
35
71
  if (args.invoice_date_filter)
36
72
  params.set("invoice_date_filter", args.invoice_date_filter);
73
+ if (args.sage_exported !== undefined)
74
+ params.set("sage_exported", String(args.sage_exported));
75
+ if (args.sort)
76
+ params.set("sort", args.sort);
77
+ if (args.direction)
78
+ params.set("direction", args.direction);
37
79
  const query = params.toString();
38
80
  const path = `${apiClient.buildPath("/invoices")}${query ? `?${query}` : ""}`;
39
81
  const result = await apiClient.get(path);
40
82
  return jsonResponse(result);
41
83
  }));
42
84
  server.registerTool("get_invoice", {
43
- description: "Get invoice details by ID",
85
+ description: "Get invoice details by ID, including line items, linked POs, and comments",
44
86
  inputSchema: {
45
87
  id: z.number().int().positive().describe("Invoice ID"),
46
88
  },
@@ -49,24 +91,29 @@ export function registerInvoiceTools(server, apiClient) {
49
91
  return jsonResponse(invoice);
50
92
  }));
51
93
  server.registerTool("create_invoice", {
52
- description: "Create a new invoice",
94
+ description: "Create a new invoice. If the company has 'create invoice in awaiting review' enabled, the invoice starts in awaiting_review status.",
53
95
  inputSchema: {
54
- invoice_number: z.string().optional().describe("Invoice number"),
55
- issue_date: z.string().optional().describe("Issue date"),
56
- supplier_id: z.number().int().optional().describe("Supplier ID"),
96
+ invoice_number: z.string().optional().describe("Invoice number/reference"),
97
+ issue_date: z.string().optional().describe("Issue date (format depends on company date_format)"),
98
+ uploaded_date: z.string().optional().describe("Upload date"),
57
99
  received_date: z.string().optional().describe("Received date"),
58
100
  due_date: z.string().optional().describe("Due date"),
59
- gross_amount: z.number().describe("Gross amount"),
60
- currency_id: z.number().int().describe("Currency ID"),
61
- company_id: z.number().int().describe("Company ID"),
101
+ gross_amount: z.number().optional().describe("Gross amount"),
102
+ currency_id: z.number().int().optional().describe("Currency ID"),
103
+ supplier_id: z.number().int().optional().describe("Supplier ID"),
104
+ standalone_invoice: z.boolean().optional().describe("True if invoice is not linked to any PO"),
105
+ payment_term_id: z.number().int().optional().describe("Payment term ID"),
106
+ selected_purchase_order_ids: z.array(z.number().int()).optional().describe("PO IDs to link this invoice to"),
62
107
  line_items: z.array(invoiceLineItemSchema).optional().describe("Invoice line items"),
108
+ custom_field_values_attributes: z.array(customFieldValueSchema).optional().describe("Invoice-level custom field values"),
63
109
  },
64
110
  }, withErrorHandling(async (args) => {
65
- const { line_items, ...invoiceData } = args;
111
+ const { line_items, custom_field_values_attributes, ...invoiceData } = args;
66
112
  const body = {
67
113
  invoice: {
68
114
  ...invoiceData,
69
115
  ...(line_items ? { invoice_line_items_attributes: line_items } : {}),
116
+ ...(custom_field_values_attributes ? { custom_field_values_attributes } : {}),
70
117
  },
71
118
  };
72
119
  const invoice = await apiClient.post(apiClient.buildPath("/invoices"), body);
@@ -78,45 +125,60 @@ export function registerInvoiceTools(server, apiClient) {
78
125
  id: z.number().int().positive().describe("Invoice ID"),
79
126
  invoice_number: z.string().optional().describe("Invoice number"),
80
127
  issue_date: z.string().optional().describe("Issue date"),
81
- supplier_id: z.number().int().optional().describe("Supplier ID"),
128
+ uploaded_date: z.string().optional().describe("Upload date"),
129
+ received_date: z.string().optional().describe("Received date"),
82
130
  due_date: z.string().optional().describe("Due date"),
83
131
  gross_amount: z.number().optional().describe("Gross amount"),
132
+ currency_id: z.number().int().optional().describe("Currency ID"),
133
+ supplier_id: z.number().int().optional().describe("Supplier ID"),
134
+ standalone_invoice: z.boolean().optional().describe("Standalone invoice flag"),
135
+ payment_term_id: z.number().int().optional().describe("Payment term ID"),
136
+ selected_purchase_order_ids: z.array(z.number().int()).optional().describe("PO IDs to link"),
137
+ line_items: z.array(invoiceLineItemSchema).optional().describe("Invoice line items (include id to update, _destroy to remove)"),
138
+ custom_field_values_attributes: z.array(customFieldValueSchema).optional().describe("Invoice-level custom field values"),
84
139
  },
85
140
  }, withErrorHandling(async (args) => {
86
- const { id, ...data } = args;
87
- const invoice = await apiClient.put(apiClient.buildPath(`/invoices/${id}`), { invoice: data });
141
+ const { id, line_items, custom_field_values_attributes, ...data } = args;
142
+ const body = {
143
+ invoice: {
144
+ ...data,
145
+ ...(line_items ? { invoice_line_items_attributes: line_items } : {}),
146
+ ...(custom_field_values_attributes ? { custom_field_values_attributes } : {}),
147
+ },
148
+ };
149
+ const invoice = await apiClient.put(apiClient.buildPath(`/invoices/${id}`), body);
88
150
  return jsonResponse(invoice);
89
151
  }));
90
152
  server.registerTool("accept_invoice", {
91
- description: "Accept an invoice that is awaiting review",
153
+ description: "Accept an invoice that is in awaiting_review status",
92
154
  inputSchema: { id: z.number().int().positive().describe("Invoice ID") },
93
155
  }, withErrorHandling(async (args) => {
94
156
  const result = await apiClient.put(apiClient.buildPath(`/invoices/${args.id}/accept`));
95
157
  return jsonResponse(result);
96
158
  }));
97
159
  server.registerTool("approve_invoice", {
98
- description: "Approve an invoice",
160
+ description: "Approve an invoice (requires invoice approval permission)",
99
161
  inputSchema: { id: z.number().int().positive().describe("Invoice ID") },
100
162
  }, withErrorHandling(async (args) => {
101
163
  const result = await apiClient.put(apiClient.buildPath(`/invoices/${args.id}/approve`));
102
164
  return jsonResponse(result);
103
165
  }));
104
166
  server.registerTool("reject_invoice", {
105
- description: "Reject an invoice",
167
+ description: "Reject an invoice (requires invoice approval permission)",
106
168
  inputSchema: { id: z.number().int().positive().describe("Invoice ID") },
107
169
  }, withErrorHandling(async (args) => {
108
170
  const result = await apiClient.put(apiClient.buildPath(`/invoices/${args.id}/reject`));
109
171
  return jsonResponse(result);
110
172
  }));
111
173
  server.registerTool("cancel_invoice", {
112
- description: "Cancel an invoice",
174
+ description: "Cancel an invoice (requires cancel permission)",
113
175
  inputSchema: { id: z.number().int().positive().describe("Invoice ID") },
114
176
  }, withErrorHandling(async (args) => {
115
177
  const result = await apiClient.put(apiClient.buildPath(`/invoices/${args.id}/cancel`));
116
178
  return jsonResponse(result);
117
179
  }));
118
180
  server.registerTool("archive_invoice", {
119
- description: "Archive an invoice",
181
+ description: "Archive an invoice (requires archive permission)",
120
182
  inputSchema: { id: z.number().int().positive().describe("Invoice ID") },
121
183
  }, withErrorHandling(async (args) => {
122
184
  const result = await apiClient.put(apiClient.buildPath(`/invoices/${args.id}/archive`));
@@ -129,4 +191,11 @@ export function registerInvoiceTools(server, apiClient) {
129
191
  const result = await apiClient.put(apiClient.buildPath(`/invoices/${args.id}/dearchive`));
130
192
  return jsonResponse(result);
131
193
  }));
194
+ server.registerTool("rerun_invoice_approval_flow", {
195
+ description: "Rerun the approval flow for an invoice. Useful when approval flow rules have changed.",
196
+ inputSchema: { id: z.number().int().positive().describe("Invoice ID") },
197
+ }, withErrorHandling(async (args) => {
198
+ const result = await apiClient.post(apiClient.buildPath(`/invoices/${args.id}/rerun_approval_flow`));
199
+ return jsonResponse(result);
200
+ }));
132
201
  }
@@ -1,11 +1,19 @@
1
1
  import { z } from "zod";
2
2
  import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
3
3
  export function registerPaymentTools(server, apiClient) {
4
+ server.registerTool("get_payment", {
5
+ description: "Get payment details by ID",
6
+ inputSchema: {
7
+ id: z.number().int().positive().describe("Payment ID"),
8
+ },
9
+ }, withErrorHandling(async (args) => {
10
+ const payment = await apiClient.get(apiClient.buildPath(`/npayments/${args.id}`));
11
+ return jsonResponse(payment);
12
+ }));
4
13
  server.registerTool("create_payment", {
5
- description: "Create a payment (feature flagged). ptype values: bank_transfer, card, credit_card, check, cash, one_time_card, letter_of_credit, other",
14
+ description: "Create a payment to settle invoices and/or purchase orders (feature flagged — contact sales to enable). The payment date must match the company's date_format setting.",
6
15
  inputSchema: {
7
- user_id: z.number().int().describe("Payment creator user ID"),
8
- reference: z.string().optional().describe("Reference number"),
16
+ reference: z.string().optional().describe("Payment reference number"),
9
17
  supplier_id: z.number().int().describe("Supplier ID"),
10
18
  ptype: z
11
19
  .enum([
@@ -19,44 +27,68 @@ export function registerPaymentTools(server, apiClient) {
19
27
  "other",
20
28
  ])
21
29
  .describe("Payment type"),
22
- date: z.string().describe("Payment date"),
30
+ payment_mode: z.string().optional().describe("Payment mode"),
31
+ status: z.string().optional().describe("Payment status"),
32
+ date: z.string().describe("Payment date (must match company date_format setting)"),
23
33
  currency_id: z.number().int().describe("Currency ID"),
24
- amount: z.number().describe("Total amount (must match sum of invoice amounts)"),
34
+ amount: z.number().describe("Total payment amount"),
25
35
  invoices: z
26
36
  .array(z.object({
27
37
  invoice_id: z.number().int().describe("Invoice ID"),
28
- gross_amount: z.number().describe("Amount for this invoice"),
38
+ gross_amount: z.number().describe("Amount to apply to this invoice"),
39
+ }))
40
+ .optional()
41
+ .describe("Invoices to pay with amounts"),
42
+ purchase_orders: z
43
+ .array(z.object({
44
+ purchase_order_id: z.number().int().describe("Purchase Order ID"),
45
+ budget_id: z.number().int().optional().describe("Budget ID"),
46
+ gross_amount: z.number().describe("Amount to apply to this PO"),
47
+ }))
48
+ .optional()
49
+ .describe("Purchase orders to pay with amounts"),
50
+ comments: z
51
+ .array(z.object({
52
+ comment: z.string().describe("Comment text"),
29
53
  }))
30
- .describe("Invoices to pay"),
54
+ .optional()
55
+ .describe("Payment comments"),
31
56
  },
32
57
  }, withErrorHandling(async (args) => {
33
- const { invoices, ...paymentData } = args;
58
+ const { invoices, purchase_orders, comments, ...paymentData } = args;
34
59
  const body = {
35
60
  npayment: {
36
61
  ...paymentData,
37
- npayment_invoices_attributes: invoices,
62
+ ...(invoices ? { npayment_invoices_attributes: invoices } : {}),
63
+ ...(purchase_orders ? { npayment_link_orders_attributes: purchase_orders } : {}),
64
+ ...(comments ? { npayment_comments_attributes: comments } : {}),
38
65
  },
39
66
  };
40
67
  const payment = await apiClient.post(apiClient.buildPath("/npayments"), body);
41
68
  return jsonResponse(payment);
42
69
  }));
43
70
  server.registerTool("create_po_payment", {
44
- description: "Create a payment for a purchase order",
71
+ description: "Create a payment for a specific purchase order with optional item-level breakdown. Marks PO items as paid.",
45
72
  inputSchema: {
46
73
  purchase_order_id: z.number().int().positive().describe("Purchase Order ID"),
74
+ amount: z.number().optional().describe("Total payment amount (if not using item-level payments)"),
47
75
  note: z.string().optional().describe("Payment note"),
48
76
  item_payments: z
49
77
  .array(z.object({
50
- purchase_order_item_id: z.number().int().describe("PO item ID"),
78
+ purchase_order_item_id: z.number().int().describe("PO line item ID"),
51
79
  amount: z.number().describe("Payment amount for this item"),
52
80
  }))
81
+ .optional()
53
82
  .describe("Item-level payment amounts"),
54
83
  },
55
84
  }, withErrorHandling(async (args) => {
56
85
  const body = {
57
86
  payment: {
58
- note: args.note,
59
- purchase_order_item_payments_attributes: args.item_payments,
87
+ ...(args.amount !== undefined ? { amount: args.amount } : {}),
88
+ ...(args.note ? { note: args.note } : {}),
89
+ ...(args.item_payments
90
+ ? { purchase_order_item_payments_attributes: args.item_payments }
91
+ : {}),
60
92
  },
61
93
  };
62
94
  const result = await apiClient.post(apiClient.buildPath(`/purchase_orders/${args.purchase_order_id}/payments`), body);
@@ -2,13 +2,19 @@ import { z } from "zod";
2
2
  import { jsonResponse, withErrorHandling } from "../tool-helpers.js";
3
3
  export function registerProductTools(server, apiClient) {
4
4
  server.registerTool("list_products", {
5
- description: "List products with optional filters for supplier and archived status",
5
+ description: "List products. When page param is provided, returns paginated response with meta. Without page param, returns all products as an array. Can filter by supplier and archived status.",
6
6
  inputSchema: {
7
+ page: z.number().int().positive().optional().describe("Page number — enables pagination"),
8
+ per_page: z.number().int().positive().optional().describe("Results per page (default: 20)"),
7
9
  supplier_id: z.number().int().optional().describe("Filter by supplier ID"),
8
- archived: z.boolean().optional().describe("Filter by archived status"),
10
+ archived: z.boolean().optional().describe("Filter by archived status (default: false)"),
9
11
  },
10
12
  }, withErrorHandling(async (args) => {
11
13
  const params = new URLSearchParams();
14
+ if (args.page)
15
+ params.set("page", String(args.page));
16
+ if (args.per_page)
17
+ params.set("per_page", String(args.per_page));
12
18
  if (args.supplier_id)
13
19
  params.set("supplier_id", String(args.supplier_id));
14
20
  if (args.archived !== undefined)
@@ -28,12 +34,12 @@ export function registerProductTools(server, apiClient) {
28
34
  return jsonResponse(product);
29
35
  }));
30
36
  server.registerTool("create_product", {
31
- description: "Create a new product",
37
+ description: "Create a new product and associate it with a supplier",
32
38
  inputSchema: {
33
39
  description: z.string().describe("Product description (required)"),
40
+ supplier_id: z.number().int().describe("Supplier ID (required — product is associated with this supplier)"),
34
41
  sku: z.string().optional().describe("SKU code"),
35
42
  unit_price: z.number().optional().describe("Unit price"),
36
- supplier_id: z.number().int().optional().describe("Supplier ID"),
37
43
  },
38
44
  }, withErrorHandling(async (args) => {
39
45
  const product = await apiClient.post(apiClient.buildPath("/products"), { product: args });
@@ -47,7 +53,6 @@ export function registerProductTools(server, apiClient) {
47
53
  sku: z.string().optional().describe("SKU code"),
48
54
  unit_price: z.number().optional().describe("Unit price"),
49
55
  supplier_id: z.number().int().optional().describe("Supplier ID"),
50
- archived: z.boolean().optional().describe("Archive status"),
51
56
  },
52
57
  }, withErrorHandling(async (args) => {
53
58
  const { id, ...data } = args;