Skip to main content
Glama
amittell

firewalla-mcp-server

search_alarms

Search and filter network security alarms from Firewalla firewall. Use full-text queries or field filters to monitor security activities, bandwidth usage, device status, and content alerts.

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
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"
groupByNoGroup alarms by specified fields (comma-separated)
sortByNoSort alarms (default: ts:desc)
limitNoMaximum results (optional, default: 200, API maximum: 500)
cursorNoPagination cursor from previous response

Implementation Reference

  • The SearchAlarmsHandler class that implements the core execution logic for the 'search_alarms' tool. Handles input validation, calls the Firewalla search API via searchTools.search_alarms(), processes alarms with geographic enrichment, normalizes fields, and returns standardized responses 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 ); } } }
  • Registration of the SearchAlarmsHandler instance in the ToolRegistry constructor's registerHandlers() method.
    this.register(new SearchAlarmsHandler());
  • Input schema definition for the 'search_alarms' tool provided in the server's ListToolsRequestHandler response.
    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 function used by SearchAlarmsHandler (and other search handlers) for common search parameters like query syntax, limits, and fields.
    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, }; }
  • Import statement for SearchAlarmsHandler from the handlers/search.ts file.
    import { SearchFlowsHandler, SearchAlarmsHandler, SearchRulesHandler, SearchDevicesHandler, SearchTargetListsHandler, } from './handlers/search.js';

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