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';
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure. While it mentions search capabilities and alarm types, it doesn't describe important behavioral aspects like whether this is a read-only operation, what permissions are required, whether results are paginated (though cursor parameter hints at this), rate limits, or what the response format looks like. For a search tool with 5 parameters and no annotation coverage, this is inadequate.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is appropriately concise - a single sentence stating the purpose followed by a comprehensive list of alarm types. Every element serves a purpose: the first sentence defines the tool's function, and the alarm type list provides essential context for the 'type' parameter. However, it could be slightly more structured by separating the alarm types into a more readable format.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (5 parameters, no annotations, no output schema), the description is incomplete. It doesn't explain what the tool returns, how results are structured, or important behavioral constraints. While it provides alarm type mappings which are helpful, it misses critical context about the tool's operation, response format, and usage boundaries compared to sibling alarm tools.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema description coverage is 100%, so all parameters are well-documented in the schema itself. The description adds some value by listing alarm types (1-16) which correspond to the 'type' field in the query parameter, but this is essentially duplicating information that could be in the schema. The description doesn't provide additional parameter semantics beyond what's already in the comprehensive schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose as 'Search alarms using full-text or field filters' and provides a comprehensive list of alarm types. This specifies the verb ('search') and resource ('alarms') with filtering methods. However, it doesn't explicitly differentiate from sibling tools like 'get_active_alarms' or 'get_specific_alarm', which is why it doesn't achieve a perfect score.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. There are multiple sibling tools related to alarms (get_active_alarms, get_specific_alarm, get_alarm_trends), but the description offers no comparison or context about when this search tool is preferable. The only implicit guidance is the mention of filtering capabilities, but this isn't explicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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