Skip to main content
Glama
amittell

firewalla-mcp-server

get_active_alarms

Retrieve and filter active security alerts from Firewalla firewall, enabling real-time monitoring of issues like abnormal uploads, VPN errors, or device connectivity. Supports grouping, sorting, and pagination for efficient network management.

Instructions

Retrieve current security alerts and alarms from Firewalla firewall

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cursorNoPagination cursor from previous response
groupByNoGroup alarms by field (e.g., type, box)
limitNoResults per page (optional, default: 200, API maximum: 500)
queryNoSearch query for filtering alarms (default: status:1 for active). Use type:N where N is: 1=Security Activity, 2=Abnormal Upload, 3=Large Bandwidth Usage, 4=Monthly Data Plan, 5=New Device, 6=Device Back Online, 7=Device Offline, 8=Video Activity, 9=Gaming Activity, 10=Porn Activity, 11=VPN Activity, 12=VPN Connection Restored, 13=VPN Connection Error, 14=Open Port, 15=Internet Connectivity Update, 16=Large Upload. Examples: type:8 (video), type:10 (porn), region:US, source_ip:*
sortByNoSort alarms (default: ts:desc)

Implementation Reference

  • GetActiveAlarmsHandler class: core implementation executing tool logic. Handles input validation, calls firewalla.getActiveAlarms API, normalizes data, derives severity levels, applies geo-enrichment, and returns unified paginated response.
    export class GetActiveAlarmsHandler extends BaseToolHandler { name = 'get_active_alarms'; description = 'Retrieve active security alarms with optional severity filtering. Data is cached for 15 seconds for performance. Use force_refresh=true to bypass cache for real-time data.'; category = 'security' as const; constructor() { super({ enableGeoEnrichment: true, enableFieldNormalization: true, additionalMeta: { data_source: 'alarms', entity_type: 'security_alarms', supports_geographic_enrichment: true, supports_field_normalization: true, supports_pagination: true, supports_filtering: true, standardization_version: '2.0.0', }, }); } async execute( args: ToolArgs, firewalla: FirewallaClient ): Promise<ToolResponse> { try { // Parameter validation const queryValidation = ParameterValidator.validateOptionalString( args?.query, 'query' ); const groupByValidation = ParameterValidator.validateOptionalString( args?.groupBy, 'groupBy' ); const sortByValidation = ParameterValidator.validateOptionalString( args?.sortBy, 'sortBy' ); const limitValidation = ParameterValidator.validateNumber( args?.limit, 'limit', { required: false, defaultValue: 200, ...getLimitValidationConfig('get_active_alarms'), } ); const cursorValidation = ParameterValidator.validateOptionalString( args?.cursor, 'cursor' ); const includeTotalValidation = ParameterValidator.validateBoolean( args?.include_total_count, 'include_total_count', false ); const severityValidation = ParameterValidator.validateEnum( args?.severity, 'severity', ['low', 'medium', 'high', 'critical'], false // not required ); const forceRefreshValidation = ParameterValidator.validateBoolean( args?.force_refresh, 'force_refresh', false ); const validationResult = ParameterValidator.combineValidationResults([ queryValidation, groupByValidation, sortByValidation, limitValidation, cursorValidation, includeTotalValidation, severityValidation, forceRefreshValidation, ]); if (!validationResult.isValid) { return this.createErrorResponse( 'Parameter validation failed', ErrorType.VALIDATION_ERROR, undefined, validationResult.errors ); } // Build query string combining provided query and severity filter let sanitizedQuery = queryValidation.sanitizedValue as string | undefined; const severityValue = severityValidation.sanitizedValue as | string | undefined; // Add severity filter to query if provided if (severityValue) { const severityQuery = `severity:${severityValue}`; if (sanitizedQuery) { sanitizedQuery = `(${sanitizedQuery}) AND ${severityQuery}`; } else { sanitizedQuery = severityQuery; } } // Validate cursor format if provided if (cursorValidation.sanitizedValue !== undefined) { const cursorFormatValidation = ParameterValidator.validateCursor( cursorValidation.sanitizedValue, 'cursor' ); if (!cursorFormatValidation.isValid) { return this.createErrorResponse( 'Invalid cursor format', ErrorType.VALIDATION_ERROR, { provided_value: cursorValidation.sanitizedValue, documentation: 'Cursors should be obtained from previous response next_cursor field', }, cursorFormatValidation.errors ); } } // Skip query sanitization that may be over-sanitizing and breaking queries // Just use the query directly - basic validation was already done above const response = await withToolTimeout( async () => firewalla.getActiveAlarms( sanitizedQuery, groupByValidation.sanitizedValue as string | undefined, (sortByValidation.sanitizedValue as string) || 'timestamp:desc', limitValidation.sanitizedValue as number, cursorValidation.sanitizedValue as string | undefined, forceRefreshValidation.sanitizedValue as boolean ), 'get_active_alarms' ); // Calculate total count if requested let totalCount: number = SafeAccess.getNestedValue( response as any, 'count', 0 ) as number; let pagesTraversed = 1; if ( includeTotalValidation.sanitizedValue === true && response.next_cursor ) { // Traverse all pages to get true total count let cursor: string | undefined = response.next_cursor; const pageSize = 100; // Use smaller pages for counting const maxPages = 100; // Safety limit while (cursor && pagesTraversed < maxPages) { const nextPage = await firewalla.getActiveAlarms( sanitizedQuery, undefined, 'timestamp:desc', pageSize, cursor ); const pageCount = SafeAccess.getNestedValue( nextPage as any, 'count', 0 ) as number; totalCount += pageCount; cursor = nextPage.next_cursor; pagesTraversed++; } } // Validate response structure const alarmValidationSchema = createValidationSchema('alarms'); const alarmValidationResult = validateResponseStructure( response, alarmValidationSchema ); // Normalize alarm data for consistency const alarmResults = SafeAccess.safeArrayAccess( response.results, (arr: any[]) => arr, [] ) as any[]; // First normalize other fields, then handle severity derivation separately const normalizedAlarms = batchNormalize(alarmResults, { aid: (v: any) => v, // Preserve alarm ID as-is from API gid: (v: any) => v, // Preserve GID as-is from API ts: (v: any) => v, // Preserve timestamp as-is from API type: (v: any) => sanitizeFieldValue(v, 'unknown').value, status: (v: any) => sanitizeFieldValue(v, 'unknown').value, message: (v: any) => sanitizeFieldValue(v, 'No message available').value, direction: (v: any) => sanitizeFieldValue(v, 'unknown').value, protocol: (v: any) => sanitizeFieldValue(v, 'unknown').value, device: (v: any) => (v ? normalizeUnknownFields(v) : null), remote: (v: any) => (v ? normalizeUnknownFields(v) : null), }); // Handle severity derivation using immutable approach const finalNormalizedAlarms = normalizedAlarms.map((alarm: any) => { const providedSeverity = sanitizeFieldValue(alarm.severity, null).value; const finalSeverity = !providedSeverity || providedSeverity === 'unknown' || providedSeverity === null ? deriveAlarmSeverity(alarm.type) : providedSeverity; return { ...alarm, severity: finalSeverity, }; }); const startTime = Date.now(); // Process alarm data with timestamps but preserve original IDs const processedAlarms = SafeAccess.safeArrayMap( alarmResults, // Use original client response, not normalized (alarm: any, index: number) => { // Apply timestamp normalization const timestampNormalized = normalizeTimestamps(alarm); const finalAlarm = timestampNormalized.data; // Get the corresponding normalized alarm for other fields const normalizedAlarm = finalNormalizedAlarms[index] || {}; // Preserve original alarm ID from client response const originalAid = alarm.aid; // Direct from client, not processed return { aid: originalAid || 'unknown', // Use original from client timestamp: unixToISOStringOrNow(finalAlarm.ts), type: normalizedAlarm.type || finalAlarm.type || 'unknown', status: normalizedAlarm.status || finalAlarm.status || 'unknown', message: normalizedAlarm.message || finalAlarm.message || 'Unknown alarm', direction: normalizedAlarm.direction || finalAlarm.direction || 'unknown', protocol: normalizedAlarm.protocol || finalAlarm.protocol || 'unknown', gid: alarm.gid || 'unknown', // Use original GID too severity: normalizedAlarm.severity || 'medium', // Use normalized severity // Include conditional properties (use normalized if available, fallback to original) ...(normalizedAlarm.device || finalAlarm.device ? { device: normalizedAlarm.device || finalAlarm.device } : {}), ...(normalizedAlarm.remote || finalAlarm.remote ? { remote: normalizedAlarm.remote || finalAlarm.remote } : {}), ...(normalizedAlarm.src || finalAlarm.src ? { src: normalizedAlarm.src || finalAlarm.src } : {}), ...(normalizedAlarm.dst || finalAlarm.dst ? { dst: normalizedAlarm.dst || finalAlarm.dst } : {}), ...(normalizedAlarm.port || finalAlarm.port ? { port: normalizedAlarm.port || finalAlarm.port } : {}), ...(normalizedAlarm.dport || finalAlarm.dport ? { dport: normalizedAlarm.dport || finalAlarm.dport } : {}), }; } ); // Apply geographic enrichment to IP fields in alarm data const enrichedAlarms = await this.enrichGeoIfNeeded(processedAlarms, [ 'src', 'dst', 'device.ip', 'remote.ip', ]); const unifiedResponseData = { count: SafeAccess.getNestedValue(response as any, 'count', 0), alarms: enrichedAlarms, next_cursor: response.next_cursor, total_count: totalCount, pages_traversed: pagesTraversed, has_more: !!response.next_cursor, validation_warnings: alarmValidationResult.warnings && alarmValidationResult.warnings.length > 0 ? alarmValidationResult.warnings : undefined, cache_info: { ttl_seconds: forceRefreshValidation.sanitizedValue ? 0 : 15, from_cache: !forceRefreshValidation.sanitizedValue, last_updated: getCurrentTimestamp(), }, }; const executionTime = Date.now() - startTime; return this.createUnifiedResponse(unifiedResponseData, { executionTimeMs: executionTime, }); } catch (error: unknown) { if (error instanceof TimeoutError) { return createTimeoutErrorResponse( 'get_active_alarms', error.duration, 10000 // default timeout ); } const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return this.createErrorResponse( `Failed to get active alarms: ${errorMessage}`, ErrorType.API_ERROR ); } }
  • Tool registration in ToolRegistry constructor's registerHandlers method, instantiating and registering the GetActiveAlarmsHandler.
    this.register(new GetActiveAlarmsHandler());
  • Input schema definition for get_active_alarms tool in MCP server's listTools response, specifying parameters, types, limits, and descriptions.
    name: 'get_active_alarms', description: 'Retrieve current security alerts and alarms from Firewalla firewall', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for filtering alarms (default: status:1 for active). Use type:N where N is: 1=Security Activity, 2=Abnormal Upload, 3=Large Bandwidth Usage, 4=Monthly Data Plan, 5=New Device, 6=Device Back Online, 7=Device Offline, 8=Video Activity, 9=Gaming Activity, 10=Porn Activity, 11=VPN Activity, 12=VPN Connection Restored, 13=VPN Connection Error, 14=Open Port, 15=Internet Connectivity Update, 16=Large Upload. Examples: type:8 (video), type:10 (porn), region:US, source_ip:*', }, groupBy: { type: 'string', description: 'Group alarms by field (e.g., type, box)', }, sortBy: { type: 'string', description: 'Sort alarms (default: ts:desc)', }, limit: { type: 'number', description: 'Results per page (optional, default: 200, API maximum: 500)', minimum: 1, maximum: 500, default: 200, }, cursor: { type: 'string', description: 'Pagination cursor from previous response', }, }, required: [], },
  • Limit configuration for get_active_alarms tool (max 500 results), used in parameter validation via getLimitValidationConfig.
    get_active_alarms: 500, // API documented maximum
  • Helper function and severity map to derive alarm severity levels from alarm types when not provided by API.
    function deriveAlarmSeverity(alarmType: any): string { if (!alarmType || typeof alarmType !== 'string') { return 'medium'; // Default severity for unknown types } // Normalize alarm type to uppercase and remove special characters const normalizedType = alarmType.toUpperCase().replace(/[^A-Z0-9_]/g, '_'); // Try exact match first if (ALARM_TYPE_SEVERITY_MAP[normalizedType]) { return ALARM_TYPE_SEVERITY_MAP[normalizedType]; } // Try partial matches for common patterns const typeString = normalizedType.toLowerCase(); if ( typeString.includes('malware') || typeString.includes('virus') || typeString.includes('trojan') ) { return 'critical'; } if ( typeString.includes('intrusion') || typeString.includes('attack') || typeString.includes('exploit') ) { return 'high'; } if ( typeString.includes('scan') || typeString.includes('suspicious') || typeString.includes('anomaly') ) { return 'medium'; } if ( typeString.includes('dns') || typeString.includes('http') || typeString.includes('status') ) { return 'low'; } // Default to medium severity for unrecognized types return 'medium';

Latest Blog Posts

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/amittell/firewalla-mcp-server'

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