UNPKG

amazon-seller-mcp

Version:

Model Context Protocol (MCP) client for Amazon Selling Partner API

292 lines 10.4 kB
/** * Notification manager for MCP server * * This file implements notification handling for the MCP server */ // Node.js built-ins import { EventEmitter } from 'events'; // Internal imports import { getLogger } from '../utils/logger.js'; /** * Notification types */ export var NotificationType; (function (NotificationType) { NotificationType["INVENTORY_CHANGE"] = "inventory_change"; NotificationType["ORDER_STATUS_CHANGE"] = "order_status_change"; })(NotificationType || (NotificationType = {})); /** * Notification manager class * Handles sending notifications through the MCP server */ export class NotificationManager { /** * MCP server instance */ server; /** * Event emitter for internal events */ _eventEmitter; /** * Whether to debounce notifications */ debounced; /** * Debounce time in milliseconds */ debounceTime; /** * Map of pending notifications */ pendingNotifications; /** * Creates a new notification manager * * @param server MCP server instance * @param options Notification manager options */ constructor(server, options = {}) { this.server = server; this._eventEmitter = new EventEmitter(); this.debounced = options.debounced ?? false; this.debounceTime = options.debounceTime ?? 1000; // Default to 1 second this.pendingNotifications = new Map(); getLogger().info(`Notification manager initialized (debounced: ${this.debounced}, debounceTime: ${this.debounceTime}ms)`); } /** * Sends an inventory change notification * * @param notification Inventory change notification */ sendInventoryChangeNotification(notification) { const fullNotification = { ...notification, type: NotificationType.INVENTORY_CHANGE, timestamp: new Date().toISOString(), }; this.sendNotification(fullNotification).catch((error) => { getLogger().error('Error sending inventory change notification:', { error: error.message, }); }); } /** * Sends an order status change notification * * @param notification Order status change notification */ sendOrderStatusChangeNotification(notification) { const fullNotification = { ...notification, type: NotificationType.ORDER_STATUS_CHANGE, timestamp: new Date().toISOString(), }; this.sendNotification(fullNotification).catch((error) => { getLogger().error('Error sending order status change notification:', { error: error.message, }); }); } /** * Sends a notification through the MCP server * * @param notification Notification to send */ async sendNotification(notification) { if (this.debounced) { this.sendDebouncedNotification(notification); } else { this.sendImmediateNotification(notification).catch((error) => { getLogger().error('Error sending immediate notification:', { error: error.message, }); }); } } /** * Sends a notification immediately * * @param notification Notification to send */ async sendImmediateNotification(notification) { try { // Format notification based on type let title; let description; let content; switch (notification.type) { case NotificationType.INVENTORY_CHANGE: { const invNotification = notification; title = `Inventory Change: SKU ${invNotification.sku}`; description = `Inventory quantity changed from ${invNotification.previousQuantity} to ${invNotification.newQuantity}`; content = JSON.stringify(invNotification, null, 2); break; } case NotificationType.ORDER_STATUS_CHANGE: { const orderNotification = notification; title = `Order Status Change: Order ${orderNotification.orderId}`; description = `Order status changed from ${orderNotification.previousStatus} to ${orderNotification.newStatus}`; content = JSON.stringify(orderNotification, null, 2); break; } default: title = 'Notification'; description = 'A notification was received'; content = JSON.stringify(notification, null, 2); } // Send notification through MCP server logging if supported try { await this.server.server.sendLoggingMessage({ level: 'info', data: { title, description, content, type: notification.type, timestamp: notification.timestamp, }, }); } catch (loggingError) { // If logging is not supported, log as error but continue processing getLogger().error('Error sending notification:', { error: loggingError.message, }); // Also log to fallback getLogger().info(`Notification: ${title} - ${description}`); } // Emit event for internal listeners this._eventEmitter.emit('notification', notification); // Also emit specific event types for integration test compatibility switch (notification.type) { case NotificationType.INVENTORY_CHANGE: this._eventEmitter.emit('inventory-change', notification); break; case NotificationType.ORDER_STATUS_CHANGE: this._eventEmitter.emit('order-status-change', notification); break; } } catch (error) { getLogger().error('Error sending notification:', { error: error.message }); } } /** * Sends a debounced notification * * @param notification Notification to send */ sendDebouncedNotification(notification) { let key; // Create a unique key based on notification type and identifier switch (notification.type) { case NotificationType.INVENTORY_CHANGE: { const invNotification = notification; key = `${notification.type}:${invNotification.sku}:${invNotification.fulfillmentChannel}`; break; } case NotificationType.ORDER_STATUS_CHANGE: { const orderNotification = notification; key = `${notification.type}:${orderNotification.orderId}`; break; } default: key = `${notification.type}:${Date.now()}`; } // Clear existing timeout if any if (this.pendingNotifications.has(key)) { clearTimeout(this.pendingNotifications.get(key).timeout); } // Set new timeout const timeout = setTimeout(() => { if (this.pendingNotifications.has(key)) { const { notification } = this.pendingNotifications.get(key); this.pendingNotifications.delete(key); this.sendImmediateNotification(notification).catch((error) => { getLogger().error('Error sending debounced notification:', { error: error.message, }); }); } }, this.debounceTime); // Store notification and timeout this.pendingNotifications.set(key, { notification, timeout }); } /** * Adds an event listener for notifications * * @param listener Listener function */ onNotification(listener) { this._eventEmitter.on('notification', listener); } /** * Adds an event listener for specific notification types (for integration test compatibility) * * @param eventType Event type to listen for * @param listener Listener function */ addListener(eventType, listener) { this._eventEmitter.on(eventType, listener); } /** * Removes an event listener * * @param listener Listener function to remove */ removeListener(listener) { if (listener) { this._eventEmitter.removeListener('notification', listener); } } /** * Expose the event emitter for direct access (for testing) */ get eventEmitter() { return this._eventEmitter; } /** * Sends a generic notification (for integration test compatibility) * * @param eventType Event type * @param data Notification data */ async sendGenericNotification(eventType, data) { try { // Emit event for listeners this._eventEmitter.emit(eventType, data); // Also send through MCP server if possible try { await this.server.server.sendLoggingMessage({ level: 'info', data: { title: `Notification: ${eventType}`, description: `Event type: ${eventType}`, content: JSON.stringify(data, null, 2), type: eventType, timestamp: new Date().toISOString(), }, }); } catch { // If logging is not supported, just log as fallback getLogger().info(`Notification: ${eventType} - ${JSON.stringify(data)}`); } } catch (error) { getLogger().error('Error sending notification:', { error: error.message }); } } /** * Clears all pending notifications */ clearPendingNotifications() { for (const { timeout } of this.pendingNotifications.values()) { clearTimeout(timeout); } this.pendingNotifications.clear(); } } //# sourceMappingURL=notifications.js.map