@hasna/microservices 0.0.4 → 0.0.5

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 (38) hide show
  1. package/microservices/microservice-ads/src/cli/index.ts +198 -0
  2. package/microservices/microservice-ads/src/db/campaigns.ts +304 -0
  3. package/microservices/microservice-ads/src/mcp/index.ts +160 -0
  4. package/microservices/microservice-contracts/src/cli/index.ts +410 -23
  5. package/microservices/microservice-contracts/src/db/contracts.ts +430 -1
  6. package/microservices/microservice-contracts/src/db/migrations.ts +83 -0
  7. package/microservices/microservice-contracts/src/mcp/index.ts +312 -3
  8. package/microservices/microservice-domains/src/cli/index.ts +253 -0
  9. package/microservices/microservice-domains/src/db/domains.ts +613 -0
  10. package/microservices/microservice-domains/src/index.ts +21 -0
  11. package/microservices/microservice-domains/src/mcp/index.ts +168 -0
  12. package/microservices/microservice-hiring/src/cli/index.ts +318 -8
  13. package/microservices/microservice-hiring/src/db/hiring.ts +503 -0
  14. package/microservices/microservice-hiring/src/db/migrations.ts +21 -0
  15. package/microservices/microservice-hiring/src/index.ts +29 -0
  16. package/microservices/microservice-hiring/src/lib/scoring.ts +206 -0
  17. package/microservices/microservice-hiring/src/mcp/index.ts +245 -0
  18. package/microservices/microservice-payments/src/cli/index.ts +255 -3
  19. package/microservices/microservice-payments/src/db/migrations.ts +18 -0
  20. package/microservices/microservice-payments/src/db/payments.ts +552 -0
  21. package/microservices/microservice-payments/src/mcp/index.ts +223 -0
  22. package/microservices/microservice-payroll/src/cli/index.ts +269 -0
  23. package/microservices/microservice-payroll/src/db/migrations.ts +26 -0
  24. package/microservices/microservice-payroll/src/db/payroll.ts +636 -0
  25. package/microservices/microservice-payroll/src/mcp/index.ts +246 -0
  26. package/microservices/microservice-shipping/src/cli/index.ts +211 -3
  27. package/microservices/microservice-shipping/src/db/migrations.ts +8 -0
  28. package/microservices/microservice-shipping/src/db/shipping.ts +453 -3
  29. package/microservices/microservice-shipping/src/mcp/index.ts +149 -1
  30. package/microservices/microservice-social/src/cli/index.ts +244 -2
  31. package/microservices/microservice-social/src/db/migrations.ts +33 -0
  32. package/microservices/microservice-social/src/db/social.ts +378 -4
  33. package/microservices/microservice-social/src/mcp/index.ts +221 -1
  34. package/microservices/microservice-subscriptions/src/cli/index.ts +315 -0
  35. package/microservices/microservice-subscriptions/src/db/migrations.ts +68 -0
  36. package/microservices/microservice-subscriptions/src/db/subscriptions.ts +567 -3
  37. package/microservices/microservice-subscriptions/src/mcp/index.ts +267 -1
  38. package/package.json +1 -1
@@ -83,6 +83,7 @@ export interface Return {
83
83
  id: string;
84
84
  order_id: string;
85
85
  reason: string | null;
86
+ rma_code: string | null;
86
87
  status: "requested" | "approved" | "received" | "refunded";
87
88
  created_at: string;
88
89
  updated_at: string;
@@ -92,6 +93,7 @@ interface ReturnRow {
92
93
  id: string;
93
94
  order_id: string;
94
95
  reason: string | null;
96
+ rma_code: string | null;
95
97
  status: string;
96
98
  created_at: string;
97
99
  updated_at: string;
@@ -476,20 +478,32 @@ export interface CreateReturnInput {
476
478
  order_id: string;
477
479
  reason?: string;
478
480
  status?: Return["status"];
481
+ auto_rma?: boolean;
482
+ }
483
+
484
+ function generateRmaCode(): string {
485
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
486
+ let code = "RMA-";
487
+ for (let i = 0; i < 8; i++) {
488
+ code += chars.charAt(Math.floor(Math.random() * chars.length));
489
+ }
490
+ return code;
479
491
  }
480
492
 
481
493
  export function createReturn(input: CreateReturnInput): Return {
482
494
  const db = getDatabase();
483
495
  const id = crypto.randomUUID();
496
+ const rmaCode = input.auto_rma ? generateRmaCode() : null;
484
497
 
485
498
  db.prepare(
486
- `INSERT INTO returns (id, order_id, reason, status)
487
- VALUES (?, ?, ?, ?)`
499
+ `INSERT INTO returns (id, order_id, reason, status, rma_code)
500
+ VALUES (?, ?, ?, ?, ?)`
488
501
  ).run(
489
502
  id,
490
503
  input.order_id,
491
504
  input.reason || null,
492
- input.status || "requested"
505
+ input.status || "requested",
506
+ rmaCode
493
507
  );
494
508
 
495
509
  return getReturn(id)!;
@@ -641,3 +655,439 @@ export function getCostsByCarrier(): CarrierCosts[] {
641
655
  ).all() as CarrierCosts[];
642
656
  return rows;
643
657
  }
658
+
659
+ // ─── Bulk Import/Export ─────────────────────────────────────────────────────
660
+
661
+ export interface BulkImportResult {
662
+ imported: number;
663
+ errors: { line: number; message: string }[];
664
+ }
665
+
666
+ export function bulkImportOrders(csvData: string): BulkImportResult {
667
+ const lines = csvData.trim().split("\n");
668
+ if (lines.length < 2) return { imported: 0, errors: [{ line: 1, message: "No data rows found" }] };
669
+
670
+ const header = lines[0].split(",").map((h) => h.trim().toLowerCase());
671
+ const result: BulkImportResult = { imported: 0, errors: [] };
672
+
673
+ for (let i = 1; i < lines.length; i++) {
674
+ try {
675
+ const values = parseCSVLine(lines[i]);
676
+ const row: Record<string, string> = {};
677
+ header.forEach((h, idx) => {
678
+ row[h] = (values[idx] || "").trim();
679
+ });
680
+
681
+ const address: Address = {
682
+ street: row["street"] || "",
683
+ city: row["city"] || "",
684
+ state: row["state"] || "",
685
+ zip: row["zip"] || "",
686
+ country: row["country"] || "US",
687
+ };
688
+
689
+ let items: OrderItem[] = [];
690
+ if (row["items_json"]) {
691
+ try {
692
+ items = JSON.parse(row["items_json"]);
693
+ } catch {
694
+ items = [];
695
+ }
696
+ }
697
+
698
+ createOrder({
699
+ customer_name: row["customer_name"] || "Unknown",
700
+ customer_email: row["customer_email"] || undefined,
701
+ address,
702
+ items,
703
+ total_value: row["total_value"] ? parseFloat(row["total_value"]) : undefined,
704
+ });
705
+
706
+ result.imported++;
707
+ } catch (err) {
708
+ result.errors.push({
709
+ line: i + 1,
710
+ message: err instanceof Error ? err.message : String(err),
711
+ });
712
+ }
713
+ }
714
+
715
+ return result;
716
+ }
717
+
718
+ function parseCSVLine(line: string): string[] {
719
+ const result: string[] = [];
720
+ let current = "";
721
+ let inQuotes = false;
722
+
723
+ for (let i = 0; i < line.length; i++) {
724
+ const ch = line[i];
725
+ if (inQuotes) {
726
+ if (ch === '"' && line[i + 1] === '"') {
727
+ current += '"';
728
+ i++;
729
+ } else if (ch === '"') {
730
+ inQuotes = false;
731
+ } else {
732
+ current += ch;
733
+ }
734
+ } else {
735
+ if (ch === '"') {
736
+ inQuotes = true;
737
+ } else if (ch === ",") {
738
+ result.push(current);
739
+ current = "";
740
+ } else {
741
+ current += ch;
742
+ }
743
+ }
744
+ }
745
+ result.push(current);
746
+ return result;
747
+ }
748
+
749
+ export function exportOrders(
750
+ format: "csv" | "json",
751
+ dateFrom?: string,
752
+ dateTo?: string
753
+ ): string {
754
+ const db = getDatabase();
755
+ const conditions: string[] = [];
756
+ const params: unknown[] = [];
757
+
758
+ if (dateFrom) {
759
+ conditions.push("created_at >= ?");
760
+ params.push(dateFrom);
761
+ }
762
+ if (dateTo) {
763
+ conditions.push("created_at <= ?");
764
+ params.push(dateTo);
765
+ }
766
+
767
+ let sql = "SELECT * FROM orders";
768
+ if (conditions.length > 0) {
769
+ sql += " WHERE " + conditions.join(" AND ");
770
+ }
771
+ sql += " ORDER BY created_at DESC";
772
+
773
+ const rows = db.prepare(sql).all(...params) as OrderRow[];
774
+ const orders = rows.map(rowToOrder);
775
+
776
+ if (format === "json") {
777
+ return JSON.stringify(orders, null, 2);
778
+ }
779
+
780
+ // CSV format
781
+ const csvHeader = "id,customer_name,customer_email,street,city,state,zip,country,items_json,total_value,currency,status,created_at";
782
+ const csvRows = orders.map((o) => {
783
+ const itemsJson = JSON.stringify(o.items).replace(/"/g, '""');
784
+ return [
785
+ o.id,
786
+ escapeCSV(o.customer_name),
787
+ o.customer_email || "",
788
+ escapeCSV(o.address.street),
789
+ escapeCSV(o.address.city),
790
+ escapeCSV(o.address.state),
791
+ o.address.zip,
792
+ o.address.country,
793
+ `"${itemsJson}"`,
794
+ o.total_value.toString(),
795
+ o.currency,
796
+ o.status,
797
+ o.created_at,
798
+ ].join(",");
799
+ });
800
+
801
+ return [csvHeader, ...csvRows].join("\n");
802
+ }
803
+
804
+ function escapeCSV(value: string): string {
805
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
806
+ return `"${value.replace(/"/g, '""')}"`;
807
+ }
808
+ return value;
809
+ }
810
+
811
+ // ─── Delivery Timeline Analytics ────────────────────────────────────────────
812
+
813
+ export interface DeliveryStats {
814
+ carrier: string;
815
+ service: string;
816
+ total_shipments: number;
817
+ delivered_count: number;
818
+ avg_delivery_days: number;
819
+ on_time_pct: number;
820
+ late_pct: number;
821
+ }
822
+
823
+ export function getDeliveryStats(carrier?: string): DeliveryStats[] {
824
+ const db = getDatabase();
825
+ const conditions: string[] = ["delivered_at IS NOT NULL", "shipped_at IS NOT NULL"];
826
+ const params: unknown[] = [];
827
+
828
+ if (carrier) {
829
+ conditions.push("carrier = ?");
830
+ params.push(carrier);
831
+ }
832
+
833
+ const sql = `
834
+ SELECT
835
+ carrier,
836
+ service,
837
+ COUNT(*) as total_shipments,
838
+ SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered_count,
839
+ AVG(julianday(delivered_at) - julianday(shipped_at)) as avg_delivery_days,
840
+ SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at <= estimated_delivery THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as on_time_pct,
841
+ SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at > estimated_delivery THEN 1 ELSE 0 END) * 100.0 / COUNT(*) as late_pct
842
+ FROM shipments
843
+ WHERE ${conditions.join(" AND ")}
844
+ GROUP BY carrier, service
845
+ ORDER BY carrier, service
846
+ `;
847
+
848
+ return db.prepare(sql).all(...params) as DeliveryStats[];
849
+ }
850
+
851
+ // ─── Late Delivery Alerts ───────────────────────────────────────────────────
852
+
853
+ export interface OverdueShipment {
854
+ shipment_id: string;
855
+ order_id: string;
856
+ carrier: string;
857
+ service: string;
858
+ tracking_number: string | null;
859
+ estimated_delivery: string;
860
+ days_overdue: number;
861
+ status: string;
862
+ }
863
+
864
+ export function listOverdueShipments(graceDays: number = 0): OverdueShipment[] {
865
+ const db = getDatabase();
866
+
867
+ const sql = `
868
+ SELECT
869
+ id as shipment_id,
870
+ order_id,
871
+ carrier,
872
+ service,
873
+ tracking_number,
874
+ estimated_delivery,
875
+ CAST(julianday('now') - julianday(estimated_delivery) - ? AS INTEGER) as days_overdue,
876
+ status
877
+ FROM shipments
878
+ WHERE estimated_delivery IS NOT NULL
879
+ AND status != 'delivered'
880
+ AND julianday('now') > julianday(estimated_delivery) + ?
881
+ ORDER BY days_overdue DESC
882
+ `;
883
+
884
+ return db.prepare(sql).all(graceDays, graceDays) as OverdueShipment[];
885
+ }
886
+
887
+ // ─── Customer History ───────────────────────────────────────────────────────
888
+
889
+ export interface CustomerHistory {
890
+ customer_email: string;
891
+ orders: Order[];
892
+ shipments: Shipment[];
893
+ returns: Return[];
894
+ }
895
+
896
+ export function getCustomerHistory(email: string): CustomerHistory {
897
+ const db = getDatabase();
898
+
899
+ const orderRows = db.prepare(
900
+ "SELECT * FROM orders WHERE customer_email = ? ORDER BY created_at DESC"
901
+ ).all(email) as OrderRow[];
902
+ const orders = orderRows.map(rowToOrder);
903
+
904
+ const orderIds = orders.map((o) => o.id);
905
+ let shipments: Shipment[] = [];
906
+ let returns: Return[] = [];
907
+
908
+ if (orderIds.length > 0) {
909
+ const placeholders = orderIds.map(() => "?").join(",");
910
+
911
+ const shipmentRows = db.prepare(
912
+ `SELECT * FROM shipments WHERE order_id IN (${placeholders}) ORDER BY created_at DESC`
913
+ ).all(...orderIds) as ShipmentRow[];
914
+ shipments = shipmentRows.map(rowToShipment);
915
+
916
+ const returnRows = db.prepare(
917
+ `SELECT * FROM returns WHERE order_id IN (${placeholders}) ORDER BY created_at DESC`
918
+ ).all(...orderIds) as ReturnRow[];
919
+ returns = returnRows.map(rowToReturn);
920
+ }
921
+
922
+ return { customer_email: email, orders, shipments, returns };
923
+ }
924
+
925
+ // ─── Carrier Performance ────────────────────────────────────────────────────
926
+
927
+ export interface CarrierPerformance {
928
+ carrier: string;
929
+ total_shipments: number;
930
+ delivered_count: number;
931
+ on_time_pct: number;
932
+ avg_cost: number;
933
+ avg_delivery_days: number;
934
+ }
935
+
936
+ export function getCarrierPerformance(): CarrierPerformance[] {
937
+ const db = getDatabase();
938
+
939
+ const sql = `
940
+ SELECT
941
+ carrier,
942
+ COUNT(*) as total_shipments,
943
+ SUM(CASE WHEN status = 'delivered' THEN 1 ELSE 0 END) as delivered_count,
944
+ COALESCE(
945
+ SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at IS NOT NULL AND delivered_at <= estimated_delivery THEN 1 ELSE 0 END) * 100.0
946
+ / NULLIF(SUM(CASE WHEN estimated_delivery IS NOT NULL AND delivered_at IS NOT NULL THEN 1 ELSE 0 END), 0),
947
+ 0
948
+ ) as on_time_pct,
949
+ COALESCE(AVG(cost), 0) as avg_cost,
950
+ COALESCE(
951
+ AVG(CASE WHEN delivered_at IS NOT NULL AND shipped_at IS NOT NULL THEN julianday(delivered_at) - julianday(shipped_at) END),
952
+ 0
953
+ ) as avg_delivery_days
954
+ FROM shipments
955
+ GROUP BY carrier
956
+ ORDER BY on_time_pct DESC
957
+ `;
958
+
959
+ return db.prepare(sql).all() as CarrierPerformance[];
960
+ }
961
+
962
+ // ─── Cost Optimizer ─────────────────────────────────────────────────────────
963
+
964
+ export interface CostRecommendation {
965
+ carrier: string;
966
+ service: string;
967
+ avg_cost: number;
968
+ avg_delivery_days: number;
969
+ shipment_count: number;
970
+ }
971
+
972
+ export function optimizeCost(
973
+ weight: number,
974
+ _fromZip?: string,
975
+ _toZip?: string
976
+ ): CostRecommendation[] {
977
+ const db = getDatabase();
978
+
979
+ // Find historical shipments with similar weight (within 50% range)
980
+ const minWeight = weight * 0.5;
981
+ const maxWeight = weight * 1.5;
982
+
983
+ const sql = `
984
+ SELECT
985
+ carrier,
986
+ service,
987
+ AVG(cost) as avg_cost,
988
+ AVG(CASE WHEN delivered_at IS NOT NULL AND shipped_at IS NOT NULL THEN julianday(delivered_at) - julianday(shipped_at) END) as avg_delivery_days,
989
+ COUNT(*) as shipment_count
990
+ FROM shipments
991
+ WHERE cost IS NOT NULL
992
+ AND weight IS NOT NULL
993
+ AND weight BETWEEN ? AND ?
994
+ GROUP BY carrier, service
995
+ HAVING shipment_count >= 1
996
+ ORDER BY avg_cost ASC
997
+ `;
998
+
999
+ return db.prepare(sql).all(minWeight, maxWeight) as CostRecommendation[];
1000
+ }
1001
+
1002
+ // ─── Order Timeline ─────────────────────────────────────────────────────────
1003
+
1004
+ export interface TimelineEvent {
1005
+ timestamp: string;
1006
+ type: "order_created" | "order_updated" | "shipment_created" | "shipment_updated" | "return_created" | "return_updated";
1007
+ entity_id: string;
1008
+ details: string;
1009
+ }
1010
+
1011
+ export function getOrderTimeline(orderId: string): TimelineEvent[] {
1012
+ const db = getDatabase();
1013
+ const events: TimelineEvent[] = [];
1014
+
1015
+ // Order creation
1016
+ const order = db.prepare("SELECT * FROM orders WHERE id = ?").get(orderId) as OrderRow | null;
1017
+ if (!order) return [];
1018
+
1019
+ events.push({
1020
+ timestamp: order.created_at,
1021
+ type: "order_created",
1022
+ entity_id: order.id,
1023
+ details: `Order created for ${order.customer_name} — $${order.total_value} ${order.currency} [${order.status}]`,
1024
+ });
1025
+
1026
+ if (order.updated_at !== order.created_at) {
1027
+ events.push({
1028
+ timestamp: order.updated_at,
1029
+ type: "order_updated",
1030
+ entity_id: order.id,
1031
+ details: `Order updated — status: ${order.status}`,
1032
+ });
1033
+ }
1034
+
1035
+ // Shipments
1036
+ const shipmentRows = db.prepare(
1037
+ "SELECT * FROM shipments WHERE order_id = ? ORDER BY created_at ASC"
1038
+ ).all(orderId) as ShipmentRow[];
1039
+
1040
+ for (const s of shipmentRows) {
1041
+ events.push({
1042
+ timestamp: s.created_at,
1043
+ type: "shipment_created",
1044
+ entity_id: s.id,
1045
+ details: `Shipment created via ${s.carrier}/${s.service}${s.tracking_number ? ` (${s.tracking_number})` : ""} [${s.status}]`,
1046
+ });
1047
+
1048
+ if (s.shipped_at) {
1049
+ events.push({
1050
+ timestamp: s.shipped_at,
1051
+ type: "shipment_updated",
1052
+ entity_id: s.id,
1053
+ details: `Shipment shipped via ${s.carrier}`,
1054
+ });
1055
+ }
1056
+ if (s.delivered_at) {
1057
+ events.push({
1058
+ timestamp: s.delivered_at,
1059
+ type: "shipment_updated",
1060
+ entity_id: s.id,
1061
+ details: `Shipment delivered via ${s.carrier}`,
1062
+ });
1063
+ }
1064
+ }
1065
+
1066
+ // Returns
1067
+ const returnRows = db.prepare(
1068
+ "SELECT * FROM returns WHERE order_id = ? ORDER BY created_at ASC"
1069
+ ).all(orderId) as ReturnRow[];
1070
+
1071
+ for (const r of returnRows) {
1072
+ events.push({
1073
+ timestamp: r.created_at,
1074
+ type: "return_created",
1075
+ entity_id: r.id,
1076
+ details: `Return requested${r.reason ? `: ${r.reason}` : ""}${r.rma_code ? ` (${r.rma_code})` : ""} [${r.status}]`,
1077
+ });
1078
+
1079
+ if (r.updated_at !== r.created_at) {
1080
+ events.push({
1081
+ timestamp: r.updated_at,
1082
+ type: "return_updated",
1083
+ entity_id: r.id,
1084
+ details: `Return updated — status: ${r.status}`,
1085
+ });
1086
+ }
1087
+ }
1088
+
1089
+ // Sort by timestamp
1090
+ events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
1091
+
1092
+ return events;
1093
+ }
@@ -11,6 +11,9 @@ import {
11
11
  deleteOrder,
12
12
  searchOrders,
13
13
  listByStatus,
14
+ bulkImportOrders,
15
+ exportOrders,
16
+ getOrderTimeline,
14
17
  } from "../db/shipping.js";
15
18
  import {
16
19
  createShipment,
@@ -28,6 +31,11 @@ import {
28
31
  import {
29
32
  getShippingStats,
30
33
  getCostsByCarrier,
34
+ getDeliveryStats,
35
+ listOverdueShipments,
36
+ getCustomerHistory,
37
+ getCarrierPerformance,
38
+ optimizeCost,
31
39
  } from "../db/shipping.js";
32
40
 
33
41
  const server = new McpServer({
@@ -275,10 +283,11 @@ server.registerTool(
275
283
  "create_return",
276
284
  {
277
285
  title: "Create Return",
278
- description: "Create a return request for an order.",
286
+ description: "Create a return request for an order. Set auto_rma to true to generate a unique RMA code.",
279
287
  inputSchema: {
280
288
  order_id: z.string(),
281
289
  reason: z.string().optional(),
290
+ auto_rma: z.boolean().optional(),
282
291
  },
283
292
  },
284
293
  async (params) => {
@@ -372,6 +381,145 @@ server.registerTool(
372
381
  }
373
382
  );
374
383
 
384
+ // ─── Bulk Import/Export ──────────────────────────────────────────────────────
385
+
386
+ server.registerTool(
387
+ "bulk_import_orders",
388
+ {
389
+ title: "Bulk Import Orders",
390
+ description: "Import orders from CSV data. Columns: customer_name,customer_email,street,city,state,zip,country,items_json,total_value.",
391
+ inputSchema: {
392
+ csv_data: z.string().describe("CSV data with header row"),
393
+ },
394
+ },
395
+ async ({ csv_data }) => {
396
+ const result = bulkImportOrders(csv_data);
397
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
398
+ }
399
+ );
400
+
401
+ server.registerTool(
402
+ "export_orders",
403
+ {
404
+ title: "Export Orders",
405
+ description: "Export orders in CSV or JSON format, optionally filtered by date range.",
406
+ inputSchema: {
407
+ format: z.enum(["csv", "json"]).describe("Output format"),
408
+ date_from: z.string().optional().describe("Filter from date (YYYY-MM-DD)"),
409
+ date_to: z.string().optional().describe("Filter to date (YYYY-MM-DD)"),
410
+ },
411
+ },
412
+ async ({ format, date_from, date_to }) => {
413
+ const output = exportOrders(format, date_from, date_to);
414
+ return { content: [{ type: "text", text: output }] };
415
+ }
416
+ );
417
+
418
+ // ─── Delivery Timeline Analytics ────────────────────────────────────────────
419
+
420
+ server.registerTool(
421
+ "delivery_timeline_stats",
422
+ {
423
+ title: "Delivery Timeline Stats",
424
+ description: "Get delivery time analytics (avg days, on-time %, late %) per carrier and service level.",
425
+ inputSchema: {
426
+ carrier: z.string().optional().describe("Filter by carrier"),
427
+ },
428
+ },
429
+ async ({ carrier }) => {
430
+ const stats = getDeliveryStats(carrier);
431
+ return { content: [{ type: "text", text: JSON.stringify(stats, null, 2) }] };
432
+ }
433
+ );
434
+
435
+ // ─── Late Delivery Alerts ───────────────────────────────────────────────────
436
+
437
+ server.registerTool(
438
+ "list_overdue_shipments",
439
+ {
440
+ title: "List Overdue Shipments",
441
+ description: "Find shipments past their estimated delivery date + grace period that are not yet delivered.",
442
+ inputSchema: {
443
+ grace_days: z.number().optional().describe("Grace days beyond estimated delivery (default 0)"),
444
+ },
445
+ },
446
+ async ({ grace_days }) => {
447
+ const overdue = listOverdueShipments(grace_days ?? 0);
448
+ return { content: [{ type: "text", text: JSON.stringify({ overdue, count: overdue.length }, null, 2) }] };
449
+ }
450
+ );
451
+
452
+ // ─── Customer History ───────────────────────────────────────────────────────
453
+
454
+ server.registerTool(
455
+ "customer_shipping_history",
456
+ {
457
+ title: "Customer Shipping History",
458
+ description: "Get all orders, shipments, and returns for a customer by email.",
459
+ inputSchema: {
460
+ email: z.string().describe("Customer email address"),
461
+ },
462
+ },
463
+ async ({ email }) => {
464
+ const history = getCustomerHistory(email);
465
+ return { content: [{ type: "text", text: JSON.stringify(history, null, 2) }] };
466
+ }
467
+ );
468
+
469
+ // ─── Carrier Performance ────────────────────────────────────────────────────
470
+
471
+ server.registerTool(
472
+ "carrier_performance",
473
+ {
474
+ title: "Carrier Performance",
475
+ description: "Rank carriers by on-time delivery %, average cost, and average delivery days.",
476
+ inputSchema: {},
477
+ },
478
+ async () => {
479
+ const perf = getCarrierPerformance();
480
+ return { content: [{ type: "text", text: JSON.stringify(perf, null, 2) }] };
481
+ }
482
+ );
483
+
484
+ // ─── Cost Optimizer ─────────────────────────────────────────────────────────
485
+
486
+ server.registerTool(
487
+ "optimize_shipping_cost",
488
+ {
489
+ title: "Optimize Shipping Cost",
490
+ description: "Recommend cheapest carrier/service based on historical cost data for a given package weight.",
491
+ inputSchema: {
492
+ weight: z.number().describe("Package weight in kg"),
493
+ from_zip: z.string().optional().describe("Origin zip code"),
494
+ to_zip: z.string().optional().describe("Destination zip code"),
495
+ },
496
+ },
497
+ async ({ weight, from_zip, to_zip }) => {
498
+ const recommendations = optimizeCost(weight, from_zip, to_zip);
499
+ return { content: [{ type: "text", text: JSON.stringify(recommendations, null, 2) }] };
500
+ }
501
+ );
502
+
503
+ // ─── Order Timeline ─────────────────────────────────────────────────────────
504
+
505
+ server.registerTool(
506
+ "order_timeline",
507
+ {
508
+ title: "Order Timeline",
509
+ description: "Get the full event history for an order, including shipments and returns.",
510
+ inputSchema: {
511
+ order_id: z.string().describe("Order ID"),
512
+ },
513
+ },
514
+ async ({ order_id }) => {
515
+ const timeline = getOrderTimeline(order_id);
516
+ if (timeline.length === 0) {
517
+ return { content: [{ type: "text", text: `No events found for order '${order_id}'.` }], isError: true };
518
+ }
519
+ return { content: [{ type: "text", text: JSON.stringify(timeline, null, 2) }] };
520
+ }
521
+ );
522
+
375
523
  // --- Start ---
376
524
  async function main() {
377
525
  const transport = new StdioServerTransport();