Skip to main content
Glama
amittell

firewalla-mcp-server

get_offline_devices

Retrieve offline network devices from Firewalla firewall to monitor connectivity status and identify disconnected endpoints.

Instructions

Get all offline devices (convenience wrapper around get_device_status)

Input Schema

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

Implementation Reference

  • The main handler class for the 'get_offline_devices' tool. It fetches device status from Firewalla API, filters offline devices, applies sorting and limiting, normalizes data, enriches with geo info, and standardizes the response.
    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 }
          );
        }
      }
    }
  • The JSON schema definition for the tool's input parameters, registered with the MCP server.
    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: [],
    },
  • Registers the GetOfflineDevicesHandler instance in the central ToolRegistry during initialization.
    this.register(new GetOfflineDevicesHandler()); // wrapper around get_device_status
  • Defines the rate limiting configuration for the tool via getToolLimit function.
    get_offline_devices: STANDARD_LIMITS.OFFLINE_DEVICES,

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