Skip to main content
Glama
amittell

firewalla-mcp-server

get_device_status

Check online or offline status of devices on your Firewalla network to monitor connectivity and identify disconnected devices.

Instructions

Check online/offline status of devices on Firewalla network

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
limitYesMaximum number of devices to return (required)
boxNoGet devices under a specific Firewalla box (requires box ID)
groupNoGet devices under a specific box group (requires group ID)

Implementation Reference

  • The GetDeviceStatusHandler class provides the core execution logic for the 'get_device_status' tool. It validates parameters, calls the Firewalla API via firewalla.getDeviceStatus(), processes the response with data normalization, timestamp handling, geographic enrichment, and returns a standardized response.
    export class GetDeviceStatusHandler extends BaseToolHandler {
      name = 'get_device_status';
      description =
        'Check online/offline status of all network devices with detailed information including MAC addresses, IP addresses, device types, and last seen timestamps. Requires limit parameter. Data is cached for 2 minutes for performance.';
      category = 'device' as const;
    
      constructor() {
        super({
          enableGeoEnrichment: true,
          enableFieldNormalization: true,
          additionalMeta: {
            data_source: 'devices',
            entity_type: 'network_devices',
            supports_geographic_enrichment: true,
            supports_field_normalization: true,
            supports_pagination: true,
            supports_filtering: 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: 200,
              ...getLimitValidationConfig(this.name),
            }
          );
    
          if (!limitValidation.isValid) {
            return createErrorResponse(
              this.name,
              'Parameter validation failed',
              ErrorType.VALIDATION_ERROR,
              undefined,
              limitValidation.errors
            );
          }
    
          const deviceId = args?.device_id;
          const includeOffline = (args?.include_offline as boolean) !== false; // Default to true
          const limit = limitValidation.sanitizedValue! as number;
          const cursor = args?.cursor; // Cursor for pagination
    
          const devicesResponse = await withToolTimeout(
            async () =>
              firewalla.getDeviceStatus(deviceId, includeOffline, limit, cursor),
            this.name
          );
    
          // Validate response structure
          const validationSchema = createValidationSchema('devices');
          const validationResult = validateResponseStructure(
            devicesResponse,
            validationSchema
          );
    
          if (!validationResult.isValid) {
            // Validation warnings logged for debugging
          }
    
          // Normalize device data for consistency
          const deviceResults = SafeAccess.safeArrayAccess(
            devicesResponse.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
          });
    
          // Optimize device counting to avoid dual array iteration
          const deviceCounts = normalizedDevices.reduce(
            (acc: { online: number; offline: number }, d: any) => {
              if (d.online === true) {
                acc.online++;
              } else {
                acc.offline++;
              }
              return acc;
            },
            { online: 0, offline: 0 }
          );
    
          const startTime = Date.now();
    
          // Process device data with timestamps but preserve original IDs
          const processedDevices = deviceResults.map(
            (device: any, index: number) => {
              // Apply timestamp normalization to device data
              const timestampNormalized = normalizeTimestamps(device);
              const finalDevice = timestampNormalized.data;
    
              // Get normalized device for other fields
              const normalizedDevice = normalizedDevices[index] || {};
    
              return {
                id: device.id || device.mac || 'unknown', // Use original ID or MAC
                gid: device.gid || 'unknown', // Use original GID
                name:
                  normalizedDevice.name ||
                  finalDevice.name ||
                  device.name ||
                  'unknown',
                ip: normalizedDevice.ip || finalDevice.ip || device.ip || 'unknown',
                macVendor:
                  normalizedDevice.macVendor ||
                  finalDevice.macVendor ||
                  device.macVendor ||
                  'unknown',
                online:
                  normalizedDevice.online !== undefined
                    ? normalizedDevice.online
                    : finalDevice.online !== undefined
                      ? finalDevice.online
                      : Boolean(device.online),
                lastSeen: unixToISOStringOrNow(
                  SafeAccess.getNestedValue(finalDevice, 'lastSeen', 0) as number
                ),
                ipReserved: SafeAccess.getNestedValue(
                  finalDevice,
                  'ipReserved',
                  false
                ),
                network: finalDevice.network, // Already normalized
                group: finalDevice.group, // Already normalized
                totalDownload: sanitizeByteCount(
                  SafeAccess.getNestedValue(finalDevice, 'totalDownload', 0)
                ),
                totalUpload: sanitizeByteCount(
                  SafeAccess.getNestedValue(finalDevice, 'totalUpload', 0)
                ),
              };
            }
          );
    
          // Apply geographic enrichment for IP addresses
          const enrichedDevices = await this.enrichGeoIfNeeded(processedDevices, [
            'ip',
          ]);
    
          const unifiedResponseData = {
            total_devices: SafeAccess.getNestedValue(
              devicesResponse,
              'total_count',
              0
            ),
            online_devices: (deviceCounts as { online: number; offline: number })
              .online,
            offline_devices: (deviceCounts as { online: number; offline: number })
              .offline,
            page_size: SafeAccess.safeArrayAccess(
              devicesResponse.results,
              arr => arr.length,
              0
            ),
            has_more: SafeAccess.getNestedValue(
              devicesResponse as any,
              'has_more',
              false
            ),
            devices: enrichedDevices,
            next_cursor: SafeAccess.getNestedValue(
              devicesResponse,
              'next_cursor',
              null
            ),
          };
    
          const executionTime = Date.now() - startTime;
          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 createErrorResponse(
            this.name,
            `Failed to get device status: ${errorMessage}`,
            ErrorType.API_ERROR,
            { originalError: errorMessage }
          );
        }
      }
    }
  • Defines the input schema and description for the 'get_device_status' tool in the MCP ListTools response, specifying required 'limit' parameter and optional 'box'/'group' filters.
      name: 'get_device_status',
      description:
        'Check online/offline status of devices on Firewalla network',
      inputSchema: {
        type: 'object',
        properties: {
          limit: {
            type: 'number',
            description: 'Maximum number of devices to return (required)',
            minimum: 1,
            maximum: 1000,
          },
          box: {
            type: 'string',
            description:
              'Get devices under a specific Firewalla box (requires box ID)',
          },
          group: {
            type: 'string',
            description:
              'Get devices under a specific box group (requires group ID)',
          },
        },
        required: ['limit'],
      },
    },
  • Registers the GetDeviceStatusHandler instance in the central ToolRegistry during automatic handler registration in the constructor.
    this.register(new GetDeviceStatusHandler());
  • The setupTools function sets up the MCP CallToolRequest handler that uses the registry to execute tools, including get_device_status.
    const response = await handler.execute(args || {}, firewalla);

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