Skip to main content
Glama
gpt-controller.ts11 kB
import axios from 'axios'; import WebSocket from 'ws'; import { FlightData } from '../models/flight-data'; /** * GPT Controller for GeoFS MCP * * This controller handles communication between GPT-4o and the MCP server, * allowing natural language commands to be translated into aircraft controls. */ export class GPTController { private ws: WebSocket | null = null; private openaiApiKey: string; private messageId = 1; private currentFlightData: FlightData | null = null; private isConnected = false; private commandQueue: Array<{name: string, params: any}> = []; private isProcessingCommand = false; private lastGptResponse = ''; constructor(openaiApiKey: string, wsUrl: string = 'ws://localhost:3002') { this.openaiApiKey = openaiApiKey; this.connectToMCP(wsUrl); } /** * Connect to the MCP server via WebSocket */ private connectToMCP(wsUrl: string): void { console.log(`Connecting to MCP server at ${wsUrl}...`); this.ws = new WebSocket(wsUrl); this.ws.on('open', () => { console.log('Connected to MCP server'); this.isConnected = true; // Request initial flight data this.sendCommand('getFlightData'); }); this.ws.on('message', (data: WebSocket.Data) => { try { const message = JSON.parse(data.toString()); console.log('Received message from MCP server:', message); // Handle different message types if (message.type === 'flightData') { this.currentFlightData = message.data; console.log('Updated flight data'); } else if (message.id && this.isProcessingCommand) { this.isProcessingCommand = false; this.processNextCommand(); } } catch (error) { console.error('Error processing message:', error); } }); this.ws.on('close', () => { console.log('Disconnected from MCP server'); this.isConnected = false; // Try to reconnect after 5 seconds setTimeout(() => { this.connectToMCP(wsUrl); }, 5000); }); this.ws.on('error', (error) => { console.error('WebSocket error:', error); }); } /** * Process a natural language command using GPT-4o */ public async processNaturalLanguageCommand(command: string): Promise<string> { if (!this.isConnected) { return 'Not connected to MCP server. Please try again later.'; } try { // Prepare the context for GPT-4o const context = this.prepareGptContext(); // Call GPT-4o API const gptResponse = await this.callGpt(command, context); this.lastGptResponse = gptResponse; // Parse the response and extract commands const commands = this.parseGptResponse(gptResponse); // Queue the commands for execution commands.forEach(cmd => { this.queueCommand(cmd.name, cmd.params); }); // Start processing commands this.processNextCommand(); return gptResponse; } catch (error) { console.error('Error processing natural language command:', error); return 'Error processing your command. Please try again.'; } } /** * Prepare the context for GPT-4o based on current flight data */ private prepareGptContext(): string { let context = 'You are an AI flight assistant that helps control an aircraft in the GeoFS flight simulator. '; if (this.currentFlightData) { const { position, attitude, speed, controls, aircraft } = this.currentFlightData; context += `Current flight data:\n`; context += `- Aircraft: ${aircraft?.name || 'Unknown'}\n`; context += `- Position: Latitude ${position?.latitude.toFixed(6)}, Longitude ${position?.longitude.toFixed(6)}, Altitude ${position?.altitude.toFixed(1)}m\n`; context += `- Attitude: Heading ${attitude?.heading.toFixed(1)}°, Pitch ${attitude?.pitch.toFixed(1)}°, Roll ${attitude?.roll.toFixed(1)}°\n`; context += `- Speed: ${speed?.kias.toFixed(1)} KIAS, Vertical Speed ${(speed?.verticalSpeed * 196.85).toFixed(0)} ft/min\n`; context += `- Controls: Throttle ${controls?.throttle.toFixed(2)}, Flaps ${controls?.flaps.toFixed(2)}, Gear ${controls?.gear.toFixed(2)}\n\n`; } else { context += 'No current flight data available.\n\n'; } context += `You can control the aircraft using these commands: - setThrottle: Set engine throttle (0-1) - setFlaps: Set flaps position (0-1) - setGear: Set landing gear position (0-1 where 0 is up, 1 is down) - setHeading: Set target heading in degrees - setPitch: Set target pitch in degrees - setRoll: Set target roll in degrees - setAltitude: Set target altitude in meters - startPattern: Start a predefined flight pattern (options: takeoff, landing, approach, holdingPattern) When responding to user requests, provide a brief explanation of what you're doing, then output a JSON array of commands in this format: [ {"name": "commandName", "params": {"paramName": paramValue}}, {"name": "anotherCommand", "params": {"paramName": paramValue}} ] For complex maneuvers like takeoff, break them down into a sequence of commands with appropriate parameters.`; return context; } /** * Call the GPT-4o API with the user's command and context */ private async callGpt(command: string, context: string): Promise<string> { try { // This is a placeholder for the actual OpenAI API call // In a real implementation, you would use the OpenAI SDK or API // For demo purposes, we'll simulate responses for common commands if (command.toLowerCase().includes('takeoff') || command.toLowerCase().includes('take off')) { return this.simulateTakeoffResponse(command); } else if (command.toLowerCase().includes('land')) { return this.simulateLandingResponse(command); } else if (command.toLowerCase().includes('turn')) { return this.simulateTurnResponse(command); } else { // Generic response for other commands return `I'll help you with that request. Here's what I'll do:\n\n[{"name":"getFlightData","params":{}}]`; } // Actual OpenAI API call would look something like this: /* const response = await axios.post( 'https://api.openai.com/v1/chat/completions', { model: 'gpt-4o', messages: [ { role: 'system', content: context }, { role: 'user', content: command } ], temperature: 0.7, max_tokens: 500 }, { headers: { 'Authorization': `Bearer ${this.openaiApiKey}`, 'Content-Type': 'application/json' } } ); return response.data.choices[0].message.content; */ } catch (error) { console.error('Error calling GPT API:', error); throw error; } } /** * Simulate a takeoff response for demo purposes */ private simulateTakeoffResponse(command: string): string { return `I'll help you take off. Here's the sequence of commands I'll execute: 1. First, I'll set the flaps to takeoff position (30%) 2. Then I'll make sure the landing gear is down 3. I'll gradually increase throttle to 100% 4. Once we reach takeoff speed, I'll pitch up to 10 degrees 5. After positive climb, I'll retract the landing gear 6. At 1000ft, I'll reduce flaps and maintain climb [ {"name":"setFlaps","params":{"value":0.3}}, {"name":"setGear","params":{"value":1}}, {"name":"setThrottle","params":{"value":1.0}}, {"name":"startPattern","params":{"pattern":"takeoff"}} ]`; } /** * Simulate a landing response for demo purposes */ private simulateLandingResponse(command: string): string { return `I'll help you land the aircraft. Here's the sequence: 1. I'll reduce speed by setting throttle to 30% 2. I'll extend flaps to 50% for initial approach 3. I'll lower the landing gear 4. I'll maintain a 3-degree glideslope to the runway 5. Just before touchdown, I'll flare by pitching up slightly 6. After touchdown, I'll reduce throttle to idle [ {"name":"setThrottle","params":{"value":0.3}}, {"name":"setFlaps","params":{"value":0.5}}, {"name":"setGear","params":{"value":1}}, {"name":"startPattern","params":{"pattern":"landing"}} ]`; } /** * Simulate a turn response for demo purposes */ private simulateTurnResponse(command: string): string { // Extract heading from command if possible let heading = 90; // Default to 90 degrees const headingMatch = command.match(/(\d+)\s*degrees/); if (headingMatch) { heading = parseInt(headingMatch[1], 10); } return `I'll turn the aircraft to a heading of ${heading} degrees. 1. I'll bank the aircraft by setting roll to 20 degrees 2. Once we reach the desired heading, I'll level off [ {"name":"setHeading","params":{"degrees":${heading}}} ]`; } /** * Parse GPT-4o response to extract commands */ private parseGptResponse(response: string): Array<{name: string, params: any}> { try { // Extract JSON array from the response const jsonMatch = response.match(/\[\s*\{.*\}\s*\]/s); if (jsonMatch) { const jsonStr = jsonMatch[0]; const commands = JSON.parse(jsonStr); return commands; } return []; } catch (error) { console.error('Error parsing GPT response:', error); return []; } } /** * Queue a command for execution */ private queueCommand(name: string, params: any = {}): void { this.commandQueue.push({ name, params }); this.processNextCommand(); } /** * Process the next command in the queue */ private processNextCommand(): void { if (this.commandQueue.length === 0 || this.isProcessingCommand || !this.isConnected) { return; } this.isProcessingCommand = true; const command = this.commandQueue.shift(); if (command) { this.sendCommand(command.name, command.params); } } /** * Send a command to the MCP server */ private sendCommand(command: string, params: any = {}): void { if (!this.ws || this.ws.readyState !== WebSocket.OPEN) { console.error('WebSocket not connected'); this.isProcessingCommand = false; return; } const message = { id: this.messageId++, type: 'command', command, params }; this.ws.send(JSON.stringify(message)); console.log(`Sent command: ${command}`, params); } /** * Get the last GPT response */ public getLastGptResponse(): string { return this.lastGptResponse; } /** * Get the current flight data */ public getCurrentFlightData(): FlightData | null { return this.currentFlightData; } /** * Close the WebSocket connection */ public close(): void { if (this.ws) { this.ws.close(); } } }

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/lobstercare/geofs-mcp'

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