Skip to main content
Glama

1MCP Server

presetNotificationService.ts7.42 kB
import { EventEmitter } from 'events'; import logger from '@src/logger/logger.js'; /** * Client connection interface for tracking */ export interface ClientConnection { id: string; presetName?: string; sendNotification(method: string, params?: any): Promise<void>; isConnected(): boolean; } /** * PresetNotificationService manages client tracking and notifications * when presets are modified. Sends listChanged notifications to affected clients. */ export class PresetNotificationService extends EventEmitter { private static instance: PresetNotificationService; private clientsByPreset = new Map<string, Set<ClientConnection>>(); private clientsById = new Map<string, ClientConnection>(); private constructor() { super(); } public static getInstance(): PresetNotificationService { if (!PresetNotificationService.instance) { PresetNotificationService.instance = new PresetNotificationService(); } return PresetNotificationService.instance; } /** * Track a client connection with its associated preset */ public trackClient(client: ClientConnection, presetName?: string): void { // Store client by ID for quick lookup this.clientsById.set(client.id, client); if (presetName) { // Track client by preset if (!this.clientsByPreset.has(presetName)) { this.clientsByPreset.set(presetName, new Set()); } const clientSet = this.clientsByPreset.get(presetName)!; clientSet.add(client); // Update client's preset association client.presetName = presetName; logger.debug('Client tracked for preset', { clientId: client.id, presetName, totalClientsForPreset: clientSet.size, }); } else { logger.debug('Client tracked without preset', { clientId: client.id }); } this.emit('client_tracked', { client, presetName }); } /** * Untrack a client connection */ public untrackClient(clientId: string): void { const client = this.clientsById.get(clientId); if (!client) { return; } // Remove from clients by ID this.clientsById.delete(clientId); // Remove from preset tracking if associated if (client.presetName) { const clientSet = this.clientsByPreset.get(client.presetName); if (clientSet) { clientSet.delete(client); // Clean up empty preset sets if (clientSet.size === 0) { this.clientsByPreset.delete(client.presetName); } logger.debug('Client untracked from preset', { clientId, presetName: client.presetName, remainingClientsForPreset: clientSet.size, }); } } this.emit('client_untracked', { clientId, presetName: client.presetName }); } /** * Update a client's preset association */ public updateClientPreset(clientId: string, newPresetName?: string): void { const client = this.clientsById.get(clientId); if (!client) { logger.warn('Attempted to update preset for unknown client', { clientId }); return; } const oldPresetName = client.presetName; // Remove from old preset tracking if (oldPresetName) { const oldClientSet = this.clientsByPreset.get(oldPresetName); if (oldClientSet) { oldClientSet.delete(client); if (oldClientSet.size === 0) { this.clientsByPreset.delete(oldPresetName); } } } // Add to new preset tracking if (newPresetName) { if (!this.clientsByPreset.has(newPresetName)) { this.clientsByPreset.set(newPresetName, new Set()); } this.clientsByPreset.get(newPresetName)!.add(client); } // Update client association client.presetName = newPresetName; logger.debug('Client preset updated', { clientId, oldPresetName, newPresetName, }); this.emit('client_preset_updated', { clientId, oldPresetName, newPresetName }); } /** * Send listChanged notifications to all clients using a specific preset */ public async notifyPresetChange(presetName: string): Promise<void> { const clients = this.clientsByPreset.get(presetName) || new Set(); if (clients.size === 0) { logger.debug('No clients to notify for preset change', { presetName }); return; } logger.info('Sending preset change notifications', { presetName, clientCount: clients.size, }); // Send notifications in parallel const notifications = Array.from(clients).map(async (client) => { if (!client.isConnected()) { logger.debug('Skipping disconnected client', { clientId: client.id, presetName, }); return; } try { // Send all three types of listChanged notifications await Promise.all([ client.sendNotification('notifications/tools/listChanged'), client.sendNotification('notifications/resources/listChanged'), client.sendNotification('notifications/prompts/listChanged'), ]); logger.debug('Preset change notifications sent to client', { clientId: client.id, presetName, }); } catch (error) { logger.error('Failed to send preset change notification to client', { clientId: client.id, presetName, error, }); // If client is no longer reachable, untrack it if (error instanceof Error && (error.message.includes('connection') || error.message.includes('closed'))) { this.untrackClient(client.id); } } }); await Promise.allSettled(notifications); logger.info('Preset change notifications completed', { presetName, clientCount: clients.size, }); this.emit('preset_notifications_sent', { presetName, clientCount: clients.size }); } /** * Get statistics about tracked clients */ public getStats(): { totalClients: number; presetCount: number; clientsByPreset: Record<string, number>; } { const clientsByPreset: Record<string, number> = {}; for (const [presetName, clientSet] of this.clientsByPreset) { clientsByPreset[presetName] = clientSet.size; } return { totalClients: this.clientsById.size, presetCount: this.clientsByPreset.size, clientsByPreset, }; } /** * Clean up disconnected clients */ public async cleanup(): Promise<number> { let removedCount = 0; for (const [clientId, client] of this.clientsById) { if (!client.isConnected()) { this.untrackClient(clientId); removedCount++; } } if (removedCount > 0) { logger.info('Cleaned up disconnected clients', { removedCount }); } return removedCount; } /** * Get clients for a specific preset */ public getClientsForPreset(presetName: string): ClientConnection[] { const clientSet = this.clientsByPreset.get(presetName); return clientSet ? Array.from(clientSet) : []; } /** * Check if a preset has any tracked clients */ public hasClientsForPreset(presetName: string): boolean { const clientSet = this.clientsByPreset.get(presetName); return !!(clientSet && clientSet.size > 0); } /** * Get all tracked preset names */ public getTrackedPresets(): string[] { return Array.from(this.clientsByPreset.keys()); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/1mcp-app/agent'

If you have feedback or need assistance with the MCP directory API, please join our Discord server