Skip to main content
Glama
amittell

firewalla-mcp-server

get_offline_devices

Retrieve a list of offline devices from the Firewalla MCP server, sorted by last seen time, with options to filter by box and limit results for efficient network monitoring.

Instructions

Get all offline devices (convenience wrapper around get_device_status)

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
boxNoFilter devices under a specific Firewalla box
limitNoMaximum number of offline devices to return
sort_by_last_seenNoSort devices by last seen time (default: true)

Implementation Reference

  • Core handler implementation. Fetches device status from Firewalla API, filters for offline devices (online===false), applies buffer fetching (3x limit), sorts by lastSeen, normalizes data, adds geo-enrichment for IPs, and returns standardized response with metadata including total_offline_devices, limit_applied, returned_count.
    export class GetOfflineDevicesHandler extends BaseToolHandler { name = 'get_offline_devices'; description = 'Get all offline devices with last seen timestamps and detailed device information. Requires limit parameter. Data cached for 2 minutes for performance.'; category = 'network' as const; constructor() { super({ enableGeoEnrichment: true, enableFieldNormalization: true, additionalMeta: { data_source: 'devices', entity_type: 'offline_devices', supports_geographic_enrichment: true, supports_field_normalization: true, standardization_version: '2.0.0', }, }); } async execute( args: ToolArgs, firewalla: FirewallaClient ): Promise<ToolResponse> { try { // Parameter validation with standardized limits const limitValidation = ParameterValidator.validateNumber( args?.limit, 'limit', { required: false, defaultValue: 100, ...getLimitValidationConfig(this.name), } ); const sortValidation = ParameterValidator.validateBoolean( args?.sort_by_last_seen, 'sort_by_last_seen', true ); const validationResult = ParameterValidator.combineValidationResults([ limitValidation, sortValidation, ]); if (!validationResult.isValid) { return this.createErrorResponse( 'Parameter validation failed', ErrorType.VALIDATION_ERROR, undefined, validationResult.errors ); } const limit = limitValidation.sanitizedValue! as number; const sortByLastSeen = sortValidation.sanitizedValue ?? true; // Buffer Strategy: Fetch extra devices to account for post-processing filtering // // Problem: When filtering for offline devices, we don't know how many devices // are offline until after fetching. If we only fetch the requested limit, // we might get fewer results than requested after filtering. // // Solution: Use a "buffer multiplier" strategy where we fetch 3x the requested // limit to increase the probability of having enough offline devices after // filtering. This trades some API overhead for more consistent result counts. // // The multiplier of 3 is empirically chosen based on typical online/offline // ratios in network environments (usually 60-80% devices are online). const fetchLimit = Math.min(limit * 3, 1000); // 3x buffer with 1000 cap for API limits const allDevicesResponse = await withToolTimeout( async () => firewalla.getDeviceStatus(undefined, undefined, fetchLimit), this.name ); // Normalize device data for consistency first const deviceResults = SafeAccess.safeArrayAccess( allDevicesResponse.results, (arr: any[]) => arr, [] ) as any[]; const normalizedDevices = batchNormalize(deviceResults, { name: (v: any) => sanitizeFieldValue(v, 'Unknown Device').value, ip: (v: any) => sanitizeFieldValue(v, 'unknown').value, macVendor: (v: any) => sanitizeFieldValue(v, 'unknown').value, network: (v: any) => (v ? normalizeUnknownFields(v) : null), group: (v: any) => (v ? normalizeUnknownFields(v) : null), online: (v: any) => Boolean(v), // Ensure consistent boolean handling }); // Filter to only offline devices with consistent boolean checking let offlineDevices = SafeAccess.safeArrayFilter( normalizedDevices, (device: any) => device.online === false ); // Sort by last seen timestamp if requested if (sortByLastSeen) { offlineDevices = offlineDevices.sort((a, b) => { const aTime = Number(SafeAccess.getNestedValue(a, 'lastSeen', 0)); const bTime = Number(SafeAccess.getNestedValue(b, 'lastSeen', 0)); return bTime - aTime; // Most recent first }); } // Apply the requested limit const limitedOfflineDevices = offlineDevices.slice(0, limit); const responseStartTime = Date.now(); // Process device data const deviceData = SafeAccess.safeArrayMap( limitedOfflineDevices, (device: any) => ({ id: SafeAccess.getNestedValue(device, 'id', 'unknown'), gid: SafeAccess.getNestedValue(device, 'gid', 'unknown'), name: device.name, // Already normalized ip: device.ip, // Already normalized macVendor: device.macVendor, // Already normalized online: device.online, // Already normalized to false for offline devices lastSeen: SafeAccess.getNestedValue(device, 'lastSeen', 0), lastSeenFormatted: safeUnixToISOString( SafeAccess.getNestedValue(device, 'lastSeen', 0) as number, 'Never' ), ipReserved: SafeAccess.getNestedValue(device, 'ipReserved', false), network: device.network, // Already normalized group: device.group, // Already normalized totalDownload: sanitizeByteCount( SafeAccess.getNestedValue(device, 'totalDownload', 0) ), totalUpload: sanitizeByteCount( SafeAccess.getNestedValue(device, 'totalUpload', 0) ), }) ); // Apply geographic enrichment for IP addresses const enrichedDeviceData = await this.enrichGeoIfNeeded(deviceData, [ 'ip', ]); const unifiedResponseData = { total_offline_devices: offlineDevices.length, limit_applied: limit, returned_count: limitedOfflineDevices.length, devices: enrichedDeviceData, }; const executionTime = Date.now() - responseStartTime; return this.createUnifiedResponse(unifiedResponseData, { executionTimeMs: executionTime, }); } catch (error: unknown) { // Handle timeout errors specifically if (error instanceof TimeoutError) { return createTimeoutErrorResponse( this.name, error.duration, 10000 // Default timeout from timeout-manager ); } const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred'; return this.createErrorResponse( `Failed to get offline devices: ${errorMessage}`, ErrorType.API_ERROR, { originalError: errorMessage } ); } } }
  • Registers the GetOfflineDevicesHandler instance in the central ToolRegistry during initialization of convenience wrappers.
    this.register(new GetOfflineDevicesHandler()); // wrapper around get_device_status
  • MCP protocol input schema definition for the tool, including parameters limit (1-500, default 100), sort_by_last_seen (boolean, default true), and optional box filter.
    { name: 'get_offline_devices', description: 'Get all offline devices (convenience wrapper around get_device_status)', inputSchema: { type: 'object', properties: { limit: { type: 'number', description: 'Maximum number of offline devices to return', minimum: 1, maximum: 500, default: 100, }, sort_by_last_seen: { type: 'boolean', description: 'Sort devices by last seen time (default: true)', default: true, }, box: { type: 'string', description: 'Filter devices under a specific Firewalla box', }, }, required: [], }, },
  • Import statement for GetOfflineDevicesHandler from network handlers module.
    GetFlowDataHandler, GetBandwidthUsageHandler, GetOfflineDevicesHandler, } from './handlers/network.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