signalk-mcp-server
Version:
MCP server for SignalK marine data integration
520 lines • 20.7 kB
TypeScript
import { EventEmitter } from 'events';
import type { SignalKClientOptions, SignalKValue, SignalKDelta, AISTarget, ActiveAlarm, VesselState, AISTargetsResponse, ActiveAlarmsResponse, ConnectionStatus, AvailablePathsResponse, PathValueResponse } from './types/index.js';
export declare class SignalKClient extends EventEmitter {
hostname: string;
port: number;
useTLS: boolean;
originalUrl: string;
context: string;
connected: boolean;
latestValues: Map<string, SignalKValue>;
availablePaths: Set<string>;
aisTargets: Map<string, AISTarget>;
activeAlarms: Map<string, ActiveAlarm>;
private client;
constructor(options?: SignalKClientOptions);
/**
* Set SignalK connection configuration from environment variables with sensible defaults
*
* Environment Variables:
* - SIGNALK_HOST: Hostname/IP (default: 'localhost')
* - SIGNALK_PORT: Port number (default: 3000)
* - SIGNALK_TLS: Use secure connections - true/false (default: false)
*
* Sets instance properties:
* - hostname: The server hostname/IP (e.g., 'localhost', '192.168.1.100')
* - port: The server port (e.g., 3000, 443, 80)
* - useTLS: Whether to use secure connections (WSS/HTTPS vs WS/HTTP)
*
* @param options - Override options
*/
setSignalKConfig(options?: SignalKClientOptions): void;
/**
* Build WebSocket URL for streaming connections
* @returns WebSocket URL (ws:// or wss://)
*/
buildWebSocketUrl(): string;
/**
* Build HTTP URL for REST API calls
* @returns HTTP URL (http:// or https://)
*/
buildHttpUrl(): string;
/**
* Build SignalK REST API URL for a specific vessel and path
* @param vesselContext - Vessel context (e.g., 'self', 'urn:mrn:imo:mmsi:123456789')
* @param path - SignalK path in dot notation (e.g., 'navigation.position')
* @returns Complete REST API URL
*/
buildRestApiUrl(vesselContext?: string, path?: string): string;
/**
* Sets up WebSocket event handlers for connection, disconnection, errors, and delta messages
*
* Event handlers:
* - 'connect': Sets connected flag and emits 'connected' event
* - 'disconnect': Clears connected flag and emits 'disconnected' event
* - 'error': Logs errors and emits 'error' event
* - 'delta': Processes incoming SignalK delta messages with vessel data updates
*
* @example
* const client = new SignalKClient();
* client.on('connected', () => console.log('Connected to SignalK'));
* client.on('delta', (delta) => console.log('Received data:', delta));
*/
setupEventHandlers(): void;
/**
* Establishes connection to SignalK server (HTTP-only mode)
*
* This method now operates in HTTP-only mode for maximum data freshness.
* WebSocket functionality is preserved but disabled for future streaming capabilities
* when MCP servers support real-time data streams.
*
* Features:
* - Tests HTTP connectivity to SignalK server
* - Sets connected status based on HTTP availability
* - WebSocket code preserved for future streaming implementation
*
* @returns Promise that resolves when HTTP connection is verified
*
* @example
* const client = new SignalKClient({ hostname: 'localhost', port: 3000 });
* try {
* await client.connect();
* console.log('Connected successfully');
* } catch (error) {
* console.error('Connection failed:', error);
* }
*/
connect(): Promise<void>;
/**
* Fetches initial complete vessel state via HTTP API to populate cache immediately
*
* This method is called after WebSocket connection to ensure getVesselState()
* has immediate access to complete vessel data instead of waiting for deltas.
*
* Features:
* - HTTP GET to /signalk/v1/api/vessels/self for complete state
* - Populates latestValues Map with all available paths
* - Preserves current timestamp for each value
* - Updates availablePaths Set automatically
* - Graceful error handling - logs errors but doesn't throw
*
* @returns Promise that resolves when initial state is fetched and cached
*
* @private
*/
private fetchInitialVesselState;
/**
* Recursively populates latestValues Map from SignalK API response data
*
* This helper method traverses the nested SignalK data structure and
* extracts all value objects, storing them in the latestValues Map
* with proper full path keys (context.path).
*
* @param obj - The SignalK data object to traverse
* @param context - The vessel context (e.g., 'vessels.self')
* @param pathPrefix - Current path prefix being built
*
* @private
*/
private populateLatestValuesFromData;
/**
* Processes incoming SignalK delta messages and updates internal data stores
*
* Delta message processing:
* - Updates latest values cache with timestamps
* - Tracks AIS targets from other vessels
* - Monitors system notifications and alarms
* - Discovers available data paths automatically
* - Emits 'delta' event for external listeners
*
* @param delta - SignalK delta message with vessel updates
*
* @example
* // Delta messages are received automatically via WebSocket
* client.on('delta', (delta) => {
* console.log('Vessel context:', delta.context);
* console.log('Updates:', delta.updates);
* });
*/
handleDelta(delta: SignalKDelta): void;
/**
* Processes individual value updates from SignalK delta messages
*
* Update processing:
* - Stores latest values with full path keys (context.path)
* - Maintains set of available data paths
* - Updates AIS target data for other vessels
* - Processes notification/alarm state changes
* - Preserves timestamps and source information
*
* @param message - SignalK delta message containing updates array
*
* @example
* // Updates are processed automatically from delta messages:
* // {
* // "context": "vessels.self",
* // "updates": [{
* // "timestamp": "2023-06-22T10:30:15Z",
* // "values": [{
* // "path": "navigation.position",
* // "value": {"latitude": 37.8199, "longitude": -122.4783}
* // }]
* // }]
* // }
*/
processUpdates(message: SignalKDelta): void;
/**
* Updates AIS target information for other vessels detected in the area
*
* AIS data tracking:
* - Creates new target entries for unknown vessels
* - Updates existing targets with latest position/course/speed data
* - Maintains MMSI identifier and last update timestamp
* - Supports any SignalK path (position, course, speed, name, etc.)
*
* @param vesselContext - Vessel context (e.g., 'vessels.urn:mrn:imo:mmsi:123456789')
* @param path - SignalK data path (e.g., 'navigation.position')
* @param value - The data value for this path
* @param timestamp - ISO timestamp of the update
*
* @example
* // AIS targets are updated automatically from delta messages:
* // Context: "vessels.urn:mrn:imo:mmsi:123456789"
* // Path: "navigation.position"
* // Value: {"latitude": 37.8200, "longitude": -122.4800}
*
* const targets = client.getAISTargets();
* console.log('Nearby vessels:', targets.targets.length);
*/
updateAISTarget(vesselContext: string, path: string, value: any, timestamp: string): void;
/**
* Updates active alarm and notification states from SignalK notification paths
*
* Alarm processing:
* - Adds alarms when state is not 'normal' (alert, warn, alarm, emergency)
* - Removes alarms when state returns to 'normal' or null
* - Preserves alarm message and metadata
* - Tracks timestamp of alarm state changes
*
* @param path - Notification path (e.g., 'notifications.engines.temperature')
* @param value - Notification object with state and message
* @param timestamp - ISO timestamp of the notification
*
* @example
* // Alarms are updated automatically from notification paths:
* // Path: "notifications.engines.temperature"
* // Value: {
* // "state": "alert",
* // "message": "Engine temperature high",
* // "method": ["visual", "sound"]
* // }
*
* const alarms = client.getActiveAlarms();
* console.log('Active alarms:', alarms.count);
*/
updateAlarms(path: string, value: any, timestamp: string): void;
/**
* Returns current vessel state with all available sensor data, navigation information, and vessel identity
*
* This method fetches fresh data directly from the SignalK HTTP API on each request,
* ensuring that stale cached data is never returned. The response includes:
* - All SignalK paths for the current vessel context (vessels.self by default)
* - Vessel identity information (name, MMSI, call sign)
* - Position, heading, speed, wind, engine data, etc.
* - Latest values with timestamps and source information
* - Connection status and context information
*
* @returns Promise<VesselState> object with fresh data from SignalK server
*
* @example
* const state = await client.getVesselState();
* console.log('Vessel name:', state.data['name']?.value);
* console.log('Position:', state.data['navigation.position']?.value);
* console.log('Speed:', state.data['navigation.speedOverGround']?.value);
* console.log('Wind:', state.data['environment.wind']?.value);
*
* // Example response:
* // {
* // "connected": true,
* // "context": "vessels.self",
* // "timestamp": "2023-06-22T10:30:15.123Z",
* // "data": {
* // "name": {
* // "value": "My Vessel",
* // "timestamp": "2023-06-22T10:30:15.000Z",
* // "source": "vessel-identity"
* // },
* // "navigation.position": {
* // "value": {"latitude": 37.8199, "longitude": -122.4783},
* // "timestamp": "2023-06-22T10:30:15.000Z",
* // "source": {"label": "GPS1", "type": "NMEA0183"}
* // },
* // "navigation.speedOverGround": {
* // "value": 5.2,
* // "timestamp": "2023-06-22T10:30:15.000Z"
* // }
* // }
* // }
*/
getVesselState(): Promise<VesselState>;
/**
* Calculates the distance between two geographic coordinates using the Haversine formula
*
* @param lat1 - Latitude of first point
* @param lon1 - Longitude of first point
* @param lat2 - Latitude of second point
* @param lon2 - Longitude of second point
* @returns Distance in meters
*/
private calculateDistance;
/**
* Pattern-based filtering for AIS data fields
* Determines if a SignalK path should be included in AIS target data
*
* @param path - SignalK data path to check
* @returns true if the path should be included, false otherwise
*/
private shouldIncludeAISPath;
/**
* Returns nearby AIS targets (other vessels) with their position and navigation data
*
* This method fetches fresh AIS data directly from the SignalK HTTP API on each request,
* ensuring that stale cached data is never returned. The response includes:
* - Only vessels with proper MMSI identifiers (true AIS targets)
* - Position, course, speed, and vessel identification
* - Distance in meters from self vessel (when positions available)
* - Sorted by proximity (closest vessels first)
* - Supports pagination with configurable page size
* - Only includes targets updated within last 5 minutes
*
* @param page - Page number (1-based, default: 1)
* @param pageSize - Number of targets per page (default: 10, max: 50)
* @returns Promise<AISTargetsResponse> with array of nearby vessels
*
* @example
* const targets = await client.getAISTargets(1, 10);
* console.log(`Found ${targets.count} nearby vessels`);
* console.log(`Page ${targets.pagination.page} of ${targets.pagination.totalPages}`);
*
* targets.targets.forEach(target => {
* console.log(`MMSI: ${target.mmsi}`);
* if (target.distanceMeters) {
* console.log(`Distance: ${target.distanceMeters}m`);
* }
* if (target['navigation.position']) {
* console.log(`Position: ${target['navigation.position'].value.latitude}, ${target['navigation.position'].value.longitude}`);
* }
* });
*
* // Example response:
* // {
* // "connected": true,
* // "count": 2,
* // "timestamp": "2023-06-22T10:30:15.123Z",
* // "targets": [
* // {
* // "mmsi": "123456789",
* // "distanceMeters": 1852.5,
* // "navigation.position": {
* // "value": {"latitude": 37.8200, "longitude": -122.4800},
* // "timestamp": "2023-06-22T10:29:45.000Z"
* // },
* // "lastUpdate": "2023-06-22T10:29:45.000Z"
* // }
* // ],
* // "pagination": {
* // "page": 1,
* // "pageSize": 10,
* // "totalCount": 15,
* // "totalPages": 2,
* // "hasNextPage": true,
* // "hasPreviousPage": false
* // }
* // }
*/
getAISTargets(page?: number, pageSize?: number): Promise<AISTargetsResponse>;
/**
* Returns all alarms and system notifications including resolved (normal state) alarms
*
* This method fetches fresh alarm data directly from the SignalK HTTP API on each request,
* ensuring that stale cached data is never returned. The response includes:
* - All notification paths from the current vessel
* - Alarm states: alert, warn, alarm, emergency, and normal (resolved)
* - Notification messages and metadata
* - Fresh timestamps for each notification
*
* @returns Promise<ActiveAlarmsResponse> with array of all notifications
*
* @example
* const alarms = await client.getActiveAlarms();
* console.log(`${alarms.count} total alarms (including resolved)`);
*
* // Filter for only critical alarms
* const criticalAlarms = alarms.alarms.filter(alarm =>
* alarm.state !== 'normal'
* );
* console.log(`${criticalAlarms.length} critical alarms`);
*
* alarms.alarms.forEach(alarm => {
* console.log(`${alarm.state}: ${alarm.message || 'No message'}`);
* console.log(`Path: ${alarm.path}`);
* console.log(`Time: ${alarm.timestamp}`);
* });
*
* // Example response:
* // {
* // "connected": true,
* // "count": 2,
* // "timestamp": "2023-06-22T10:30:15.123Z",
* // "alarms": [
* // {
* // "path": "notifications.engines.temperature",
* // "state": "normal",
* // "message": "Engine temperature normal",
* // "timestamp": "2023-06-22T10:25:30.000Z"
* // },
* // {
* // "path": "notifications.battery.voltage",
* // "state": "alert",
* // "message": "Battery voltage low",
* // "timestamp": "2023-06-22T10:28:45.000Z"
* // }
* // ]
* // }
*/
getActiveAlarms(): Promise<ActiveAlarmsResponse>;
/**
* Discovers and returns all available SignalK data paths on the server
*
* Path discovery:
* - Primary: Uses HTTP REST API to get complete path list from server
* - Fallback: Uses WebSocket-discovered paths if HTTP fails
* - Filters out metadata fields ($schema, meta, timestamp)
* - Returns sorted alphabetical list of available data paths
*
* @returns Promise<AvailablePathsResponse> with array of available paths
*
* @example
* const pathsResponse = await client.listAvailablePaths();
* console.log(`${pathsResponse.count} paths available`);
*
* pathsResponse.paths.forEach(path => {
* console.log(`Available: ${path}`);
* });
*
* // Example response:
* // {
* // "connected": true,
* // "count": 25,
* // "timestamp": "2023-06-22T10:30:15.123Z",
* // "paths": [
* // "electrical.batteries.house.voltage",
* // "environment.wind.speedApparent",
* // "navigation.courseOverGround",
* // "navigation.position",
* // "navigation.speedOverGround",
* // "propulsion.main.temperature"
* // ]
* // }
*/
listAvailablePaths(): Promise<AvailablePathsResponse>;
/**
* Gets the latest value for a specific SignalK data path
*
* Value retrieval:
* - Primary: Uses HTTP REST API for real-time data from server
* - Fallback: Uses WebSocket-cached value if HTTP fails
* - Returns complete value object with metadata
* - Supports any valid SignalK path
*
* @param path - SignalK data path in dot notation (e.g., 'navigation.position')
* @returns Promise<PathValueResponse> with latest value and metadata
*
* @example
* // Get current position
* const position = await client.getPathValue('navigation.position');
* console.log('Latitude:', position.data.value.latitude);
* console.log('Longitude:', position.data.value.longitude);
*
* // Get wind speed
* const windSpeed = await client.getPathValue('environment.wind.speedApparent');
* console.log('Wind speed:', windSpeed.data.value, 'm/s');
*
* // Get engine temperature
* const engineTemp = await client.getPathValue('propulsion.main.temperature');
* console.log('Engine temp:', engineTemp.data.value, 'K');
*
* // Example response:
* // {
* // "connected": true,
* // "path": "navigation.position",
* // "timestamp": "2023-06-22T10:30:15.123Z",
* // "data": {
* // "value": {
* // "latitude": 37.8199,
* // "longitude": -122.4783
* // },
* // "timestamp": "2023-06-22T10:30:15.000Z",
* // "source": {
* // "label": "GPS1",
* // "type": "NMEA0183"
* // }
* // }
* // }
*/
getPathValue(path: string): Promise<PathValueResponse>;
/**
* Returns comprehensive connection status and client configuration information
*
* This method now reflects HTTP-only mode status. WebSocket information is
* preserved for future streaming support but not actively used.
*
* Status information:
* - HTTP connection state (verified during connect())
* - Server URLs (both WebSocket and HTTP for reference)
* - Configuration details (hostname, port, TLS)
* - Vessel context being monitored
*
* Note: Cache statistics (pathCount, aisTargetCount, activeAlarmCount) will
* always be 0 in HTTP-only mode as data is fetched fresh on each request.
*
* @returns ConnectionStatus object with detailed connection information
*
* @example
* const status = client.getConnectionStatus();
* console.log('Connected:', status.connected);
* console.log('Server:', status.hostname + ':' + status.port);
* console.log('TLS:', status.useTLS);
* console.log('HTTP URL:', status.httpUrl);
*
* // Example response:
* // {
* // "connected": true,
* // "url": "http://localhost:3000",
* // "wsUrl": "ws://localhost:3000", // Preserved for future use
* // "httpUrl": "http://localhost:3000",
* // "hostname": "localhost",
* // "port": 3000,
* // "useTLS": false,
* // "context": "vessels.self",
* // "pathCount": 0, // Always 0 in HTTP-only mode
* // "aisTargetCount": 0, // Always 0 in HTTP-only mode
* // "activeAlarmCount": 0, // Always 0 in HTTP-only mode
* // "timestamp": "2023-06-22T10:30:15.123Z"
* // }
*/
getConnectionStatus(): ConnectionStatus;
/**
* Cleanly disconnects from the SignalK server
*
* In HTTP-only mode, this simply sets the connected flag to false.
* The WebSocket disconnect is preserved for future streaming support.
*
* @example
* // Disconnect when done
* client.disconnect();
* console.log('Disconnected from SignalK server');
*/
disconnect(): void;
}
//# sourceMappingURL=signalk-client.d.ts.map