Skip to main content
Glama
amittell

firewalla-mcp-server

search_alarms

Query and filter network alarms by type, status, IP, region, or device using Firewalla syntax. Group, sort, and paginate results for efficient alarm management and monitoring on the firewalla-mcp-server.

Instructions

Search alarms using full-text or field filters. Alarm types: 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.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cursorNoPagination cursor from previous response
groupByNoGroup alarms by specified fields (comma-separated)
limitNoMaximum results (optional, default: 200, API maximum: 500)
queryNoSearch query using Firewalla syntax. Supported fields: type:1-16 (see alarm types above), resolved:true/false, status:1/2 (active/archived), source_ip:192.168.*, region:US (country code), gid:box_id, device.name:*, message:"text search". Examples: "type:8 AND region:US" (video from US), "type:10 AND status:1" (active porn alerts), "source_ip:192.168.* AND NOT resolved:true"
sortByNoSort alarms (default: ts:desc)

Implementation Reference

  • The primary handler class SearchAlarmsHandler that executes the search_alarms tool logic. Handles input validation, calls Firewalla search API, processes alarms with device info harmonization, applies geographic enrichment to IPs, and returns standardized response with metadata.
    export class SearchAlarmsHandler extends BaseToolHandler { name = 'search_alarms'; description = `Security alarm searching with powerful filtering and enhanced reliability. Data cached for 15 seconds, use force_refresh=true for real-time security data. Search through security alerts and alarms using flexible query syntax to identify threats and suspicious activities. Features automatic boolean query translation, enhanced schema harmonization with device information, and improved alarm ID resolution for seamless integration with get_specific_alarm and delete_alarm. REQUIRED PARAMETERS: - query: Search query string using alarm field syntax OPTIONAL PARAMETERS: - limit: Maximum number of results to return (default: 200, max: 500) - force_refresh: Bypass cache for real-time data (default: false) - cursor: Pagination cursor from previous response - sort_by: Field to sort results by - aggregate: Enable aggregation statistics QUERY EXAMPLES (with automatic boolean translation): - Boolean status (both syntaxes supported): "resolved:true" OR "resolved=true", "acknowledged:false" OR "acknowledged=false" (automatically converted to backend format) - IP-based searches: "source_ip:192.168.1.100", "destination_ip:10.0.*" - Type filtering: "type:8", "type:9", "type:10" (use numeric alarm types) - Time-based: "timestamp:>=2024-01-01", "last_24_hours:true" - Complex combinations: "type:8 AND source_ip:192.168.* NOT resolved:true" CACHE CONTROL: - Default: 15-second cache for optimal performance - Real-time: Use force_refresh=true for incident response - Cache info included in responses for timing awareness COMMON USE CASES: - Active security alerts: "type:1 AND resolved:false" - Geographic threats: "country:China AND type:2" - Video/Gaming/Porn activity: "type:8 OR type:9 OR type:10" - VPN issues: "type:13" (VPN Connection Error) ERROR RECOVERY: - If no results, try broader time ranges or different type filters - Check field names against the API documentation - Use wildcards (*) for partial matches when exact queries fail See the Error Handling Guide for troubleshooting: /docs/error-handling-guide.md`; category = 'search' as const; constructor() { // Enable full standardization: geographic enrichment and field normalization for security alarms super({ enableGeoEnrichment: true, // Security alarms often contain IP addresses that require geographic enrichment enableFieldNormalization: true, // Ensure consistent snake_case field naming across all responses additionalMeta: { data_source: 'alarms', entity_type: 'security_alarms', supports_geographic_enrichment: true, supports_field_normalization: true, standardization_version: '2.0.0', }, }); } async execute( args: ToolArgs, firewalla: FirewallaClient ): Promise<ToolResponse> { const searchArgs = args as SearchAlarmsArgs; const startTime = Date.now(); try { // Validate common search parameters const validation = validateCommonSearchParameters( searchArgs, this.name, 'alarms' ); if (!validation.isValid) { return validation.response; } // Validate force_refresh parameter if provided const forceRefreshValidation = ParameterValidator.validateBoolean( searchArgs.force_refresh, 'force_refresh', false ); if (!forceRefreshValidation.isValid) { return createErrorResponse( this.name, 'Force refresh parameter validation failed', ErrorType.VALIDATION_ERROR, undefined, forceRefreshValidation.errors ); } const searchTools = createSearchTools(firewalla); const searchParams: SearchParams = { query: searchArgs.query, limit: searchArgs.limit, offset: searchArgs.offset, cursor: searchArgs.cursor, sort_by: searchArgs.sort_by, sort_order: searchArgs.sort_order, group_by: searchArgs.group_by, aggregate: searchArgs.aggregate, time_range: searchArgs.time_range, force_refresh: forceRefreshValidation.sanitizedValue as boolean, }; const result = await withToolTimeout( async () => searchTools.search_alarms(searchParams), this.name ); const executionTime = Date.now() - startTime; // Process alarm data with enhanced standardization and schema harmonization let processedAlarms = SafeAccess.safeArrayMap( (result as any).results, (alarm: Alarm) => { // Try to extract device information from various possible locations const deviceInfo = { id: SafeAccess.getNestedValue( alarm as any, 'device.id', SafeAccess.getNestedValue( alarm as any, 'deviceId', SafeAccess.getNestedValue(alarm as any, 'mac', 'unknown') ) ), name: SafeAccess.getNestedValue( alarm as any, 'device.name', SafeAccess.getNestedValue(alarm as any, 'deviceName', 'unknown') ), ip: SafeAccess.getNestedValue( alarm as any, 'device.ip', SafeAccess.getNestedValue( alarm as any, 'deviceIp', SafeAccess.getNestedValue(alarm as any, 'ip', 'unknown') ) ), mac: SafeAccess.getNestedValue( alarm as any, 'device.mac', SafeAccess.getNestedValue(alarm as any, 'mac', 'unknown') ), }; const rawAid = SafeAccess.getNestedValue(alarm as any, 'aid', null); // Use the actual alarm ID directly, properly handling 0 as a valid ID const finalAid = rawAid !== null && rawAid !== undefined ? String(rawAid) : 'unknown'; return { aid: finalAid, timestamp: unixToISOStringOrNow(alarm.ts), type: SafeAccess.getNestedValue(alarm as any, 'type', 'unknown'), message: SafeAccess.getNestedValue( alarm as any, 'message', 'No message' ), direction: SafeAccess.getNestedValue( alarm as any, 'direction', 'unknown' ), protocol: SafeAccess.getNestedValue( alarm as any, 'protocol', 'unknown' ), status: SafeAccess.getNestedValue( alarm as any, 'status', 'unknown' ), // Enhanced device information (only include if meaningful data found) device: deviceInfo.id !== 'unknown' || deviceInfo.name !== 'unknown' ? deviceInfo : undefined, // Extract IP addresses for potential geographic enrichment source_ip: SafeAccess.getNestedValue( alarm as any, 'remote.ip', SafeAccess.getNestedValue( alarm as any, 'source_ip', SafeAccess.getNestedValue(alarm as any, 'src', 'unknown') ) ), destination_ip: SafeAccess.getNestedValue( alarm as any, 'destination.ip', SafeAccess.getNestedValue( alarm as any, 'destination_ip', SafeAccess.getNestedValue(alarm as any, 'dst', 'unknown') ) ), }; } ); // Apply geographic enrichment pipeline for IP addresses in alarms processedAlarms = await this.enrichGeoIfNeeded(processedAlarms, [ 'source_ip', 'destination_ip', ]); // Create metadata for standardized response const metadata: SearchMetadata = { query: SafeAccess.getNestedValue( result as any, 'query', searchArgs.query || '' ) as string, entityType: 'alarms', executionTime: SafeAccess.getNestedValue( result as any, 'execution_time_ms', executionTime ) as number, cached: false, cursor: (result as any).next_cursor, hasMore: !!(result as any).next_cursor, limit: searchArgs.limit, aggregations: SafeAccess.getNestedValue( result as any, 'aggregations', null ) as Record<string, any> | undefined, }; // Add schema harmonization warning for search vs active alarms const schemaNote = { warning: 'Search endpoint returns limited fields compared to get_active_alarms', recommendation: 'Use get_active_alarms for complete device and alarm information', differences: [ 'Device objects may not be fully populated in search results', "Some severity and status fields may show 'unknown' values", 'Geographic enrichment is applied but original data may be limited', ], }; // Create unified response with standardized metadata const unifiedResponseData = { alarms: processedAlarms, metadata, schema_harmonization: schemaNote, query_info: { original_query: searchArgs.query, applied_filters: { time_range: !!searchArgs.time_range, force_refresh: !!searchArgs.force_refresh, }, }, }; // Return unified response return this.createUnifiedResponse(unifiedResponseData, { executionTimeMs: executionTime, }); } catch (error: unknown) { if (error instanceof TimeoutError) { return createTimeoutErrorResponse(this.name, error.duration, 10000); } const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return createErrorResponse( this.name, `Failed to search alarms: ${errorMessage}`, ErrorType.SEARCH_ERROR ); } } }
  • TypeScript interface defining the input parameters for the search_alarms tool, extending BaseSearchArgs with optional time_range filter.
    export interface SearchAlarmsArgs extends BaseSearchArgs { time_range?: { start?: string; end?: string; }; }
  • ToolRegistry registers the SearchAlarmsHandler instance along with other search tools during initialization.
    this.register(new SearchFlowsHandler()); this.register(new SearchAlarmsHandler()); this.register(new SearchRulesHandler()); // Analytics tools (6 handlers) this.register(new GetBoxesHandler()); this.register(new GetSimpleStatisticsHandler()); this.register(new GetStatisticsByRegionHandler()); this.register(new GetStatisticsByBoxHandler()); this.register(new GetRecentFlowActivityHandler()); this.register(new GetFlowInsightsHandler()); this.register(new GetAlarmTrendsHandler()); this.register(new GetRuleTrendsHandler()); // Convenience Wrappers (5 handlers) this.register(new GetBandwidthUsageHandler()); // wrapper around get_device_status this.register(new GetOfflineDevicesHandler()); // wrapper around get_device_status this.register(new SearchDevicesHandler()); // wrapper with client-side filtering this.register(new SearchTargetListsHandler()); // wrapper with client-side filtering this.register(new GetNetworkRulesSummaryHandler()); // wrapper around get_network_rules }
  • MCP input schema advertised in listTools response for client parameter validation and documentation.
    name: 'search_alarms', description: 'Search alarms using full-text or field filters. Alarm types: 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.', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query using Firewalla syntax. Supported fields: type:1-16 (see alarm types above), resolved:true/false, status:1/2 (active/archived), source_ip:192.168.*, region:US (country code), gid:box_id, device.name:*, message:"text search". Examples: "type:8 AND region:US" (video from US), "type:10 AND status:1" (active porn alerts), "source_ip:192.168.* AND NOT resolved:true"', }, groupBy: { type: 'string', description: 'Group alarms by specified fields (comma-separated)', }, sortBy: { type: 'string', description: 'Sort alarms (default: ts:desc)', }, limit: { type: 'number', description: 'Maximum results (optional, default: 200, API maximum: 500)', minimum: 1, maximum: 500, default: 200, }, cursor: { type: 'string', description: 'Pagination cursor from previous response', }, }, required: [], },
  • Shared validation helper used by all search handlers including search_alarms for parameter sanitization and query syntax checking.
    function validateCommonSearchParameters( args: BaseSearchArgs, toolName: string, entityType: 'flows' | 'alarms' | 'rules' | 'devices' | 'target_lists' ): CommonSearchValidationResult { // Validate optional limit parameter with default const limitValidation = ParameterValidator.validateNumber( args.limit, 'limit', { required: false, defaultValue: 200, ...getLimitValidationConfig(toolName), } ); if (!limitValidation.isValid) { return { isValid: false, response: createErrorResponse( toolName, 'Parameter validation failed', ErrorType.VALIDATION_ERROR, undefined, limitValidation.errors ), }; } // Validate required query parameter const queryValidation = ParameterValidator.validateRequiredString( args.query, 'query' ); if (!queryValidation.isValid) { return { isValid: false, response: createErrorResponse( toolName, 'Query parameter validation failed', ErrorType.VALIDATION_ERROR, undefined, queryValidation.errors ), }; } // Validate query syntax const querySyntaxValidation = validateFirewallaQuerySyntax(args.query); if (!querySyntaxValidation.isValid) { const examples = getExampleQueries(entityType); return { isValid: false, response: createErrorResponse( toolName, 'Invalid query syntax', ErrorType.VALIDATION_ERROR, { query: args.query, syntax_errors: querySyntaxValidation.errors, examples: examples.slice(0, 3), hint: 'Use field:value syntax with logical operators (AND, OR, NOT)', }, querySyntaxValidation.errors ), }; } // Validate field names in the query const fieldValidation = QuerySanitizer.validateQueryFields( args.query, entityType ); if (!fieldValidation.isValid) { return { isValid: false, response: createErrorResponse( toolName, 'Query contains invalid field names', ErrorType.VALIDATION_ERROR, { query: args.query, documentation: entityType === 'alarms' ? 'See /docs/error-handling-guide.md for troubleshooting' : 'See /docs/query-syntax-guide.md for valid field names', }, fieldValidation.errors ), }; } // Validate cursor format if provided if (args.cursor !== undefined) { const cursorValidation = ParameterValidator.validateCursor( args.cursor, 'cursor' ); if (!cursorValidation.isValid) { return { isValid: false, response: createErrorResponse( toolName, 'Invalid cursor format', ErrorType.VALIDATION_ERROR, undefined, cursorValidation.errors ), }; } } // Validate group_by parameter if provided if (args.group_by !== undefined) { const groupByValidation = ParameterValidator.validateEnum( args.group_by, 'group_by', SEARCH_FIELDS[entityType], false ); if (!groupByValidation.isValid) { return { isValid: false, response: createErrorResponse( toolName, 'Invalid group_by field', ErrorType.VALIDATION_ERROR, { group_by: args.group_by, valid_fields: SEARCH_FIELDS[entityType], documentation: 'See /docs/query-syntax-guide.md for valid fields', }, groupByValidation.errors ), }; } } return { isValid: true, limit: args.limit, query: args.query, cursor: args.cursor, groupBy: args.group_by, }; }

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