UNPKG

@shopana/novaposhta-mcp-server

Version:

MCP Server for Nova Poshta API integration with AI assistants

1,439 lines (1,423 loc) 77.6 kB
#!/usr/bin/env node // src/config.ts function loadConfig(overrides = {}) { const envApiKey = process.env.NOVA_POSHTA_API_KEY || process.env.NP_API_KEY; const apiKey = overrides.apiKey ?? envApiKey; return { apiKey, baseUrl: overrides.baseUrl ?? process.env.NOVA_POSHTA_BASE_URL ?? "https://api.novaposhta.ua/v2.0/json/", logLevel: overrides.logLevel ?? process.env.LOG_LEVEL ?? "info", timeout: overrides.timeout ?? Number.parseInt(process.env.TIMEOUT ?? "30000", 10) }; } // src/server.ts import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, isInitializeRequest } from "@modelcontextprotocol/sdk/types.js"; // node_modules/@shopana/novaposhta-api-client/dist/core/client.js function toHttpTransport(ctx) { return { async request(request) { const { apiKey, ...rest } = request; const finalRequest = { ...rest, ...apiKey ? { apiKey } : {} }; const response = await ctx.transport({ url: ctx.baseUrl, body: finalRequest }); return response.data; } }; } function createClient(ctx) { const self = {}; self.use = function use(service) { if (typeof service.attach === "function") { service.attach(ctx); } const ns = service.namespace; if (!ns || typeof ns !== "string") { throw new Error('Service must define a string "namespace" property'); } if (ns in self) { throw new Error(`Namespace already installed on client: ${ns}`); } self[ns] = service; return self; }; return self; } // node_modules/@shopana/novaposhta-api-client/dist/types/enums.js var NovaPoshtaModel; (function(NovaPoshtaModel2) { NovaPoshtaModel2["TrackingDocument"] = "TrackingDocumentGeneral"; NovaPoshtaModel2["InternetDocument"] = "InternetDocumentGeneral"; NovaPoshtaModel2["Common"] = "CommonGeneral"; NovaPoshtaModel2["Address"] = "AddressGeneral"; NovaPoshtaModel2["Counterparty"] = "CounterpartyGeneral"; NovaPoshtaModel2["ContactPerson"] = "ContactPersonGeneral"; NovaPoshtaModel2["ScanSheet"] = "ScanSheetGeneral"; NovaPoshtaModel2["AdditionalService"] = "AdditionalServiceGeneral"; })(NovaPoshtaModel || (NovaPoshtaModel = {})); var NovaPoshtaMethod; (function(NovaPoshtaMethod2) { NovaPoshtaMethod2["GetStatusDocuments"] = "getStatusDocuments"; NovaPoshtaMethod2["GetDocumentsEWMovement"] = "getDocumentsEWMovement"; NovaPoshtaMethod2["Save"] = "save"; NovaPoshtaMethod2["Update"] = "update"; NovaPoshtaMethod2["Delete"] = "delete"; NovaPoshtaMethod2["GetDocumentPrice"] = "getDocumentPrice"; NovaPoshtaMethod2["GetDocumentDeliveryDate"] = "getDocumentDeliveryDate"; NovaPoshtaMethod2["GetDocumentList"] = "getDocumentList"; NovaPoshtaMethod2["GenerateReport"] = "generateReport"; NovaPoshtaMethod2["GetCargoTypes"] = "getCargoTypes"; NovaPoshtaMethod2["GetBackwardDeliveryCargoTypes"] = "getBackwardDeliveryCargoTypes"; NovaPoshtaMethod2["GetPalletsList"] = "getPalletsList"; NovaPoshtaMethod2["GetTypesOfPayersForRedelivery"] = "getTypesOfPayersForRedelivery"; NovaPoshtaMethod2["GetPackList"] = "getPackList"; NovaPoshtaMethod2["GetTiresWheelsList"] = "getTiresWheelsList"; NovaPoshtaMethod2["GetCargoDescriptionList"] = "getCargoDescriptionList"; NovaPoshtaMethod2["GetMessageCodeText"] = "getMessageCodeText"; NovaPoshtaMethod2["GetServiceTypes"] = "getServiceTypes"; NovaPoshtaMethod2["GetOwnershipFormsList"] = "getOwnershipFormsList"; NovaPoshtaMethod2["GetTimeIntervals"] = "getTimeIntervals"; NovaPoshtaMethod2["GetPickupTimeIntervals"] = "getPickupTimeIntervals"; NovaPoshtaMethod2["GetSettlements"] = "getSettlements"; NovaPoshtaMethod2["GetCities"] = "getCities"; NovaPoshtaMethod2["GetAreas"] = "getAreas"; NovaPoshtaMethod2["GetWarehouses"] = "getWarehouses"; NovaPoshtaMethod2["GetWarehouseTypes"] = "getWarehouseTypes"; NovaPoshtaMethod2["GetStreet"] = "getStreet"; NovaPoshtaMethod2["SearchSettlements"] = "searchSettlements"; NovaPoshtaMethod2["SearchSettlementStreets"] = "searchSettlementStreets"; NovaPoshtaMethod2["GetSettlementAreas"] = "getSettlementAreas"; NovaPoshtaMethod2["GetSettlementCountryRegion"] = "getSettlementCountryRegion"; })(NovaPoshtaMethod || (NovaPoshtaMethod = {})); var PaymentMethod; (function(PaymentMethod2) { PaymentMethod2["Cash"] = "Cash"; PaymentMethod2["NonCash"] = "NonCash"; })(PaymentMethod || (PaymentMethod = {})); var CargoType; (function(CargoType2) { CargoType2["Parcel"] = "Parcel"; CargoType2["Cargo"] = "Cargo"; CargoType2["Documents"] = "Documents"; CargoType2["TiresWheels"] = "TiresWheels"; CargoType2["Pallet"] = "Pallet"; })(CargoType || (CargoType = {})); var ServiceType; (function(ServiceType2) { ServiceType2["DoorsDoors"] = "DoorsDoors"; ServiceType2["DoorsWarehouse"] = "DoorsWarehouse"; ServiceType2["WarehouseWarehouse"] = "WarehouseWarehouse"; ServiceType2["WarehouseDoors"] = "WarehouseDoors"; })(ServiceType || (ServiceType = {})); var PayerType; (function(PayerType2) { PayerType2["Sender"] = "Sender"; PayerType2["Recipient"] = "Recipient"; PayerType2["ThirdPerson"] = "ThirdPerson"; })(PayerType || (PayerType = {})); var DeliveryStatus; (function(DeliveryStatus2) { DeliveryStatus2[DeliveryStatus2["CreatedBySender"] = 1] = "CreatedBySender"; DeliveryStatus2[DeliveryStatus2["Deleted"] = 2] = "Deleted"; DeliveryStatus2[DeliveryStatus2["NotFound"] = 3] = "NotFound"; DeliveryStatus2[DeliveryStatus2["InSenderCityInterregional"] = 4] = "InSenderCityInterregional"; DeliveryStatus2[DeliveryStatus2["InSenderCityLocal"] = 41] = "InSenderCityLocal"; DeliveryStatus2[DeliveryStatus2["InTransitToRecipientCity"] = 5] = "InTransitToRecipientCity"; DeliveryStatus2[DeliveryStatus2["InRecipientCityAwaitingDelivery"] = 6] = "InRecipientCityAwaitingDelivery"; DeliveryStatus2[DeliveryStatus2["ArrivedAtWarehouse"] = 7] = "ArrivedAtWarehouse"; DeliveryStatus2[DeliveryStatus2["ArrivedAtPostomat"] = 8] = "ArrivedAtPostomat"; DeliveryStatus2[DeliveryStatus2["Received"] = 9] = "Received"; DeliveryStatus2[DeliveryStatus2["ReceivedAwaitingMoneyTransfer"] = 10] = "ReceivedAwaitingMoneyTransfer"; DeliveryStatus2[DeliveryStatus2["ReceivedAndMoneyTransferred"] = 11] = "ReceivedAndMoneyTransferred"; DeliveryStatus2[DeliveryStatus2["BeingPacked"] = 12] = "BeingPacked"; DeliveryStatus2[DeliveryStatus2["OnTheWayToRecipient"] = 101] = "OnTheWayToRecipient"; DeliveryStatus2[DeliveryStatus2["RefusedByRecipientReturnCreated"] = 102] = "RefusedByRecipientReturnCreated"; DeliveryStatus2[DeliveryStatus2["RefusedByRecipient"] = 103] = "RefusedByRecipient"; DeliveryStatus2[DeliveryStatus2["AddressChanged"] = 104] = "AddressChanged"; DeliveryStatus2[DeliveryStatus2["StorageTerminated"] = 105] = "StorageTerminated"; DeliveryStatus2[DeliveryStatus2["ReturnWaybillCreated"] = 106] = "ReturnWaybillCreated"; DeliveryStatus2[DeliveryStatus2["FailedDeliveryAttempt"] = 111] = "FailedDeliveryAttempt"; DeliveryStatus2[DeliveryStatus2["DeliveryRescheduledByRecipient"] = 112] = "DeliveryRescheduledByRecipient"; })(DeliveryStatus || (DeliveryStatus = {})); var WarehouseType; (function(WarehouseType2) { WarehouseType2["Branch"] = "Branch"; WarehouseType2["Postomat"] = "Postomat"; WarehouseType2["PickupPoint"] = "PickupPoint"; })(WarehouseType || (WarehouseType = {})); var SettlementType; (function(SettlementType2) { SettlementType2["City"] = "\u043C."; SettlementType2["Town"] = "\u0441\u043C\u0442."; SettlementType2["Village"] = "\u0441."; SettlementType2["UrbanVillage"] = "\u0441\u0449."; })(SettlementType || (SettlementType = {})); var OwnershipForm; (function(OwnershipForm2) { OwnershipForm2["PrivatePerson"] = "PrivatePerson"; OwnershipForm2["Organization"] = "Organization"; })(OwnershipForm || (OwnershipForm = {})); var CounterpartyType; (function(CounterpartyType2) { CounterpartyType2["PrivatePerson"] = "PrivatePerson"; CounterpartyType2["Organization"] = "Organization"; })(CounterpartyType || (CounterpartyType = {})); var ContactPersonType; (function(ContactPersonType2) { ContactPersonType2["Sender"] = "Sender"; ContactPersonType2["Recipient"] = "Recipient"; })(ContactPersonType || (ContactPersonType = {})); var TimeIntervalType; (function(TimeIntervalType2) { TimeIntervalType2["CityDeliveryTimeInterval1"] = "CityDeliveryTimeInterval1"; TimeIntervalType2["CityDeliveryTimeInterval2"] = "CityDeliveryTimeInterval2"; TimeIntervalType2["CityDeliveryTimeInterval3"] = "CityDeliveryTimeInterval3"; TimeIntervalType2["CityDeliveryTimeInterval4"] = "CityDeliveryTimeInterval4"; })(TimeIntervalType || (TimeIntervalType = {})); var PickupTimeInterval; (function(PickupTimeInterval2) { PickupTimeInterval2["CityPickingTimeInterval1"] = "CityPickingTimeInterval1"; PickupTimeInterval2["CityPickingTimeInterval2"] = "CityPickingTimeInterval2"; PickupTimeInterval2["CityPickingTimeInterval3"] = "CityPickingTimeInterval3"; PickupTimeInterval2["CityPickingTimeInterval4"] = "CityPickingTimeInterval4"; })(PickupTimeInterval || (PickupTimeInterval = {})); var BackwardDeliveryType; (function(BackwardDeliveryType2) { BackwardDeliveryType2["Money"] = "Money"; BackwardDeliveryType2["Documents"] = "Documents"; BackwardDeliveryType2["Cargo"] = "Cargo"; })(BackwardDeliveryType || (BackwardDeliveryType = {})); var BackwardDeliverySubtype; (function(BackwardDeliverySubtype2) { BackwardDeliverySubtype2["MoneyTransfer"] = "MoneyTransfer"; BackwardDeliverySubtype2["DocumentsReturn"] = "DocumentsReturn"; BackwardDeliverySubtype2["CargoReturn"] = "CargoReturn"; })(BackwardDeliverySubtype || (BackwardDeliverySubtype = {})); var AdditionalService; (function(AdditionalService2) { AdditionalService2["SaturdayDelivery"] = "SaturdayDelivery"; AdditionalService2["DeliveryByHand"] = "DeliveryByHand"; AdditionalService2["AfterpaymentOnGoodsCost"] = "AfterpaymentOnGoodsCost"; AdditionalService2["LocalExpress"] = "LocalExpress"; AdditionalService2["PreferredDeliveryDate"] = "PreferredDeliveryDate"; AdditionalService2["TimeInterval"] = "TimeInterval"; AdditionalService2["PackingNumber"] = "PackingNumber"; AdditionalService2["InfoRegClientBarcodes"] = "InfoRegClientBarcodes"; AdditionalService2["AccompanyingDocuments"] = "AccompanyingDocuments"; AdditionalService2["AdditionalInformation"] = "AdditionalInformation"; AdditionalService2["NumberOfFloorsLifting"] = "NumberOfFloorsLifting"; AdditionalService2["NumberOfFloorsDescent"] = "NumberOfFloorsDescent"; AdditionalService2["ForwardingCount"] = "ForwardingCount"; AdditionalService2["RedBoxBarcode"] = "RedBoxBarcode"; AdditionalService2["SpecialCargo"] = "SpecialCargo"; })(AdditionalService || (AdditionalService = {})); var DocumentType; (function(DocumentType2) { DocumentType2["InternetDocument"] = "InternetDocument"; DocumentType2["ReturnDocument"] = "ReturnDocument"; DocumentType2["RedirectionDocument"] = "RedirectionDocument"; })(DocumentType || (DocumentType = {})); var Currency; (function(Currency2) { Currency2["UAH"] = "UAH"; Currency2["USD"] = "USD"; Currency2["EUR"] = "EUR"; Currency2["RUB"] = "RUB"; })(Currency || (Currency = {})); var Language; (function(Language2) { Language2["Ukrainian"] = "ua"; Language2["Russian"] = "ru"; })(Language || (Language = {})); var DeliveryDay; (function(DeliveryDay2) { DeliveryDay2["Monday"] = "Delivery1"; DeliveryDay2["Tuesday"] = "Delivery2"; DeliveryDay2["Wednesday"] = "Delivery3"; DeliveryDay2["Thursday"] = "Delivery4"; DeliveryDay2["Friday"] = "Delivery5"; DeliveryDay2["Saturday"] = "Delivery6"; DeliveryDay2["Sunday"] = "Delivery7"; })(DeliveryDay || (DeliveryDay = {})); var PackingType; (function(PackingType2) { PackingType2["Box"] = "Box"; PackingType2["Tube"] = "Tube"; PackingType2["Envelope"] = "Envelope"; PackingType2["Bag"] = "Bag"; })(PackingType || (PackingType = {})); var TireWheelType; (function(TireWheelType2) { TireWheelType2["Tires"] = "Tires"; TireWheelType2["Wheels"] = "Wheels"; })(TireWheelType || (TireWheelType = {})); var StreetType; (function(StreetType2) { StreetType2["Street"] = "\u0432\u0443\u043B."; StreetType2["Avenue"] = "\u043F\u0440\u043E\u0441\u043F."; StreetType2["Boulevard"] = "\u0431\u0443\u043B."; StreetType2["Lane"] = "\u043F\u0440\u043E\u0432."; StreetType2["Square"] = "\u043F\u043B."; StreetType2["Embankment"] = "\u043D\u0430\u0431."; })(StreetType || (StreetType = {})); var NOVA_POSHTA_MODELS = Object.values(NovaPoshtaModel); var NOVA_POSHTA_METHODS = Object.values(NovaPoshtaMethod); var PAYMENT_METHODS = Object.values(PaymentMethod); var CARGO_TYPES = Object.values(CargoType); var SERVICE_TYPES = Object.values(ServiceType); var PAYER_TYPES = Object.values(PayerType); var DELIVERY_STATUSES = Object.values(DeliveryStatus); // node_modules/@shopana/novaposhta-api-client/dist/services/waybillService.js var WaybillService = class { constructor() { this.namespace = "waybill"; } attach(ctx) { this.transport = toHttpTransport(ctx); this.apiKey = ctx.apiKey; } /** * Create a standard waybill */ async create(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.Save, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Create a waybill with additional options and services */ async createWithOptions(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.Save, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Create a postomat waybill (with restrictions) */ async createForPostomat(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.Save, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Update an existing waybill */ async update(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.Update, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Delete waybills */ async delete(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.Delete, methodProperties: { DocumentRefs: request.documentRefs.join(",") } }; return await this.transport.request(apiRequest); } /** * Calculate delivery date */ async getDeliveryDate(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.GetDocumentDeliveryDate, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Calculate delivery price */ async getPrice(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.GetDocumentPrice, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Batch create multiple waybills */ async createBatch(requests) { const results = []; for (const request of requests) { try { const result = await this.create(request); results.push(result); } catch (error) { results.push({ success: false, data: [], errors: [error instanceof Error ? error.message : "Unknown error"], warnings: [], info: [], messageCodes: [], errorCodes: [], warningCodes: [], infoCodes: [] }); } } return results; } /** * Batch delete multiple waybills */ async deleteBatch(documentRefs) { return this.delete({ documentRefs }); } /** * Get estimated delivery cost and date in one request */ async getEstimate(request) { const [price, deliveryDate] = await Promise.all([ this.getPrice(request), this.getDeliveryDate({ serviceType: request.serviceType, citySender: request.citySender, cityRecipient: request.cityRecipient }) ]); return { price, deliveryDate }; } /** * Validate waybill data without creating */ async validateWaybill(request) { try { return true; } catch { return false; } } /** * Check if postomat delivery is available for the request */ canDeliverToPostomat(request) { if (!request.cargoType || !["Parcel", "Documents"].includes(request.cargoType)) { return false; } if (!request.serviceType || !["DoorsWarehouse", "WarehouseWarehouse"].includes(request.serviceType)) { return false; } if (request.cost && request.cost > 1e4) { return false; } return true; } // ============================================================================= // LEGACY COMPATIBILITY METHODS // ============================================================================= /** * Create express waybill (legacy method for compatibility) * @deprecated Use create() method instead */ async createExpressWaybill(request) { return this.create(request); } /** * Create waybill with options (legacy method for compatibility) * @deprecated Use createWithOptions() method instead */ async createWaybillWithOptions(request) { return this.createWithOptions(request); } /** * Create postomat express waybill (legacy method for compatibility) * @deprecated Use createForPostomat() method instead */ async createPoshtomatExpressWaybill(request) { return this.createForPostomat(request); } /** * Update express waybill (legacy method for compatibility) * @deprecated Use update() method instead */ async updateExpressWaybill(request) { return this.update(request); } /** * Delete waybill (legacy method for compatibility) * @deprecated Use delete() method instead */ async deleteWaybill(request) { return this.delete(request); } /** * Get delivery date (legacy method for compatibility) * @deprecated Use getDeliveryDate() method instead */ async getDocumentDeliveryDate(request) { return this.getDeliveryDate(request); } /** * Get delivery price (legacy method for compatibility) * @deprecated Use getPrice() method instead */ async getDeliveryPrice(request) { return this.getPrice(request); } /** * Get document price (legacy method for compatibility) * @deprecated Use getPrice() method instead */ async getDocumentPrice(request) { return this.getPrice(request); } }; // node_modules/@shopana/novaposhta-api-client/dist/services/trackingService.js var TrackingService = class { constructor() { this.namespace = "tracking"; } attach(ctx) { this.transport = toHttpTransport(ctx); this.apiKey = ctx.apiKey; } /** * Track multiple documents */ async trackDocuments(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.TrackingDocument, calledMethod: NovaPoshtaMethod.GetStatusDocuments, methodProperties: { Documents: request.documents.map((doc) => ({ DocumentNumber: doc.documentNumber, Phone: doc.phone })) } }; return await this.transport.request(apiRequest); } /** * Track a single document by number */ async trackDocument(documentNumber, phone) { const response = await this.trackDocuments({ documents: [{ documentNumber, phone }] }); if (response.success && response.data.length > 0) { return response.data[0] || null; } return null; } /** * Get document movement history */ async getDocumentMovement(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.TrackingDocument, calledMethod: NovaPoshtaMethod.GetDocumentsEWMovement, methodProperties: { Documents: request.documents.map((doc) => ({ DocumentNumber: doc.documentNumber, Phone: doc.phone })), ShowDeliveryDetails: request.showDeliveryDetails ? "1" : "0" } }; return await this.transport.request(apiRequest); } /** * Get list of documents for a date range */ async getDocumentList(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.InternetDocument, calledMethod: NovaPoshtaMethod.GetDocumentList, methodProperties: { DateTimeFrom: request.dateTimeFrom, DateTimeTo: request.dateTimeTo, Page: request.page?.toString() || "1", GetFullList: request.getFullList || "0", DateTime: request.dateTime } }; return await this.transport.request(apiRequest); } /** * Track multiple documents and return organized results */ async trackMultiple(documentNumbers) { const documents = documentNumbers.map((num) => ({ documentNumber: num })); const response = await this.trackDocuments({ documents }); const successful = []; const failed = []; if (response.success && response.data) { response.data.forEach((item, index) => { if (item && item.StatusCode !== DeliveryStatus.NotFound) { successful.push(item); } else { failed.push(documentNumbers[index] || "unknown"); } }); } else { failed.push(...documentNumbers); } const statistics = this.calculateStatistics(successful); return { successful, failed, statistics }; } /** * Filter tracking results by criteria */ filterTrackingResults(results, filter) { return results.filter((result) => { if (filter.status && filter.status.length > 0) { if (!filter.status.includes(result.StatusCode)) { return false; } } if (filter.dateFrom || filter.dateTo) { const resultDate = new Date(result.DateCreated); if (filter.dateFrom && resultDate < new Date(filter.dateFrom)) { return false; } if (filter.dateTo && resultDate > new Date(filter.dateTo)) { return false; } } if (filter.citySender && !result.CitySender.includes(filter.citySender)) { return false; } if (filter.cityRecipient && !result.CityRecipient.includes(filter.cityRecipient)) { return false; } return true; }); } /** * Get tracking statistics for a set of results */ calculateStatistics(results) { const stats = { totalTracked: results.length, delivered: 0, inTransit: 0, atWarehouse: 0, failed: 0, unknown: 0 }; results.forEach((result) => { switch (result.StatusCode) { case DeliveryStatus.Received: case DeliveryStatus.ReceivedAwaitingMoneyTransfer: case DeliveryStatus.ReceivedAndMoneyTransferred: stats.delivered++; break; case DeliveryStatus.InTransitToRecipientCity: case DeliveryStatus.OnTheWayToRecipient: case DeliveryStatus.BeingPacked: stats.inTransit++; break; case DeliveryStatus.ArrivedAtWarehouse: case DeliveryStatus.ArrivedAtPostomat: case DeliveryStatus.InRecipientCityAwaitingDelivery: stats.atWarehouse++; break; case DeliveryStatus.RefusedByRecipient: case DeliveryStatus.RefusedByRecipientReturnCreated: case DeliveryStatus.FailedDeliveryAttempt: stats.failed++; break; default: stats.unknown++; break; } }); return stats; } /** * Check if document is delivered */ isDelivered(status) { return [ DeliveryStatus.Received, DeliveryStatus.ReceivedAwaitingMoneyTransfer, DeliveryStatus.ReceivedAndMoneyTransferred ].includes(status.StatusCode); } /** * Check if document is in transit */ isInTransit(status) { return [ DeliveryStatus.InTransitToRecipientCity, DeliveryStatus.OnTheWayToRecipient, DeliveryStatus.BeingPacked ].includes(status.StatusCode); } /** * Check if document is at warehouse/postomat */ isAtWarehouse(status) { return [ DeliveryStatus.ArrivedAtWarehouse, DeliveryStatus.ArrivedAtPostomat, DeliveryStatus.InRecipientCityAwaitingDelivery ].includes(status.StatusCode); } /** * Get human-readable status description */ getStatusDescription(status, language = "ua") { const descriptions = { [DeliveryStatus.CreatedBySender]: { ua: "\u0421\u0442\u0432\u043E\u0440\u0435\u043D\u043E \u0432\u0456\u0434\u043F\u0440\u0430\u0432\u043D\u0438\u043A\u043E\u043C", ru: "\u0421\u043E\u0437\u0434\u0430\u043D\u043E \u043E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u0435\u043B\u0435\u043C", en: "Created by sender" }, [DeliveryStatus.Deleted]: { ua: "\u0412\u0438\u0434\u0430\u043B\u0435\u043D\u043E", ru: "\u0423\u0434\u0430\u043B\u0435\u043D\u043E", en: "Deleted" }, [DeliveryStatus.NotFound]: { ua: "\u041D\u0435 \u0437\u043D\u0430\u0439\u0434\u0435\u043D\u043E", ru: "\u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E", en: "Not found" }, [DeliveryStatus.InTransitToRecipientCity]: { ua: "\u041F\u0440\u044F\u043C\u0443\u0454 \u0434\u043E \u043C\u0456\u0441\u0442\u0430 \u043E\u0434\u0435\u0440\u0436\u0443\u0432\u0430\u0447\u0430", ru: "\u041D\u0430\u043F\u0440\u0430\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0432 \u0433\u043E\u0440\u043E\u0434 \u043F\u043E\u043B\u0443\u0447\u0430\u0442\u0435\u043B\u044F", en: "En route to recipient city" }, [DeliveryStatus.Received]: { ua: "\u041E\u0442\u0440\u0438\u043C\u0430\u043D\u043E", ru: "\u041F\u043E\u043B\u0443\u0447\u0435\u043D\u043E", en: "Delivered" } // Add more status descriptions as needed }; return descriptions[status]?.[language] || `Unknown status: ${status}`; } /** * Monitor documents with periodic updates */ async monitorDocuments(documentNumbers, callback, intervalMs = 3e5) { const monitor = async () => { try { const response = await this.trackMultiple(documentNumbers); callback(response.successful); } catch (error) { console.error("Error monitoring documents:", error); } }; await monitor(); const intervalId = setInterval(monitor, intervalMs); return () => { clearInterval(intervalId); }; } // ============================================================================= // LEGACY COMPATIBILITY METHODS // ============================================================================= /** * Track waybill (legacy method for compatibility) * @deprecated Use trackDocument() or trackDocuments() method instead */ async trackWaybill(request) { return this.trackDocuments({ documents: request.documents.map((doc) => ({ documentNumber: doc.documentNumber, phone: doc.phone })) }); } /** * Get status documents (legacy method for compatibility) * @deprecated Use trackDocuments() method instead */ async getStatusDocuments(request) { return this.trackDocuments(request); } }; // node_modules/@shopana/novaposhta-api-client/dist/services/referenceService.js var ReferenceService = class { constructor() { this.namespace = "reference"; } attach(ctx) { this.transport = toHttpTransport(ctx); this.apiKey = ctx.apiKey; } /** * Get cargo types * @description Retrieves list of available cargo types * @cacheable 24 hours */ async getCargoTypes(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetCargoTypes, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get pallets list * @description Retrieves list of available pallets * @cacheable 24 hours */ async getPalletsList(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetPalletsList, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get pack list * @description Retrieves list of available packaging types * @cacheable 24 hours */ async getPackList(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetPackList, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get tires and wheels list * @description Retrieves list of available tires and wheels types * @cacheable 24 hours */ async getTiresWheelsList(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetTiresWheelsList, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get cargo description list * @description Retrieves list of cargo descriptions with optional search * @cacheable 24 hours */ async getCargoDescriptionList(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetCargoDescriptionList, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get message code text * @description Retrieves list of error codes and their descriptions * @cacheable 24 hours */ async getMessageCodeText(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetMessageCodeText, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get service types * @description Retrieves list of delivery service types * @cacheable 24 hours */ async getServiceTypes(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetServiceTypes, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get ownership forms list * @description Retrieves list of ownership forms * @cacheable 24 hours */ async getOwnershipFormsList(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetOwnershipFormsList, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get time intervals * @description Retrieves available time intervals for delivery * @cacheable 1 hour */ async getTimeIntervals(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetTimeIntervals, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get pickup time intervals * @description Retrieves available time intervals for pickup * @cacheable 1 hour */ async getPickupTimeIntervals(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetPickupTimeIntervals, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get backward delivery cargo types * @description Retrieves list of backward delivery cargo types * @cacheable 24 hours */ async getBackwardDeliveryCargoTypes(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetBackwardDeliveryCargoTypes, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get types of payers for redelivery * @description Retrieves list of payer types for redelivery * @cacheable 24 hours */ async getTypesOfPayersForRedelivery(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Common, calledMethod: NovaPoshtaMethod.GetTypesOfPayersForRedelivery, methodProperties: request }; return await this.transport.request(apiRequest); } }; // node_modules/@shopana/novaposhta-api-client/dist/services/addressService.js var AddressService = class { constructor() { this.namespace = "address"; } attach(ctx) { this.transport = toHttpTransport(ctx); this.apiKey = ctx.apiKey; } /** * Get settlements (areas) * @description Retrieves list of settlement areas * @cacheable 12 hours */ async getSettlements(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.GetSettlementAreas, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get settlement country regions * @description Retrieves list of settlement regions for a specific area * @cacheable 12 hours */ async getSettlementCountryRegion(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.GetSettlementCountryRegion, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get cities * @description Retrieves list of cities with Nova Poshta offices * @cacheable 12 hours */ async getCities(request = {}) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.GetCities, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Get streets * @description Retrieves list of streets in a specific city * @cacheable 12 hours */ async getStreet(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.GetStreet, methodProperties: request }; return await this.transport.request(apiRequest); } /** * Search settlements online * @description Performs online search for settlements * @cacheable 1 hour */ async searchSettlements(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.SearchSettlements, methodProperties: { CityName: request.cityName, Page: request.page.toString(), Limit: request.limit.toString() } }; return await this.transport.request(apiRequest); } /** * Search settlement streets online * @description Performs online search for streets in a settlement * @cacheable 1 hour */ async searchSettlementStreets(request) { const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.SearchSettlementStreets, methodProperties: { StreetName: request.streetName, SettlementRef: request.settlementRef, Limit: request.limit?.toString() } }; return await this.transport.request(apiRequest); } /** * Get warehouses (branches and postomats) * @description Retrieves list of Nova Poshta warehouses filtered by city, settlement, or other criteria * @cacheable 1 hour * @note API returns HTTP 303 Redirect with link to cached file for some cities */ async getWarehouses(request = {}) { const methodProperties = { Ref: request.ref, CityName: request.cityName, CityRef: request.cityRef, SettlementRef: request.settlementRef, WarehouseId: request.warehouseId, FindByString: request.findByString, TypeOfWarehouseRef: request.typeOfWarehouseRef, BicycleParking: request.bicycleParking, PostFinance: request.postFinance, POSTerminal: request.posTerminal, Page: request.page?.toString(), Limit: request.limit?.toString(), Language: request.language }; const cleanProperties = Object.fromEntries(Object.entries(methodProperties).filter(([, value]) => value !== void 0 && value !== "")); const apiRequest = { ...this.apiKey ? { apiKey: this.apiKey } : {}, modelName: NovaPoshtaModel.Address, calledMethod: NovaPoshtaMethod.GetWarehouses, methodProperties: cleanProperties }; return await this.transport.request(apiRequest); } }; // node_modules/@shopana/novaposhta-transport-fetch/dist/index.js import fetchPonyfill from "cross-fetch"; function createFetchHttpTransport(init) { const doFetch = init?.fetchImpl ?? (typeof fetch !== "undefined" ? fetch : fetchPonyfill); const headers = { "Content-Type": "application/json", Accept: "application/json", ...init?.baseHeaders ?? {} }; async function transport({ url, body, signal }) { const res = await doFetch(url, { method: "POST", headers, body: JSON.stringify(body), signal }); const data = await res.json(); return { status: res.status, data }; } return transport; } // src/server.ts import { readFileSync } from "fs"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; import { createServer } from "http"; import { randomUUID } from "crypto"; // src/utils/logger.ts var LEVEL_PRIORITY = { debug: 10, info: 20, warn: 30, error: 40 }; var Logger = class { constructor(level = "info") { this.level = level; } debug(message, extra) { this.log("debug", message, extra); } info(message, extra) { this.log("info", message, extra); } warn(message, extra) { this.log("warn", message, extra); } error(message, extra) { this.log("error", message, extra); } log(level, message, extra) { if (LEVEL_PRIORITY[level] < LEVEL_PRIORITY[this.level]) { return; } const payload = extra !== void 0 ? { message, extra } : { message }; console.error(JSON.stringify({ level, timestamp: (/* @__PURE__ */ new Date()).toISOString(), ...payload })); } }; // src/utils/error-handler.ts function formatError(error) { if (isNovaPoshtaError(error)) { const context = error.context ? ` Context: ${JSON.stringify(error.context)}` : ""; return `Nova Poshta API error (${error.code}): ${error.message}.${context}`; } if (error instanceof Error) { return error.message; } return typeof error === "string" ? error : "Unknown error"; } function toErrorResult(error, prefix = "Failed to execute tool") { return { content: [ { type: "text", text: `${prefix}: ${formatError(error)}` } ], isError: true }; } function isNovaPoshtaError(error) { return Boolean( error && typeof error === "object" && "code" in error && "message" in error ); } // src/utils/validation.ts function isNonEmptyString(value) { return typeof value === "string" && value.trim().length > 0; } function isTrackingNumber(value) { return typeof value === "string" && /^\d{14}$/.test(value.trim()); } function isPhoneNumber(value) { return typeof value === "string" && /^380\d{9}$/.test(value.trim()); } function isDateFormat(value) { if (typeof value !== "string") return false; const trimmed = value.trim(); if (!/^\d{2}\.\d{2}\.\d{4}$/.test(trimmed)) return false; const [day, month, year] = trimmed.split(".").map(Number); if (month < 1 || month > 12) return false; if (day < 1 || day > 31) return false; if (year < 1900 || year > 2100) return false; return true; } function sanitizePhone(phone) { let sanitized = phone.replace(/\D/g, ""); if (phone.startsWith("+")) { sanitized = sanitized; } if (sanitized.startsWith("80")) { sanitized = "3" + sanitized; } if (sanitized.startsWith("0")) { sanitized = "38" + sanitized; } return sanitized; } function assertString(value, field) { if (!isNonEmptyString(value)) { throw new Error(`Field "${field}" must be a non-empty string`); } return value.trim(); } function assertNumber(value, field) { if (typeof value === "string" && value.trim() !== "") { const parsed = Number(value); if (!Number.isNaN(parsed)) { return parsed; } } if (typeof value !== "number" || Number.isNaN(value)) { throw new Error(`Field "${field}" must be a valid number`); } return value; } function assertOptionalString(value, field) { if (value === void 0 || value === null) { return void 0; } return assertString(value, field); } function assertOptionalNumber(value, field) { if (value === void 0 || value === null || value === "") { return void 0; } return assertNumber(value, field); } // src/utils/tool-response.ts function createTextResult(message, structuredContent) { return { content: [ { type: "text", text: message } ], ...structuredContent ? { structuredContent } : {} }; } function formatAsJson(input) { return JSON.stringify(input, null, 2); } // src/tools/address.ts var addressTools = [ { name: "address_search_cities", description: "Find Nova Poshta cities by name or postal index.", inputSchema: { type: "object", properties: { query: { type: "string", description: "Partial city name or postal code." }, page: { type: "number", description: "Page number (default 1)." }, limit: { type: "number", description: "Items per page (max 50)." } }, required: ["query"] } }, { name: "address_search_settlements", description: "Search for settlements (city, town, village) with pagination.", inputSchema: { type: "object", properties: { cityName: { type: "string", description: "Settlement name or postal code." }, page: { type: "number", description: "Page number (default 1)." }, limit: { type: "number", description: "Items per page (1-500)." } }, required: ["cityName"] } }, { name: "address_search_streets", description: "Search for streets inside a settlement.", inputSchema: { type: "object", properties: { settlementRef: { type: "string", description: "Settlement reference ID." }, streetName: { type: "string", description: "Street name or fragment." }, limit: { type: "number", description: "Max items to return (optional)." } }, required: ["settlementRef", "streetName"] } }, { name: "address_get_warehouses", description: "List Nova Poshta warehouses (branches, postomats, pickup points) filtered by city, settlement, type, number, or search string.", inputSchema: { type: "object", properties: { ref: { type: "string", description: "Specific warehouse reference (returns single warehouse)." }, cityName: { type: "string", description: "City name filter." }, cityRef: { type: "string", description: "City reference from getCities." }, settlementRef: { type: "string", description: "Settlement reference from searchSettlements." }, warehouseId: { type: "string", description: 'Warehouse number (e.g., "1" for Branch #1).' }, findByString: { type: "string", description: "Search string for warehouse name, address, or street." }, typeOfWarehouseRef: { type: "string", description: "Filter by warehouse type (Branch, Postomat, Pickup Point)." }, bicycleParking: { type: "string", description: "Filter by bicycle parking availability (1/0)." }, postFinance: { type: "string", description: "Filter by NovaPay cash desk availability (1/0)." }, posTerminal: { type: "string", description: "Filter by POS terminal availability (1/0)." }, page: { type: "number", description: "Page number (default 1)." }, limit: { type: "number", description: "Items per page (default 50)." }, language: { type: "string", description: "Language code (UA, RU, EN)." } }, required: [] } } ]; function getAddressTools() { return addressTools; } async function handleAddressTool(name, args, context) { try { switch (name) { case "address_search_cities": return await handleSearchCities(args, context); case "address_search_settlements": return await handleSearchSettlements(args, context); case "address_search_streets": return await handleSearchStreets(args, context); case "address_get_warehouses": return await handleGetWarehouses(args, context); default: throw new Error(`Unknown address tool: ${name}`); } } catch (error) { return toErrorResult(error, `Address tool "${name}"`); } } async function handleSearchCities(args, context) { const query = assertString(args?.query, "query"); const page = assertOptionalNumber(args?.page, "page") ?? 1; const limit = assertOptionalNumber(args?.limit, "limit") ?? 20; const response = await context.client.address.getCities({ findByString: query, page, limit }); const preview = response.data?.slice(0, 5).map((city) => { const warehouses = city.Warehouses ?? 0; return { description: city.Description, ref: city.Ref, area: city.Area, warehouses: Number(warehouses) }; }); return createTextResult(formatAsJson({ total: response.data?.length ?? 0, preview }), { response }); } async function handleSearchSettlements(args, context) { const cityName = assertString(args?.cityName, "cityName"); const page = assertOptionalNumber(args?.page, "page") ?? 1; const limit = assertOptionalNumber(args?.limit, "limit") ?? 50; const response = await context.client.address.searchSettlements({ cityName, page, limit }); const addresses = response.data?.[0]?.Addresses ?? []; const preview = addresses.slice(0, 5).map((address) => ({ name: address.MainDescription, area: address.Area, region: address.Region, warehouses: address.Warehouses, settlementRef: address.Ref, deliveryCity: address.DeliveryCity })); return createTextResult(formatAsJson({ total: addresses.length, preview }), { response }); } async function handleSearchStreets(args, context) { const settlementRef = assertString(args?.settlementRef, "settlementRef"); const streetName = assertString(args?.streetName, "streetName"); const limit = assertOptionalNumber(args?.limit, "limit"); const response = await context.client.address.searchSettlementStreets({ settlementRef, streetName, limit: limit ?? void 0 }); const addresses = response.data?.[0]?.Addresses ?? []; const preview = addresses.slice(0, 5).map((street) => ({ name: street.SettlementStreetDescription, present: street.Present, location: street.Location })); return createTextResult(formatAsJson({ total: addresses.length, preview }), { response }); } async function handleGetWarehouses(args, context) { const ref = assertOptionalString(args?.ref, "ref"); const cityName = assertOptionalString(args?.cityName, "cityName"); const cityRef = assertOptionalString(args?.cityRef, "cityRef"); const settlementRef = assertOptionalString(args?.settlementRef, "settlementRef"); const warehouseId = assertOptionalString(args?.warehouseId, "warehouseId"); const findByString = assertOptionalString(args?.findByString, "findByString"); const typeOfWarehouseRef = assertOptionalString(args?.typeOfWarehouseRef, "typeOfWarehouseRef"); const bicycleParking = assertOptionalString(args?.bicycleParking, "bicycleParking"); const postFinance = assertOptionalString(args?.postFinance, "postFinance"); const posTerminal = assertOptionalString(args?.posTerminal, "posTerminal"); const language = assertOptionalString(args?.language, "language"); const page = assertOptionalNumber(args?.page, "page"); const limit = assertOptionalNumber(args?.limit, "limit"); if (!ref && !cityRef && !settlementRef) { throw new Error("ref, cityRef, or settlementRef is required to list warehouses"); } const response = await context.client.address.getWarehouses({ ref, cityName, cityRef, settlementRef, warehouseId, findByString,