@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.
@@ -0,0 +1,246 @@
1
+ /**
2
+ * Business report tools — financial analysis and summaries.
3
+ *
4
+ * Tools registered:
5
+ * report_daybook — daily transaction log (invoices + payments + expenses)
6
+ * report_outstanding — outstanding receivables/payables with aging buckets
7
+ * report_tax_summary — tax collected/paid summary by rate
8
+ * report_item_sales — item-wise sales analysis with revenue and quantity
9
+ * report_stock_summary — current stock levels and values
10
+ * report_party_statement — party balance statement with transaction history
11
+ * report_payment_summary — payment trends by mode and type
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
+
19
+ export function registerReportTools(server: McpServer, client: HisaaboClient) {
20
+
21
+ server.tool(
22
+ "report_daybook",
23
+ [
24
+ "Get the daybook (daily transaction log) for a date range.",
25
+ "Returns all invoices, payments, and expenses in chronological order with debit/credit columns.",
26
+ "Use type_filter to narrow to only invoices, payments, or expenses.",
27
+ "The summary shows totals: sales invoiced, purchases invoiced, payments received, payments made, expenses, and net cash movement.",
28
+ "Dates are plain date strings (YYYY-MM-DD), not ISO datetime.",
29
+ ].join(" "),
30
+ {
31
+ from_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
32
+ .describe("Start date in YYYY-MM-DD format, e.g. '2024-04-01'."),
33
+ to_date: z.string().regex(/^\d{4}-\d{2}-\d{2}$/)
34
+ .describe("End date in YYYY-MM-DD format, e.g. '2024-04-30'."),
35
+ type_filter: z.enum(["all", "invoices", "payments", "expenses"]).default("all")
36
+ .describe("'all' = everything, 'invoices' = only invoice entries, 'payments' = only payment entries, 'expenses' = only expense entries."),
37
+ },
38
+ wrapTool(async (input) => {
39
+ const result = await client.reports.daybook({
40
+ fromDate: input.from_date,
41
+ toDate: input.to_date,
42
+ typeFilter: input.type_filter,
43
+ });
44
+ return {
45
+ content: [{
46
+ type: "text" as const,
47
+ text: JSON.stringify(result, null, 2),
48
+ }],
49
+ };
50
+ })
51
+ );
52
+
53
+ server.tool(
54
+ "report_outstanding",
55
+ [
56
+ "Get outstanding receivables (what customers owe you) and/or payables (what you owe suppliers).",
57
+ "Results are grouped by party with aging buckets: current (0-30 days), 31-60 days, 61-90 days, 90+ days.",
58
+ "Use type='receivable' for unpaid customer invoices, 'payable' for unpaid supplier bills, 'both' for all.",
59
+ "as_of_date lets you see outstanding amounts as of a specific past date.",
60
+ ].join(" "),
61
+ {
62
+ type: z.enum(["receivable", "payable", "both"]).default("receivable")
63
+ .describe("'receivable' = unpaid customer invoices, 'payable' = unpaid supplier bills, 'both' = all outstanding."),
64
+ as_of_date: z.string().datetime().optional()
65
+ .describe("Calculate outstanding as of this date (ISO 8601). Defaults to today."),
66
+ },
67
+ wrapTool(async (input) => {
68
+ const result = await client.reports.outstanding({
69
+ type: input.type,
70
+ asOfDate: input.as_of_date,
71
+ });
72
+ return {
73
+ content: [{
74
+ type: "text" as const,
75
+ text: JSON.stringify(result, null, 2),
76
+ }],
77
+ };
78
+ })
79
+ );
80
+
81
+ server.tool(
82
+ "report_tax_summary",
83
+ [
84
+ "Get a tax summary (GST collected/paid) for a date range, broken down by tax rate.",
85
+ "Use this to answer 'How much GST did we collect this month?' or 'What is our total ITC (input tax credit)?'",
86
+ "Results are grouped by tax rate (0%, 5%, 12%, 18%, 28%) showing taxable amount and tax amount.",
87
+ "type='sales' = output tax (collected from customers), 'purchases' = input tax (ITC from suppliers).",
88
+ ].join(" "),
89
+ {
90
+ from_date: z.string().datetime()
91
+ .describe("Start date (ISO 8601)."),
92
+ to_date: z.string().datetime()
93
+ .describe("End date (ISO 8601)."),
94
+ type: z.enum(["sales", "purchases", "both"]).default("both")
95
+ .describe("'sales' = tax on outward supplies, 'purchases' = input tax credit, 'both' = combined."),
96
+ },
97
+ wrapTool(async (input) => {
98
+ const result = await client.reports.taxSummary({
99
+ fromDate: input.from_date,
100
+ toDate: input.to_date,
101
+ type: input.type,
102
+ });
103
+ return {
104
+ content: [{
105
+ type: "text" as const,
106
+ text: JSON.stringify(result, null, 2),
107
+ }],
108
+ };
109
+ })
110
+ );
111
+
112
+ server.tool(
113
+ "report_item_sales",
114
+ [
115
+ "Get item-wise sales analysis for a date range.",
116
+ "Returns revenue, quantity sold, invoice count, and margin for each item.",
117
+ "Use sort_by to rank items by revenue (default), quantity, number of invoices, or profit margin.",
118
+ "Use compare_to_previous=true to include comparison with the equivalent prior period.",
119
+ "Use this to answer 'What were our top-selling products this quarter?' or 'Which items have the best margins?'",
120
+ ].join(" "),
121
+ {
122
+ from_date: z.string().datetime()
123
+ .describe("Start date (ISO 8601)."),
124
+ to_date: z.string().datetime()
125
+ .describe("End date (ISO 8601)."),
126
+ category: z.string().max(100).optional()
127
+ .describe("Filter by item category."),
128
+ item_type: z.enum(["product", "service"]).optional()
129
+ .describe("'product' for physical goods, 'service' for services."),
130
+ sort_by: z.enum(["revenue", "quantity", "invoices", "margin"]).default("revenue")
131
+ .describe("Sort by: 'revenue' (total sales amount), 'quantity' (units sold), 'invoices' (order count), 'margin' (profit)."),
132
+ compare_to_previous: z.boolean().default(false)
133
+ .describe("If true, include comparison data from the previous equivalent period."),
134
+ },
135
+ wrapTool(async (input) => {
136
+ const result = await client.reports.itemSales({
137
+ fromDate: input.from_date,
138
+ toDate: input.to_date,
139
+ category: input.category,
140
+ itemType: input.item_type,
141
+ sortBy: input.sort_by,
142
+ compareToPrevious: input.compare_to_previous,
143
+ });
144
+ return {
145
+ content: [{
146
+ type: "text" as const,
147
+ text: JSON.stringify(result, null, 2),
148
+ }],
149
+ };
150
+ })
151
+ );
152
+
153
+ server.tool(
154
+ "report_stock_summary",
155
+ [
156
+ "Get current stock levels and values for all inventory items.",
157
+ "Returns quantity on hand, sale value, and purchase value for each product.",
158
+ "Use category to filter by product category.",
159
+ "Set show_zero_stock=true to include items with zero stock (useful for identifying out-of-stock items).",
160
+ "Use this to answer 'What is the total value of our inventory?' or 'Which products are out of stock?'",
161
+ ].join(" "),
162
+ {
163
+ category: z.string().max(100).optional()
164
+ .describe("Filter by item category."),
165
+ show_zero_stock: z.boolean().default(false)
166
+ .describe("If true, include items with zero stock. Default false (only items with stock > 0)."),
167
+ },
168
+ wrapTool(async (input) => {
169
+ const result = await client.reports.stockSummary({
170
+ category: input.category,
171
+ showZeroStock: input.show_zero_stock,
172
+ });
173
+ return {
174
+ content: [{
175
+ type: "text" as const,
176
+ text: JSON.stringify(result, null, 2),
177
+ }],
178
+ };
179
+ })
180
+ );
181
+
182
+ server.tool(
183
+ "report_party_statement",
184
+ [
185
+ "Get a party (customer or supplier) account statement showing all transactions and running balance.",
186
+ "Returns invoices, payments, credit notes, and other entries in chronological order.",
187
+ "Use this to answer 'Show me the complete transaction history with Customer X' or 'What is the account statement for this supplier?'",
188
+ "Note: for a quick balance only, use party_get instead.",
189
+ ].join(" "),
190
+ {
191
+ party_id: z.string().uuid()
192
+ .describe("Party UUID from party_list or party_get."),
193
+ from_date: z.string().datetime().optional()
194
+ .describe("Start date for the statement (ISO 8601). Omit for full history."),
195
+ to_date: z.string().datetime().optional()
196
+ .describe("End date for the statement (ISO 8601)."),
197
+ },
198
+ wrapTool(async (input) => {
199
+ const result = await client.reports.partyStatement({
200
+ partyId: input.party_id,
201
+ fromDate: input.from_date,
202
+ toDate: input.to_date,
203
+ });
204
+ return {
205
+ content: [{
206
+ type: "text" as const,
207
+ text: JSON.stringify(result, null, 2),
208
+ }],
209
+ };
210
+ })
211
+ );
212
+
213
+ server.tool(
214
+ "report_payment_summary",
215
+ [
216
+ "Get a payment summary showing trends by payment mode (cash, UPI, bank, cheque) over a date range.",
217
+ "Use type='received' for incoming payments from customers, 'made' for payments to suppliers, 'both' for all.",
218
+ "Filter by bank_account_id to see payments for a specific account.",
219
+ "Use this to answer 'How much did we receive via UPI this month?' or 'What were our total bank deposits?'",
220
+ ].join(" "),
221
+ {
222
+ from_date: z.string().datetime()
223
+ .describe("Start date (ISO 8601)."),
224
+ to_date: z.string().datetime()
225
+ .describe("End date (ISO 8601)."),
226
+ type: z.enum(["received", "made", "both"]).default("both")
227
+ .describe("'received' = payments from customers, 'made' = payments to suppliers, 'both' = all."),
228
+ bank_account_id: z.string().uuid().optional()
229
+ .describe("Filter to payments recorded against a specific bank/cash account."),
230
+ },
231
+ wrapTool(async (input) => {
232
+ const result = await client.reports.paymentSummary({
233
+ fromDate: input.from_date,
234
+ toDate: input.to_date,
235
+ type: input.type,
236
+ bankAccountId: input.bank_account_id,
237
+ });
238
+ return {
239
+ content: [{
240
+ type: "text" as const,
241
+ text: JSON.stringify(result, null, 2),
242
+ }],
243
+ };
244
+ })
245
+ );
246
+ }
@@ -0,0 +1,222 @@
1
+ /**
2
+ * Shipment tools — track outbound and inbound shipments.
3
+ *
4
+ * Tools registered:
5
+ * shipment_list — list shipments with filters
6
+ * shipment_get — get full shipment details
7
+ * shipment_create — create a new shipment record
8
+ * shipment_update — update status, tracking, or delivery dates
9
+ * shipment_delete — delete a shipment record
10
+ */
11
+
12
+ import { z } from "zod";
13
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import type { HisaaboClient } from "../client.js";
15
+ import { wrapTool } from "../lib/errors.js";
16
+ import { MAX_PAGE_SIZE, withPaginationMeta } from "../lib/pagination.js";
17
+
18
+ const SHIPMENT_STATUSES = ["pending", "shipped", "in_transit", "delivered", "returned"] as const;
19
+
20
+ export function registerShipmentTools(server: McpServer, client: HisaaboClient) {
21
+
22
+ server.tool(
23
+ "shipment_list",
24
+ [
25
+ "List shipments for the active business with optional filters.",
26
+ "Use status='pending' to find shipments not yet dispatched, or status='in_transit' for active deliveries.",
27
+ "Filter by invoice_id to see all shipments for a specific invoice, or by party_id for a specific customer.",
28
+ ].join(" "),
29
+ {
30
+ status: z.enum(SHIPMENT_STATUSES).optional()
31
+ .describe("Filter by shipment status: 'pending', 'shipped', 'in_transit', 'delivered', or 'returned'."),
32
+ invoice_id: z.string().uuid().optional()
33
+ .describe("Filter shipments linked to a specific invoice UUID."),
34
+ party_id: z.string().uuid().optional()
35
+ .describe("Filter shipments for a specific customer/party UUID."),
36
+ page: z.number().int().min(1).default(1)
37
+ .describe("Page number for pagination."),
38
+ },
39
+ wrapTool(async (input) => {
40
+ const result = await client.shipment.list({
41
+ status: input.status,
42
+ invoiceId: input.invoice_id,
43
+ partyId: input.party_id,
44
+ page: input.page,
45
+ limit: MAX_PAGE_SIZE,
46
+ });
47
+ return {
48
+ content: [{
49
+ type: "text" as const,
50
+ text: JSON.stringify(withPaginationMeta(result), null, 2),
51
+ }],
52
+ };
53
+ })
54
+ );
55
+
56
+ server.tool(
57
+ "shipment_get",
58
+ [
59
+ "Get full details of a single shipment, including tracking info, carrier, address, and delivery dates.",
60
+ "If the carrier is one of the 7 pre-configured Indian carriers (Delhivery, BlueDart, DTDC, Ecom Express, India Post, Shadowfax, Xpressbees), a tracking URL is auto-generated.",
61
+ ].join(" "),
62
+ {
63
+ shipment_id: z.string().uuid()
64
+ .describe("Shipment UUID from shipment_list."),
65
+ },
66
+ wrapTool(async (input) => {
67
+ const shipment = await client.shipment.get(input.shipment_id);
68
+ return {
69
+ content: [{
70
+ type: "text" as const,
71
+ text: JSON.stringify(shipment, null, 2),
72
+ }],
73
+ };
74
+ })
75
+ );
76
+
77
+ server.tool(
78
+ "shipment_create",
79
+ [
80
+ "Create a new shipment record for an invoice or party.",
81
+ "For known carriers (Delhivery, BlueDart, DTDC, Ecom Express, India Post, Shadowfax, Xpressbees), providing carrier + tracking_number auto-generates the tracking URL.",
82
+ "For custom carriers, provide tracking_url manually.",
83
+ "Cost is the shipping cost charged; weight is the parcel weight in kg.",
84
+ ].join(" "),
85
+ {
86
+ invoice_id: z.string().uuid().optional()
87
+ .describe("Link this shipment to an invoice UUID. Use shipment_list to avoid duplicates."),
88
+ party_id: z.string().uuid().optional()
89
+ .describe("Customer/party UUID for this shipment."),
90
+ carrier: z.string().max(100).optional()
91
+ .describe("Carrier name, e.g. 'Delhivery', 'BlueDart', 'DTDC'. Known carriers get auto-tracking URLs."),
92
+ mode: z.string().max(50).optional()
93
+ .describe("Delivery mode, e.g. 'surface', 'air', 'express'. Matches business custom shipping methods."),
94
+ tracking_number: z.string().max(200).optional()
95
+ .describe("AWB/tracking number from the carrier. Required for auto-generating tracking URL."),
96
+ tracking_url: z.string().max(500).optional()
97
+ .describe("Manual tracking URL if carrier is not in the pre-configured list."),
98
+ cost: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
99
+ .describe("Shipping cost as decimal string, e.g. '250.00'. Default '0'."),
100
+ weight: z.string().regex(/^\d+(\.\d{1,3})?$/).optional()
101
+ .describe("Parcel weight in kg as decimal string, e.g. '2.500'."),
102
+ shipping_address: z.string().optional()
103
+ .describe("Delivery address (street/flat/block)."),
104
+ shipping_city: z.string().optional()
105
+ .describe("Delivery city."),
106
+ shipping_pincode: z.string().optional()
107
+ .describe("Delivery PIN code."),
108
+ status: z.enum(SHIPMENT_STATUSES).optional()
109
+ .describe("Initial status. Default 'pending'. Set 'shipped' if already dispatched."),
110
+ shipment_date: z.string().datetime().optional()
111
+ .describe("Date the parcel was dispatched (ISO 8601). Defaults to today."),
112
+ estimated_delivery: z.string().datetime().optional()
113
+ .describe("Expected delivery date (ISO 8601)."),
114
+ notes: z.string().optional()
115
+ .describe("Internal notes about this shipment."),
116
+ },
117
+ wrapTool(async (input) => {
118
+ const shipment = await client.shipment.create({
119
+ invoiceId: input.invoice_id,
120
+ partyId: input.party_id,
121
+ carrier: input.carrier,
122
+ mode: input.mode,
123
+ trackingNumber: input.tracking_number,
124
+ trackingUrl: input.tracking_url,
125
+ cost: input.cost,
126
+ weight: input.weight,
127
+ shippingAddress: input.shipping_address,
128
+ shippingCity: input.shipping_city,
129
+ shippingPincode: input.shipping_pincode,
130
+ status: input.status,
131
+ shipmentDate: input.shipment_date,
132
+ estimatedDelivery: input.estimated_delivery,
133
+ notes: input.notes,
134
+ });
135
+ return {
136
+ content: [{
137
+ type: "text" as const,
138
+ text: JSON.stringify(shipment, null, 2),
139
+ }],
140
+ };
141
+ })
142
+ );
143
+
144
+ server.tool(
145
+ "shipment_update",
146
+ [
147
+ "Update a shipment's status, tracking information, or delivery dates.",
148
+ "Use this to mark a shipment as shipped (set status='shipped'), in transit, or delivered.",
149
+ "Setting status='delivered' automatically records the actual delivery date if not provided.",
150
+ "Update tracking_number to trigger re-generation of the carrier tracking URL.",
151
+ ].join(" "),
152
+ {
153
+ shipment_id: z.string().uuid()
154
+ .describe("Shipment UUID to update."),
155
+ carrier: z.string().max(100).optional()
156
+ .describe("Updated carrier name."),
157
+ mode: z.string().max(50).optional()
158
+ .describe("Updated delivery mode."),
159
+ tracking_number: z.string().max(200).optional()
160
+ .describe("Updated tracking/AWB number. Re-generates tracking URL for known carriers."),
161
+ tracking_url: z.string().max(500).optional()
162
+ .describe("Manual tracking URL override."),
163
+ cost: z.string().regex(/^\d+(\.\d{1,2})?$/).optional()
164
+ .describe("Updated shipping cost as decimal string."),
165
+ weight: z.string().regex(/^\d+(\.\d{1,3})?$/).optional()
166
+ .describe("Updated parcel weight in kg."),
167
+ status: z.enum(SHIPMENT_STATUSES).optional()
168
+ .describe("New status. 'shipped' = dispatched. 'in_transit' = en route. 'delivered' = received. 'returned' = sent back."),
169
+ shipment_date: z.string().datetime().optional()
170
+ .describe("Updated dispatch date (ISO 8601)."),
171
+ estimated_delivery: z.string().datetime().optional()
172
+ .describe("Updated estimated delivery date (ISO 8601)."),
173
+ actual_delivery: z.string().datetime().optional()
174
+ .describe("Actual delivery date (ISO 8601). Auto-set when status becomes 'delivered' if omitted."),
175
+ notes: z.string().optional()
176
+ .describe("Updated notes."),
177
+ },
178
+ wrapTool(async (input) => {
179
+ const shipment = await client.shipment.update({
180
+ id: input.shipment_id,
181
+ carrier: input.carrier,
182
+ mode: input.mode,
183
+ trackingNumber: input.tracking_number,
184
+ trackingUrl: input.tracking_url,
185
+ cost: input.cost,
186
+ weight: input.weight,
187
+ status: input.status,
188
+ shipmentDate: input.shipment_date,
189
+ estimatedDelivery: input.estimated_delivery,
190
+ actualDelivery: input.actual_delivery,
191
+ notes: input.notes,
192
+ });
193
+ return {
194
+ content: [{
195
+ type: "text" as const,
196
+ text: JSON.stringify(shipment, null, 2),
197
+ }],
198
+ };
199
+ })
200
+ );
201
+
202
+ server.tool(
203
+ "shipment_delete",
204
+ [
205
+ "Delete a shipment record. This is a hard delete — the shipment record is permanently removed.",
206
+ "Requires admin role. Use with caution; prefer updating status to 'returned' instead of deleting.",
207
+ ].join(" "),
208
+ {
209
+ shipment_id: z.string().uuid()
210
+ .describe("Shipment UUID to delete."),
211
+ },
212
+ wrapTool(async (input) => {
213
+ const result = await client.shipment.delete(input.shipment_id);
214
+ return {
215
+ content: [{
216
+ type: "text" as const,
217
+ text: JSON.stringify(result, null, 2),
218
+ }],
219
+ };
220
+ })
221
+ );
222
+ }
@@ -0,0 +1,177 @@
1
+ /**
2
+ * Online store tools — manage the business's public storefront.
3
+ *
4
+ * Tools registered:
5
+ * store_settings — get current store configuration
6
+ * store_update_settings — update store configuration
7
+ * store_orders — list customer orders from the store
8
+ * store_order_get — get full details of a store order
9
+ * store_order_update — update order status (preparing/ready/delivered)
10
+ */
11
+
12
+ import { z } from "zod";
13
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
14
+ import type { HisaaboClient } from "../client.js";
15
+ import { wrapTool } from "../lib/errors.js";
16
+ import { MAX_PAGE_SIZE, withPaginationMeta } from "../lib/pagination.js";
17
+
18
+ const ORDER_STATUSES = ["pending", "confirmed", "preparing", "ready", "delivered", "cancelled"] as const;
19
+
20
+ export function registerStoreTools(server: McpServer, client: HisaaboClient) {
21
+
22
+ server.tool(
23
+ "store_settings",
24
+ [
25
+ "Get the current online store configuration for the active business.",
26
+ "Returns whether the store is enabled, the store URL slug, tagline, accent color, minimum order amount, and order prefix.",
27
+ "If storeEnabled=false, the public store is not accessible.",
28
+ "The store URL is: https://<storeSlug>.hisaabo.in (when enabled).",
29
+ ].join(" "),
30
+ {},
31
+ wrapTool(async (_input) => {
32
+ const settings = await client.store.getSettings();
33
+ return {
34
+ content: [{
35
+ type: "text" as const,
36
+ text: JSON.stringify(settings, null, 2),
37
+ }],
38
+ };
39
+ })
40
+ );
41
+
42
+ server.tool(
43
+ "store_update_settings",
44
+ [
45
+ "Update the online store configuration.",
46
+ "Enable/disable the store, change the URL slug, tagline, accent color, minimum order amount, etc.",
47
+ "store_slug must be unique across all businesses and match the pattern: lowercase letters, numbers, and hyphens.",
48
+ "Setting store_enabled=true activates the public storefront.",
49
+ ].join(" "),
50
+ {
51
+ store_enabled: z.boolean().optional()
52
+ .describe("Enable (true) or disable (false) the public storefront."),
53
+ store_slug: z.string().min(3).max(50).regex(/^[a-z0-9][a-z0-9-]*[a-z0-9]$/).optional().nullable()
54
+ .describe("URL slug for the store, e.g. 'acme-traders'. Must be lowercase alphanumeric with hyphens."),
55
+ store_tagline: z.string().max(200).optional().nullable()
56
+ .describe("Short tagline shown on the store homepage, e.g. 'Fresh groceries delivered daily'."),
57
+ store_accent_color: z.string().regex(/^#[0-9a-fA-F]{6}$/).optional().nullable()
58
+ .describe("Brand accent color as hex code, e.g. '#FF5722'."),
59
+ store_min_order_amount: z.string().regex(/^\d+(\.\d{1,2})?$/).optional().nullable()
60
+ .describe("Minimum order value as decimal string, e.g. '200.00'. Null to remove minimum."),
61
+ store_delivery_note: z.string().max(500).optional().nullable()
62
+ .describe("Note shown at checkout about delivery, e.g. 'Delivery within 2 hours in city limits'."),
63
+ store_whatsapp_number: z.string().max(15).optional().nullable()
64
+ .describe("WhatsApp number for order notifications (digits only)."),
65
+ store_allow_negative_stock: z.boolean().optional()
66
+ .describe("If true, orders can be placed even when stock is zero or negative."),
67
+ store_order_prefix: z.string().min(1).max(10).optional()
68
+ .describe("Prefix for store order numbers, e.g. 'ORD'. Default 'ORD'."),
69
+ },
70
+ wrapTool(async (input) => {
71
+ const settings = await client.store.updateSettings({
72
+ storeEnabled: input.store_enabled,
73
+ storeSlug: input.store_slug,
74
+ storeTagline: input.store_tagline,
75
+ storeAccentColor: input.store_accent_color,
76
+ storeMinOrderAmount: input.store_min_order_amount,
77
+ storeDeliveryNote: input.store_delivery_note,
78
+ storeWhatsappNumber: input.store_whatsapp_number,
79
+ storeAllowNegativeStock: input.store_allow_negative_stock,
80
+ storeOrderPrefix: input.store_order_prefix,
81
+ });
82
+ return {
83
+ content: [{
84
+ type: "text" as const,
85
+ text: JSON.stringify(settings, null, 2),
86
+ }],
87
+ };
88
+ })
89
+ );
90
+
91
+ server.tool(
92
+ "store_orders",
93
+ [
94
+ "List customer orders placed through the online store.",
95
+ "Orders progress through statuses: pending → confirmed → preparing → ready → delivered.",
96
+ "Use status='pending' to find new orders requiring attention.",
97
+ "Search by customer name, phone number, or order number.",
98
+ ].join(" "),
99
+ {
100
+ status: z.enum(ORDER_STATUSES).optional()
101
+ .describe("Filter by order status. 'pending' = new orders awaiting confirmation."),
102
+ from_date: z.string().datetime().optional()
103
+ .describe("Filter orders placed on or after this date (ISO 8601)."),
104
+ to_date: z.string().datetime().optional()
105
+ .describe("Filter orders placed on or before this date (ISO 8601)."),
106
+ search: z.string().max(200).optional()
107
+ .describe("Search by customer name, phone number, or order number."),
108
+ page: z.number().int().min(1).default(1)
109
+ .describe("Page number for pagination."),
110
+ },
111
+ wrapTool(async (input) => {
112
+ const result = await client.store.listOrders({
113
+ status: input.status,
114
+ fromDate: input.from_date,
115
+ toDate: input.to_date,
116
+ search: input.search,
117
+ page: input.page,
118
+ limit: MAX_PAGE_SIZE,
119
+ });
120
+ return {
121
+ content: [{
122
+ type: "text" as const,
123
+ text: JSON.stringify(withPaginationMeta(result), null, 2),
124
+ }],
125
+ };
126
+ })
127
+ );
128
+
129
+ server.tool(
130
+ "store_order_get",
131
+ [
132
+ "Get full details of a single store order, including line items and linked invoice.",
133
+ "If the order has been confirmed, it will have a linked invoice (invoiceId) in the response.",
134
+ ].join(" "),
135
+ {
136
+ order_id: z.string().uuid()
137
+ .describe("Store order UUID from store_orders."),
138
+ },
139
+ wrapTool(async (input) => {
140
+ const order = await client.store.getOrder(input.order_id);
141
+ return {
142
+ content: [{
143
+ type: "text" as const,
144
+ text: JSON.stringify(order, null, 2),
145
+ }],
146
+ };
147
+ })
148
+ );
149
+
150
+ server.tool(
151
+ "store_order_update",
152
+ [
153
+ "Update a confirmed store order's status to 'preparing', 'ready', or 'delivered'.",
154
+ "Orders must be confirmed first (status='confirmed') before they can be updated.",
155
+ "Use 'preparing' when the order is being packed, 'ready' when ready for pickup/delivery, 'delivered' when handed to customer.",
156
+ "To confirm a pending order or cancel an order, use the Hisaabo web app — those actions also update the linked invoice.",
157
+ ].join(" "),
158
+ {
159
+ order_id: z.string().uuid()
160
+ .describe("Store order UUID."),
161
+ status: z.enum(["preparing", "ready", "delivered"])
162
+ .describe("New status: 'preparing' (being packed), 'ready' (ready to deliver), 'delivered' (handed to customer)."),
163
+ },
164
+ wrapTool(async (input) => {
165
+ const result = await client.store.updateOrderStatus({
166
+ orderId: input.order_id,
167
+ status: input.status,
168
+ });
169
+ return {
170
+ content: [{
171
+ type: "text" as const,
172
+ text: JSON.stringify(result, null, 2),
173
+ }],
174
+ };
175
+ })
176
+ );
177
+ }