Skip to main content
Glama
keithah

Tessie MCP Extension

by keithah
tessie-client.ts7.64 kB
import axios, { AxiosInstance, AxiosResponse } from 'axios'; import { ErrorHandler, EnhancedError } from './error-handler.js'; export interface TessieVehicleState { display_name?: string; vin: string; state?: string; timestamp?: number; // Nested structure (Tesla API format) vehicle_state?: { vehicle_name?: string; locked?: boolean; sentry_mode?: boolean; odometer?: number; }; charge_state?: { battery_level?: number; est_battery_range?: number; charging_state?: string; time_to_full_charge?: number; }; climate_state?: { inside_temp?: number; outside_temp?: number; is_climate_on?: boolean; }; drive_state?: { latitude?: number; longitude?: number; shift_state?: string; speed?: number; power?: number; }; // Flat properties (Tessie API actual response format) // These are added to match the actual API behavior battery_level?: number; battery_range?: number; charging_state?: string; time_to_full_charge?: number; latitude?: number; longitude?: number; locked?: boolean; sentry_mode?: boolean; odometer?: number; inside_temp?: number; outside_temp?: number; is_climate_on?: boolean; } export interface TessieDrive { id: number; import_id?: string; started_at: number; ended_at: number; created_at: number; updated_at?: number; starting_location: string; starting_latitude: number; starting_longitude: number; starting_odometer: number; starting_saved_location?: string; ending_location: string; ending_latitude: number; ending_longitude: number; ending_odometer: number; ending_saved_location?: string; starting_battery: number; ending_battery: number; average_inside_temperature?: number; average_outside_temperature?: number; average_speed?: number; max_speed?: number; rated_range_used?: number; ideal_range_used?: number; odometer_distance: number; energy_used?: number; tag?: string; // Legacy field mappings for backward compatibility start_date?: string; end_date?: string; start_address?: string; end_address?: string; start_saved_location?: string; end_saved_location?: string; distance_miles?: number; duration_min?: number; start_odometer?: number; end_odometer?: number; start_battery_level?: number; end_battery_level?: number; } export interface TessieLocation { vin: string; latitude: number; longitude: number; address: string; saved_location?: string; } export interface TessieTirePressure { front_left: number; front_right: number; rear_left: number; rear_right: number; front_left_status: 'unknown' | 'low' | 'normal'; front_right_status: 'unknown' | 'low' | 'normal'; rear_left_status: 'unknown' | 'low' | 'normal'; rear_right_status: 'unknown' | 'low' | 'normal'; timestamp: number; } export class TessieClient { private client: AxiosInstance; private accessToken: string; constructor(accessToken: string) { this.accessToken = accessToken; this.client = axios.create({ baseURL: 'https://api.tessie.com', timeout: 30000, headers: { 'Authorization': `Bearer ${accessToken}`, 'Content-Type': 'application/json', }, }); } async getVehicleState(vin: string, useCache: boolean = true): Promise<TessieVehicleState> { return ErrorHandler.withRetry(async () => { const response: AxiosResponse<TessieVehicleState> = await this.client.get( `/${vin}/state${useCache ? '?use_cache=true' : ''}` ); return response.data; }, { maxRetries: useCache ? 2 : 3, // Fewer retries when using cache baseDelay: 1500 }); } async getVehicleStates( vin: string, startDate?: string, endDate?: string ): Promise<TessieVehicleState[]> { return ErrorHandler.withRetry(async () => { const params = new URLSearchParams(); if (startDate) params.append('start', startDate); if (endDate) params.append('end', endDate); const response: AxiosResponse<TessieVehicleState[]> = await this.client.get( `/${vin}/states?${params.toString()}` ); return response.data; }, { maxRetries: 2, // Historical data is less time-sensitive baseDelay: 2000 }); } async getVehicleLocation(vin: string): Promise<TessieLocation> { return ErrorHandler.withRetry(async () => { const response: AxiosResponse<TessieLocation> = await this.client.get(`/${vin}/location`); return response.data; }, { maxRetries: 3, baseDelay: 1000 }); } async getDrives( vin: string, startDate?: string, endDate?: string, limit: number = 50 ): Promise<TessieDrive[]> { return ErrorHandler.withRetry(async () => { const params = new URLSearchParams(); if (startDate) params.append('start', startDate); if (endDate) params.append('end', endDate); params.append('limit', limit.toString()); const response: AxiosResponse<{ results: TessieDrive[] } | TessieDrive[]> = await this.client.get( `/${vin}/drives?${params.toString()}` ); // Handle both old and new API response formats if (response.data && typeof response.data === 'object' && 'results' in response.data) { return response.data.results; } return response.data as TessieDrive[]; }, { maxRetries: 2, // Historical data requests baseDelay: 2500 }); } async getDrivingPath( vin: string, startDate: string, endDate: string ): Promise<Array<{ latitude: number; longitude: number; timestamp: string }>> { return ErrorHandler.withRetry(async () => { const params = new URLSearchParams(); params.append('start', startDate); params.append('end', endDate); const response: AxiosResponse<Array<{ latitude: number; longitude: number; timestamp: string }>> = await this.client.get(`/${vin}/path?${params.toString()}`); return response.data; }, { maxRetries: 1, // Path data is large and less critical baseDelay: 3000 }); } async getVehicles(): Promise<Array<{ vin: string; display_name: string }>> { return ErrorHandler.withRetry(async () => { const response: AxiosResponse<{ results: any[] } | any[]> = await this.client.get('/vehicles'); // Handle both old and new API response formats let vehicles: any[]; if (response.data && typeof response.data === 'object' && 'results' in response.data) { vehicles = response.data.results; } else { vehicles = response.data as any[]; } // Extract VIN and display name from the new format return vehicles.map(vehicle => ({ vin: vehicle.vin, display_name: vehicle.last_state?.vehicle_state?.vehicle_name || vehicle.display_name || `Vehicle ${vehicle.vin.slice(-6)}` })); }, { maxRetries: 2, // Account list is fairly stable baseDelay: 1500 }); } async getTirePressure( vin: string, pressureFormat: 'bar' | 'kpa' | 'psi' = 'psi', from?: number, to?: number ): Promise<TessieTirePressure> { return ErrorHandler.withRetry(async () => { const params = new URLSearchParams(); params.append('pressure_format', pressureFormat); if (from) params.append('from', from.toString()); if (to) params.append('to', to.toString()); const response: AxiosResponse<TessieTirePressure> = await this.client.get( `/${vin}/tire_pressure?${params.toString()}` ); return response.data; }, { maxRetries: 2, baseDelay: 1500 }); } }

Implementation Reference

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/keithah/tessie-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server