amazon-seller-mcp
Version:
Model Context Protocol (MCP) client for Amazon Selling Partner API
292 lines • 10.4 kB
JavaScript
/**
* 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