@hisaabo/mcp 0.1.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/.turbo/turbo-build.log +14 -0
- package/.turbo/turbo-typecheck.log +4 -0
- package/README.md +155 -0
- package/dist/bin/index.js +2682 -0
- package/dist/bin/index.js.map +1 -0
- package/dist/index.js +2681 -0
- package/package.json +26 -0
- package/src/client.ts +1315 -0
- package/src/index.ts +64 -0
- package/src/lib/errors.ts +49 -0
- package/src/lib/pagination.ts +35 -0
- package/src/resources/index.ts +211 -0
- package/src/server.ts +55 -0
- package/src/tools/bankAccount.ts +200 -0
- package/src/tools/dashboard.ts +110 -0
- package/src/tools/expense.ts +175 -0
- package/src/tools/gst.ts +90 -0
- package/src/tools/import.ts +298 -0
- package/src/tools/invoice.ts +256 -0
- package/src/tools/item.ts +262 -0
- package/src/tools/party.ts +266 -0
- package/src/tools/payment.ts +222 -0
- package/src/tools/reports.ts +246 -0
- package/src/tools/shipment.ts +222 -0
- package/src/tools/store.ts +177 -0
- package/src/tools/target.ts +143 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Invoice tools — the core of Hisaabo's invoicing workflow.
|
|
3
|
+
*
|
|
4
|
+
* Tools registered:
|
|
5
|
+
* invoice_list — search and filter invoices with pagination
|
|
6
|
+
* invoice_create — create a new sale invoice or purchase bill
|
|
7
|
+
* invoice_get — fetch full invoice details (line items, payments, balance)
|
|
8
|
+
* invoice_update_status — change invoice status (mark sent, cancel, etc.)
|
|
9
|
+
* invoice_pdf_url — get a URL to download/view the PDF (A4 or thermal)
|
|
10
|
+
* invoice_delete — soft-delete an invoice (admin or seller_manager within 2 hours)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
15
|
+
import type { HisaaboClient } from "../client.js";
|
|
16
|
+
import { wrapTool } from "../lib/errors.js";
|
|
17
|
+
import { MAX_PAGE_SIZE, withPaginationMeta } from "../lib/pagination.js";
|
|
18
|
+
|
|
19
|
+
const INVOICE_STATUS = ["draft", "unfulfilled", "sent", "paid", "partial", "overdue", "cancelled"] as const;
|
|
20
|
+
const DOCUMENT_TYPE = ["invoice", "quotation", "credit_note", "debit_note", "delivery_challan", "proforma", "sales_return", "purchase_return"] as const;
|
|
21
|
+
|
|
22
|
+
export function registerInvoiceTools(server: McpServer, client: HisaaboClient) {
|
|
23
|
+
|
|
24
|
+
server.tool(
|
|
25
|
+
"invoice_list",
|
|
26
|
+
[
|
|
27
|
+
"List invoices for the active business. Returns up to 25 invoices per page with pagination metadata.",
|
|
28
|
+
"Use `page` to fetch subsequent pages when `hasMore` is true in the response.",
|
|
29
|
+
"Example: to find all unpaid invoices from a customer, set status='sent' or status='partial' and party_id=<uuid>.",
|
|
30
|
+
"To find overdue invoices across all customers, set status='overdue'.",
|
|
31
|
+
].join(" "),
|
|
32
|
+
{
|
|
33
|
+
type: z.enum(["sale", "purchase"]).optional()
|
|
34
|
+
.describe("sale = customer invoices, purchase = supplier bills. Omit to return both."),
|
|
35
|
+
document_type: z.enum(DOCUMENT_TYPE).optional()
|
|
36
|
+
.describe("Filter by document type (default: invoice). Use 'quotation' for quotes, 'credit_note' for credits."),
|
|
37
|
+
status: z.enum(INVOICE_STATUS).optional()
|
|
38
|
+
.describe("Filter by status. Common values: 'sent' (awaiting payment), 'paid', 'overdue', 'draft'."),
|
|
39
|
+
party_id: z.string().uuid().optional()
|
|
40
|
+
.describe("UUID of a specific customer or supplier to filter by."),
|
|
41
|
+
from_date: z.string().datetime().optional()
|
|
42
|
+
.describe("Start of invoice date range (ISO 8601, e.g. '2024-04-01T00:00:00Z')."),
|
|
43
|
+
to_date: z.string().datetime().optional()
|
|
44
|
+
.describe("End of invoice date range (ISO 8601)."),
|
|
45
|
+
search: z.string().max(200).optional()
|
|
46
|
+
.describe("Search by invoice number or party name (partial match)."),
|
|
47
|
+
sort_by: z.enum(["date", "amount", "number"]).optional()
|
|
48
|
+
.describe("Sort field. Defaults to date descending."),
|
|
49
|
+
sort_dir: z.enum(["asc", "desc"]).optional()
|
|
50
|
+
.describe("Sort direction. Defaults to desc."),
|
|
51
|
+
page: z.number().int().min(1).default(1)
|
|
52
|
+
.describe("Page number (1-indexed). Use with hasMore in the response to paginate."),
|
|
53
|
+
},
|
|
54
|
+
wrapTool(async (input) => {
|
|
55
|
+
const result = await client.invoice.list({
|
|
56
|
+
type: input.type,
|
|
57
|
+
documentType: input.document_type,
|
|
58
|
+
status: input.status,
|
|
59
|
+
partyId: input.party_id,
|
|
60
|
+
fromDate: input.from_date,
|
|
61
|
+
toDate: input.to_date,
|
|
62
|
+
search: input.search,
|
|
63
|
+
sortBy: input.sort_by,
|
|
64
|
+
sortDir: input.sort_dir,
|
|
65
|
+
page: input.page,
|
|
66
|
+
limit: MAX_PAGE_SIZE,
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
content: [{
|
|
70
|
+
type: "text" as const,
|
|
71
|
+
text: JSON.stringify(withPaginationMeta(result), null, 2),
|
|
72
|
+
}],
|
|
73
|
+
};
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
server.tool(
|
|
78
|
+
"invoice_create",
|
|
79
|
+
[
|
|
80
|
+
"Create a new invoice or bill for the active business. Returns the created invoice with its assigned invoice number.",
|
|
81
|
+
"For sale invoices (customer billing), set type='sale'. For purchase bills (supplier invoices), set type='purchase'.",
|
|
82
|
+
"Each line item requires a description, quantity (decimal string), and unit_price (decimal string, no currency symbol).",
|
|
83
|
+
"Monetary values are always decimal strings, e.g. '1500.00' not 1500.",
|
|
84
|
+
"If a line item corresponds to an inventory item, set item_id to link it and update stock automatically.",
|
|
85
|
+
"Example: { party_id: 'uuid', type: 'sale', line_items: [{ description: 'Web Design', quantity: '1.00', unit_price: '15000.00', tax_percent: '18.00' }] }",
|
|
86
|
+
].join(" "),
|
|
87
|
+
{
|
|
88
|
+
party_id: z.string().uuid()
|
|
89
|
+
.describe("UUID of the customer (for sales) or supplier (for purchases). Use party_list to find UUIDs."),
|
|
90
|
+
type: z.enum(["sale", "purchase"])
|
|
91
|
+
.describe("'sale' for customer invoices (money coming in), 'purchase' for supplier bills (money going out)."),
|
|
92
|
+
document_type: z.enum(DOCUMENT_TYPE).optional()
|
|
93
|
+
.describe("Document type (default: 'invoice'). Use 'quotation' to create a quote instead of an invoice."),
|
|
94
|
+
line_items: z.array(z.object({
|
|
95
|
+
description: z.string().min(1).max(500)
|
|
96
|
+
.describe("Product or service name/description."),
|
|
97
|
+
quantity: z.string().regex(/^\d+(\.\d{1,3})?$/)
|
|
98
|
+
.describe("Quantity as decimal string, e.g. '1.000', '7.500', '100'."),
|
|
99
|
+
unit_price: z.string().regex(/^\d+(\.\d{1,2})?$/)
|
|
100
|
+
.describe("Price per unit as decimal string, e.g. '250.00', '15000.00'."),
|
|
101
|
+
tax_percent: z.string().regex(/^\d+(\.\d{1,2})?$/).default("0")
|
|
102
|
+
.describe("GST/tax rate percentage as decimal string: '0', '5.00', '12.00', '18.00', '28.00'."),
|
|
103
|
+
discount_percent: z.string().regex(/^\d+(\.\d{1,2})?$/).default("0")
|
|
104
|
+
.describe("Line-level discount percentage, e.g. '10.00' for 10% off."),
|
|
105
|
+
item_id: z.string().uuid().optional()
|
|
106
|
+
.describe("Link to an inventory item UUID to auto-fill price and update stock (optional)."),
|
|
107
|
+
})).min(1)
|
|
108
|
+
.describe("At least one line item is required."),
|
|
109
|
+
invoice_date: z.string().datetime().optional()
|
|
110
|
+
.describe("Invoice date (ISO 8601). Defaults to today if omitted."),
|
|
111
|
+
due_date: z.string().datetime().optional()
|
|
112
|
+
.describe("Payment due date (ISO 8601). Set to enforce credit terms."),
|
|
113
|
+
notes: z.string().max(2000).optional()
|
|
114
|
+
.describe("Notes visible on the printed invoice, e.g. bank account details or thank-you message."),
|
|
115
|
+
terms_and_conditions: z.string().max(2000).optional()
|
|
116
|
+
.describe("Terms and conditions text printed on the invoice."),
|
|
117
|
+
invoice_discount: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
118
|
+
.describe("Invoice-level discount. Interpretation depends on invoice_discount_type."),
|
|
119
|
+
invoice_discount_type: z.enum(["amount", "percent"]).optional()
|
|
120
|
+
.describe("Whether invoice_discount is a flat amount or percentage (default: 'amount')."),
|
|
121
|
+
round_off: z.string().regex(/^-?\d+(\.\d{1,2})?$/).optional()
|
|
122
|
+
.describe("Round-off adjustment for the total, e.g. '0.50' or '-0.25' (typically small). Default '0'."),
|
|
123
|
+
reference_document_id: z.string().uuid().optional()
|
|
124
|
+
.describe("UUID of a source document (e.g. quotation UUID when converting quote to invoice)."),
|
|
125
|
+
},
|
|
126
|
+
wrapTool(async (input) => {
|
|
127
|
+
const invoice = await client.invoice.create({
|
|
128
|
+
partyId: input.party_id,
|
|
129
|
+
type: input.type,
|
|
130
|
+
documentType: input.document_type,
|
|
131
|
+
invoiceDate: input.invoice_date,
|
|
132
|
+
dueDate: input.due_date,
|
|
133
|
+
notes: input.notes,
|
|
134
|
+
termsAndConditions: input.terms_and_conditions,
|
|
135
|
+
invoiceDiscount: input.invoice_discount,
|
|
136
|
+
invoiceDiscountType: input.invoice_discount_type,
|
|
137
|
+
roundOff: input.round_off,
|
|
138
|
+
referenceDocumentId: input.reference_document_id,
|
|
139
|
+
lineItems: input.line_items.map((li) => ({
|
|
140
|
+
description: li.description,
|
|
141
|
+
quantity: li.quantity,
|
|
142
|
+
unitPrice: li.unit_price,
|
|
143
|
+
taxPercent: li.tax_percent,
|
|
144
|
+
discountPercent: li.discount_percent,
|
|
145
|
+
itemId: li.item_id,
|
|
146
|
+
})),
|
|
147
|
+
});
|
|
148
|
+
return {
|
|
149
|
+
content: [{
|
|
150
|
+
type: "text" as const,
|
|
151
|
+
text: JSON.stringify(invoice, null, 2),
|
|
152
|
+
}],
|
|
153
|
+
};
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
server.tool(
|
|
158
|
+
"invoice_get",
|
|
159
|
+
[
|
|
160
|
+
"Get the full details of a single invoice, including all line items, payment history, and outstanding balance.",
|
|
161
|
+
"Use this after invoice_list to get complete details. The 'balanceDue' field shows how much is still owed.",
|
|
162
|
+
"Payments already applied to this invoice appear in the response.",
|
|
163
|
+
].join(" "),
|
|
164
|
+
{
|
|
165
|
+
invoice_id: z.string().uuid()
|
|
166
|
+
.describe("Invoice UUID from invoice_list or invoice_create."),
|
|
167
|
+
},
|
|
168
|
+
wrapTool(async (input) => {
|
|
169
|
+
const invoice = await client.invoice.get(input.invoice_id);
|
|
170
|
+
return {
|
|
171
|
+
content: [{
|
|
172
|
+
type: "text" as const,
|
|
173
|
+
text: JSON.stringify(invoice, null, 2),
|
|
174
|
+
}],
|
|
175
|
+
};
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
server.tool(
|
|
180
|
+
"invoice_update_status",
|
|
181
|
+
[
|
|
182
|
+
"Change the status of an invoice.",
|
|
183
|
+
"Typical workflow: create (draft) → mark sent → payment received (handled by payment_create, which auto-updates status to 'paid' or 'partial').",
|
|
184
|
+
"Use this tool to manually set status to 'sent' (invoice delivered), 'cancelled' (void the invoice), or 'overdue' (mark past due).",
|
|
185
|
+
"Note: 'paid' and 'partial' status is normally set automatically when payments are recorded — prefer payment_create over forcing 'paid' here.",
|
|
186
|
+
].join(" "),
|
|
187
|
+
{
|
|
188
|
+
invoice_id: z.string().uuid()
|
|
189
|
+
.describe("Invoice UUID."),
|
|
190
|
+
status: z.enum(INVOICE_STATUS)
|
|
191
|
+
.describe("New status. 'sent' = delivered to customer. 'cancelled' = voided. 'draft' = revert to draft."),
|
|
192
|
+
},
|
|
193
|
+
wrapTool(async (input) => {
|
|
194
|
+
const result = await client.invoice.updateStatus(input.invoice_id, input.status);
|
|
195
|
+
return {
|
|
196
|
+
content: [{
|
|
197
|
+
type: "text" as const,
|
|
198
|
+
text: JSON.stringify(result, null, 2),
|
|
199
|
+
}],
|
|
200
|
+
};
|
|
201
|
+
})
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
server.tool(
|
|
205
|
+
"invoice_delete",
|
|
206
|
+
[
|
|
207
|
+
"Soft-delete an invoice (marks it as cancelled and hides it from lists).",
|
|
208
|
+
"Admins can delete any invoice. Seller managers can only delete unpaid invoices created within the last 2 hours.",
|
|
209
|
+
"Paid invoices cannot be deleted — void them by recording a credit note instead.",
|
|
210
|
+
"This is a soft delete — the invoice is not physically removed from the database.",
|
|
211
|
+
].join(" "),
|
|
212
|
+
{
|
|
213
|
+
invoice_id: z.string().uuid()
|
|
214
|
+
.describe("Invoice UUID to delete."),
|
|
215
|
+
},
|
|
216
|
+
wrapTool(async (input) => {
|
|
217
|
+
const result = await client.invoice.delete(input.invoice_id);
|
|
218
|
+
return {
|
|
219
|
+
content: [{
|
|
220
|
+
type: "text" as const,
|
|
221
|
+
text: JSON.stringify(result, null, 2),
|
|
222
|
+
}],
|
|
223
|
+
};
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
server.tool(
|
|
228
|
+
"invoice_pdf_url",
|
|
229
|
+
[
|
|
230
|
+
"Get the URL to download or view an invoice as a PDF.",
|
|
231
|
+
"The URL requires the HISAABO_API_KEY for authentication (pass as a Bearer token).",
|
|
232
|
+
"Use format='a4' for standard invoices and format='thermal' for 80mm receipt printing.",
|
|
233
|
+
"The URL is valid for as long as the session token is valid.",
|
|
234
|
+
].join(" "),
|
|
235
|
+
{
|
|
236
|
+
invoice_id: z.string().uuid()
|
|
237
|
+
.describe("Invoice UUID."),
|
|
238
|
+
format: z.enum(["a4", "thermal"]).default("a4")
|
|
239
|
+
.describe("'a4' for standard A4 invoice PDF, 'thermal' for 80mm thermal receipt."),
|
|
240
|
+
},
|
|
241
|
+
wrapTool(async (input) => {
|
|
242
|
+
// The PDF endpoint is a Hono route, not tRPC — return the URL for the caller to use
|
|
243
|
+
const url = `${client.apiUrl}/api/invoice/${input.invoice_id}/pdf?format=${input.format}`;
|
|
244
|
+
return {
|
|
245
|
+
content: [{
|
|
246
|
+
type: "text" as const,
|
|
247
|
+
text: JSON.stringify({
|
|
248
|
+
url,
|
|
249
|
+
note: "Fetch this URL with the Authorization: Bearer <HISAABO_API_KEY> header to download the PDF.",
|
|
250
|
+
format: input.format,
|
|
251
|
+
}, null, 2),
|
|
252
|
+
}],
|
|
253
|
+
};
|
|
254
|
+
})
|
|
255
|
+
);
|
|
256
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Item (inventory) tools.
|
|
3
|
+
*
|
|
4
|
+
* Tools registered:
|
|
5
|
+
* item_list — list/search inventory items
|
|
6
|
+
* item_create — create a new product or service item
|
|
7
|
+
* item_get — get full item details including variants and stock
|
|
8
|
+
* item_adjust_stock — record a stock-in or stock-out adjustment
|
|
9
|
+
* item_update — update an existing item's details or pricing
|
|
10
|
+
* item_delete — permanently delete an item
|
|
11
|
+
* item_categories — list all distinct item categories
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
+
import type { HisaaboClient } from "../client.js";
|
|
17
|
+
import { wrapTool } from "../lib/errors.js";
|
|
18
|
+
import { MAX_PAGE_SIZE, withPaginationMeta } from "../lib/pagination.js";
|
|
19
|
+
|
|
20
|
+
export function registerItemTools(server: McpServer, client: HisaaboClient) {
|
|
21
|
+
|
|
22
|
+
server.tool(
|
|
23
|
+
"item_list",
|
|
24
|
+
[
|
|
25
|
+
"List inventory items and services for the active business.",
|
|
26
|
+
"The 'stockQuantity' field shows current stock. Items with stock below 'lowStockAlert' are low-stock.",
|
|
27
|
+
"Use low_stock=true to find items that need restocking.",
|
|
28
|
+
"Use this to find item UUIDs before creating invoices (linking item_id speeds up invoice creation and updates stock).",
|
|
29
|
+
].join(" "),
|
|
30
|
+
{
|
|
31
|
+
search: z.string().max(200).optional()
|
|
32
|
+
.describe("Search by item name, SKU, or HSN code."),
|
|
33
|
+
category: z.string().max(100).optional()
|
|
34
|
+
.describe("Filter by category."),
|
|
35
|
+
item_type: z.enum(["product", "service"]).optional()
|
|
36
|
+
.describe("'product' for physical goods with stock, 'service' for billable services (no stock tracking)."),
|
|
37
|
+
low_stock: z.boolean().optional()
|
|
38
|
+
.describe("If true, return only items where current stock is below the low-stock alert threshold."),
|
|
39
|
+
page: z.number().int().min(1).default(1)
|
|
40
|
+
.describe("Page number for pagination."),
|
|
41
|
+
},
|
|
42
|
+
wrapTool(async (input) => {
|
|
43
|
+
const result = await client.item.list({
|
|
44
|
+
search: input.search,
|
|
45
|
+
category: input.category,
|
|
46
|
+
itemType: input.item_type,
|
|
47
|
+
lowStock: input.low_stock,
|
|
48
|
+
page: input.page,
|
|
49
|
+
limit: MAX_PAGE_SIZE,
|
|
50
|
+
});
|
|
51
|
+
return {
|
|
52
|
+
content: [{
|
|
53
|
+
type: "text" as const,
|
|
54
|
+
text: JSON.stringify(withPaginationMeta(result), null, 2),
|
|
55
|
+
}],
|
|
56
|
+
};
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
server.tool(
|
|
61
|
+
"item_create",
|
|
62
|
+
[
|
|
63
|
+
"Create a new inventory item or service.",
|
|
64
|
+
"For physical products, set item_type='product' and provide initial stock_quantity.",
|
|
65
|
+
"For billable services (consulting, installation, etc.), set item_type='service' — stock is not tracked.",
|
|
66
|
+
"Monetary values (sale_price, purchase_price) are decimal strings without currency symbols: '250.00' not '₹250'.",
|
|
67
|
+
"Setting low_stock_alert triggers warnings when stock falls below that level.",
|
|
68
|
+
].join(" "),
|
|
69
|
+
{
|
|
70
|
+
name: z.string().min(1).max(200)
|
|
71
|
+
.describe("Item name as it should appear on invoices."),
|
|
72
|
+
item_type: z.enum(["product", "service"]).default("product")
|
|
73
|
+
.describe("'product' for physical goods, 'service' for services (no stock)."),
|
|
74
|
+
unit: z.enum(["pcs", "kg", "g", "l", "ml", "m", "cm", "ft", "in", "box", "dozen", "pair", "set", "pkt", "bun", "pouch", "jar", "btl", "bag", "ton", "pack", "pet", "person", "other"]).optional()
|
|
75
|
+
.describe("Unit of measurement. Default 'pcs'. Use 'kg' for weight, 'l' for liquids, etc."),
|
|
76
|
+
sale_price: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
77
|
+
.describe("Default selling price per unit as decimal string, e.g. '250.00'."),
|
|
78
|
+
purchase_price: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
79
|
+
.describe("Default purchase/cost price per unit as decimal string."),
|
|
80
|
+
tax_percent: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
81
|
+
.describe("Default GST/tax rate percentage, e.g. '18.00'. Default '0'."),
|
|
82
|
+
stock_quantity: z.string().regex(/^-?\d+(\.\d{1,3})?$/).optional()
|
|
83
|
+
.describe("Opening stock quantity as decimal string, e.g. '100.000'. Default '0'."),
|
|
84
|
+
low_stock_alert: z.string().regex(/^\d+(\.\d{1,3})?$/).optional()
|
|
85
|
+
.describe("Alert threshold: warn when stock falls below this quantity."),
|
|
86
|
+
hsn: z.string().max(20).optional()
|
|
87
|
+
.describe("HSN (Harmonized System of Nomenclature) code for GST compliance."),
|
|
88
|
+
sku: z.string().max(50).optional()
|
|
89
|
+
.describe("SKU (Stock Keeping Unit) — your internal product code."),
|
|
90
|
+
description: z.string().max(1000).optional()
|
|
91
|
+
.describe("Internal description (not shown on invoices)."),
|
|
92
|
+
category: z.string().max(100).optional()
|
|
93
|
+
.describe("Category for grouping, e.g. 'Electronics', 'Raw Materials'."),
|
|
94
|
+
},
|
|
95
|
+
wrapTool(async (input) => {
|
|
96
|
+
const item = await client.item.create({
|
|
97
|
+
name: input.name,
|
|
98
|
+
itemType: input.item_type,
|
|
99
|
+
unit: input.unit,
|
|
100
|
+
salePrice: input.sale_price,
|
|
101
|
+
purchasePrice: input.purchase_price,
|
|
102
|
+
taxPercent: input.tax_percent,
|
|
103
|
+
stockQuantity: input.stock_quantity,
|
|
104
|
+
lowStockAlert: input.low_stock_alert,
|
|
105
|
+
hsn: input.hsn,
|
|
106
|
+
sku: input.sku,
|
|
107
|
+
description: input.description,
|
|
108
|
+
category: input.category,
|
|
109
|
+
});
|
|
110
|
+
return {
|
|
111
|
+
content: [{
|
|
112
|
+
type: "text" as const,
|
|
113
|
+
text: JSON.stringify(item, null, 2),
|
|
114
|
+
}],
|
|
115
|
+
};
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
server.tool(
|
|
120
|
+
"item_get",
|
|
121
|
+
[
|
|
122
|
+
"Get full details of a single inventory item, including current stock quantity and variant information.",
|
|
123
|
+
"Use this to check current stock levels or get the full item spec before creating invoices.",
|
|
124
|
+
].join(" "),
|
|
125
|
+
{
|
|
126
|
+
item_id: z.string().uuid()
|
|
127
|
+
.describe("Item UUID from item_list."),
|
|
128
|
+
},
|
|
129
|
+
wrapTool(async (input) => {
|
|
130
|
+
const item = await client.item.get(input.item_id);
|
|
131
|
+
return {
|
|
132
|
+
content: [{
|
|
133
|
+
type: "text" as const,
|
|
134
|
+
text: JSON.stringify(item, null, 2),
|
|
135
|
+
}],
|
|
136
|
+
};
|
|
137
|
+
})
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
server.tool(
|
|
141
|
+
"item_update",
|
|
142
|
+
[
|
|
143
|
+
"Update an existing inventory item's details, pricing, or stock settings.",
|
|
144
|
+
"Only provide fields you want to change — all other fields remain unchanged.",
|
|
145
|
+
"Changing sale_price or purchase_price updates the default price for future invoices but does not retroactively change past invoice line items.",
|
|
146
|
+
].join(" "),
|
|
147
|
+
{
|
|
148
|
+
item_id: z.string().uuid()
|
|
149
|
+
.describe("Item UUID to update."),
|
|
150
|
+
name: z.string().min(1).max(200).optional()
|
|
151
|
+
.describe("Updated item name."),
|
|
152
|
+
sale_price: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
153
|
+
.describe("Updated selling price per unit as decimal string."),
|
|
154
|
+
purchase_price: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
155
|
+
.describe("Updated purchase/cost price per unit as decimal string."),
|
|
156
|
+
tax_percent: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
|
|
157
|
+
.describe("Updated GST/tax rate percentage, e.g. '18.00'."),
|
|
158
|
+
low_stock_alert: z.string().regex(/^\d+(\.\d{1,3})?$/).optional()
|
|
159
|
+
.describe("Updated low-stock alert threshold."),
|
|
160
|
+
hsn: z.string().max(20).optional()
|
|
161
|
+
.describe("Updated HSN code."),
|
|
162
|
+
sku: z.string().max(50).optional()
|
|
163
|
+
.describe("Updated SKU."),
|
|
164
|
+
description: z.string().max(1000).optional()
|
|
165
|
+
.describe("Updated internal description."),
|
|
166
|
+
category: z.string().max(100).optional()
|
|
167
|
+
.describe("Updated category."),
|
|
168
|
+
},
|
|
169
|
+
wrapTool(async (input) => {
|
|
170
|
+
const { item_id, ...fields } = input;
|
|
171
|
+
const item = await client.item.update(item_id, {
|
|
172
|
+
name: fields.name,
|
|
173
|
+
salePrice: fields.sale_price,
|
|
174
|
+
purchasePrice: fields.purchase_price,
|
|
175
|
+
taxPercent: fields.tax_percent,
|
|
176
|
+
lowStockAlert: fields.low_stock_alert,
|
|
177
|
+
hsn: fields.hsn,
|
|
178
|
+
sku: fields.sku,
|
|
179
|
+
description: fields.description,
|
|
180
|
+
category: fields.category,
|
|
181
|
+
});
|
|
182
|
+
return {
|
|
183
|
+
content: [{
|
|
184
|
+
type: "text" as const,
|
|
185
|
+
text: JSON.stringify(item, null, 2),
|
|
186
|
+
}],
|
|
187
|
+
};
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
server.tool(
|
|
192
|
+
"item_delete",
|
|
193
|
+
[
|
|
194
|
+
"Permanently delete an inventory item. Requires admin role.",
|
|
195
|
+
"Warning: this is a hard delete — it removes the item record and its variants.",
|
|
196
|
+
"Existing invoice line items that reference this item are not deleted (they retain the data at time of invoicing).",
|
|
197
|
+
"Only delete if the item was created in error. For discontinued items, consider just setting them inactive.",
|
|
198
|
+
].join(" "),
|
|
199
|
+
{
|
|
200
|
+
item_id: z.string().uuid()
|
|
201
|
+
.describe("Item UUID to delete."),
|
|
202
|
+
},
|
|
203
|
+
wrapTool(async (input) => {
|
|
204
|
+
const result = await client.item.delete(input.item_id);
|
|
205
|
+
return {
|
|
206
|
+
content: [{
|
|
207
|
+
type: "text" as const,
|
|
208
|
+
text: JSON.stringify(result, null, 2),
|
|
209
|
+
}],
|
|
210
|
+
};
|
|
211
|
+
})
|
|
212
|
+
);
|
|
213
|
+
|
|
214
|
+
server.tool(
|
|
215
|
+
"item_categories",
|
|
216
|
+
[
|
|
217
|
+
"Get a list of all distinct item categories used in the business.",
|
|
218
|
+
"Use this to discover valid category names before filtering item_list by category or creating items.",
|
|
219
|
+
].join(" "),
|
|
220
|
+
{},
|
|
221
|
+
wrapTool(async (_input) => {
|
|
222
|
+
const categories = await client.item.categories();
|
|
223
|
+
return {
|
|
224
|
+
content: [{
|
|
225
|
+
type: "text" as const,
|
|
226
|
+
text: JSON.stringify(categories, null, 2),
|
|
227
|
+
}],
|
|
228
|
+
};
|
|
229
|
+
})
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
server.tool(
|
|
233
|
+
"item_adjust_stock",
|
|
234
|
+
[
|
|
235
|
+
"Record a manual stock adjustment for an inventory item.",
|
|
236
|
+
"Use a positive adjustment to add stock (e.g. '+50' for stock received) and a negative adjustment to remove stock (e.g. '-5' for damaged goods).",
|
|
237
|
+
"Every adjustment is recorded in the audit log — always provide a reason.",
|
|
238
|
+
"Example: to record receiving 100 units from a supplier, set adjustment='+100' and reason='Stock received from Supplier X'.",
|
|
239
|
+
].join(" "),
|
|
240
|
+
{
|
|
241
|
+
item_id: z.string().uuid()
|
|
242
|
+
.describe("Item UUID from item_list."),
|
|
243
|
+
adjustment: z.string().regex(/^[+-]?\d+(\.\d{1,3})?$/)
|
|
244
|
+
.describe("Signed quantity change as decimal string. '+50' or '50' to add, '-10' to subtract."),
|
|
245
|
+
reason: z.string().max(500).optional()
|
|
246
|
+
.describe("Reason for the adjustment, e.g. 'Stock received from supplier', 'Damaged goods write-off'."),
|
|
247
|
+
},
|
|
248
|
+
wrapTool(async (input) => {
|
|
249
|
+
const result = await client.item.adjustStock({
|
|
250
|
+
itemId: input.item_id,
|
|
251
|
+
adjustment: input.adjustment,
|
|
252
|
+
reason: input.reason,
|
|
253
|
+
});
|
|
254
|
+
return {
|
|
255
|
+
content: [{
|
|
256
|
+
type: "text" as const,
|
|
257
|
+
text: JSON.stringify(result, null, 2),
|
|
258
|
+
}],
|
|
259
|
+
};
|
|
260
|
+
})
|
|
261
|
+
);
|
|
262
|
+
}
|