get_rule_trends
Analyze historical rule creation trends to monitor security policy changes and identify patterns in firewall rule deployments over time.
Instructions
Get historical rule trend data (rules created per day)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| group | No | Get trends for a specific box group |
Implementation Reference
- The primary handler class implementing the get_rule_trends tool logic. Validates 'period' parameter, calls Firewalla API firewalla.getRuleTrends(), processes time-series data, computes summary statistics (avg, max, min active rules, stability score), and returns standardized response.export class GetRuleTrendsHandler extends BaseToolHandler { name = 'get_rule_trends'; description = 'Get historical rule activity trends over time with configurable periods. Optional period parameter. Data cached for 1 hour for performance.'; category = 'analytics' as const; constructor() { super({ enableGeoEnrichment: false, // No IP fields in rule trends enableFieldNormalization: true, additionalMeta: { data_source: 'rule_trends', entity_type: 'historical_rule_data', supports_geographic_enrichment: false, supports_field_normalization: true, standardization_version: '2.0.0', }, }); } async execute( _args: ToolArgs, firewalla: FirewallaClient ): Promise<ToolResponse> { try { const periodValidation = ParameterValidator.validateEnum( _args?.period, 'period', ['1h', '24h', '7d', '30d'], false, '24h' ); if (!periodValidation.isValid) { return this.createErrorResponse( 'Parameter validation failed', ErrorType.VALIDATION_ERROR, undefined, periodValidation.errors ); } const period = periodValidation.sanitizedValue!; const trends = await withToolTimeout( async () => firewalla.getRuleTrends(period as '1h' | '24h' | '7d' | '30d'), this.name ); // Validate trends response structure if (!trends || typeof trends !== 'object') { throw new Error('Invalid trends response: not an object'); } if ( !SafeAccess.getNestedValue(trends, 'results') || !Array.isArray(trends.results) ) { throw new Error('Invalid trends response: results is not an array'); } // Validate each trend item has required properties const validTrends = SafeAccess.safeArrayFilter( trends.results, (trend: any) => trend && typeof SafeAccess.getNestedValue(trend, 'ts') === 'number' && typeof SafeAccess.getNestedValue(trend, 'value') === 'number' ); const startTime = Date.now(); const unifiedResponseData = { period, data_points: validTrends.length, trends: SafeAccess.safeArrayMap(validTrends, (trend: any) => ({ timestamp: SafeAccess.getNestedValue(trend, 'ts', 0), timestamp_iso: unixToISOString( SafeAccess.getNestedValue(trend, 'ts', 0) as number ), active_rule_count: SafeAccess.getNestedValue(trend, 'value', 0), })), summary: { avg_active_rules: validTrends.length > 0 ? Math.round( validTrends.reduce( (sum: number, t: any) => sum + (SafeAccess.getNestedValue(t, 'value', 0) as number), 0 ) / validTrends.length ) : 0, max_active_rules: validTrends.length > 0 ? Math.max( ...validTrends.map( (t: any) => SafeAccess.getNestedValue(t, 'value', 0) as number ) ) : 0, min_active_rules: validTrends.length > 0 ? Math.min( ...validTrends.map( (t: any) => SafeAccess.getNestedValue(t, 'value', 0) as number ) ) : 0, rule_stability: this.calculateRuleStability(validTrends), }, }; const executionTime = Date.now() - startTime; return this.createUnifiedResponse(unifiedResponseData, { executionTimeMs: executionTime, }); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; return this.createErrorResponse( `Failed to get rule trends: ${errorMessage}`, ErrorType.API_ERROR, { period: _args?.period || '24h', troubleshooting: 'Check if Firewalla API is accessible and firewall rules are available', } ); } } private calculateRuleStability( trends: Array<{ ts: number; value: number }> ): number { if (trends.length < 2) { return 100; } const values = trends.map( t => SafeAccess.getNestedValue(t, 'value', 0) as number ); const avgValue = values.reduce((sum, val) => sum + val, 0) / values.length; if (avgValue === 0) { return 100; } const variation = values.reduce((sum, val, i) => { return i > 0 ? sum + Math.abs(val - values[i - 1]) : sum; }, 0) / (values.length - 1); const variationPercent = variation / avgValue; return Math.max(0, Math.min(100, Math.round((1 - variationPercent) * 100))); } }
- src/tools/registry.ts:168-168 (registration)Registers the GetRuleTrendsHandler instance in the central ToolRegistry during automatic handler registration in the constructor.this.register(new GetRuleTrendsHandler());
- src/server.ts:674-687 (schema)Defines the MCP tool schema for get_rule_trends, including inputSchema with optional 'group' parameter, exposed in the ListTools response.name: 'get_rule_trends', description: 'Get historical rule trend data (rules created per day)', inputSchema: { type: 'object', properties: { group: { type: 'string', description: 'Get trends for a specific box group', }, }, required: [], }, },
- src/tools/registry.ts:65-65 (registration)Imports the GetRuleTrendsHandler class from './handlers/analytics.js' for use in registry.GetRuleTrendsHandler,