Skip to main content
Glama

Flight MCP Server

by sragss
index.tsâ€ĸ15.3 kB
#!/usr/bin/env node import { config } from "dotenv"; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js"; import { z } from "zod"; config(); interface AircraftData { hex: string; reg?: string; type?: string; t?: string; squawk?: string; callsign?: string; lat?: number; lon?: number; alt_baro?: number | "ground"; gs?: number; track?: number; seen?: number; } interface ADSBResponse { ac?: AircraftData[]; [key: string]: any; } class FlightMCPServer { private server: Server; private rapidApiKey: string; private baseUrl = "https://adsbexchange-com1.p.rapidapi.com/v2"; constructor() { this.rapidApiKey = process.env.RAPIDAPI_KEY || ""; if (!this.rapidApiKey) { throw new Error("RAPIDAPI_KEY environment variable is required"); } this.server = new Server( { name: "flight-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); this.setupHandlers(); } private async makeApiRequest(endpoint: string): Promise<ADSBResponse> { const response = await fetch(`${this.baseUrl}${endpoint}`, { headers: { "x-rapidapi-host": "adsbexchange-com1.p.rapidapi.com", "x-rapidapi-key": this.rapidApiKey, }, }); if (!response.ok) { throw new Error(`API request failed: ${response.status} ${response.statusText}`); } return response.json(); } private filterAircraftByRegion(aircraft: AircraftData[], region: string): AircraftData[] { if (region === "all") return aircraft; return aircraft.filter(ac => { if (!ac.lat || !ac.lon) return false; switch (region) { case "us": return ac.lat >= 25 && ac.lat <= 70 && ac.lon >= -170 && ac.lon <= -65; case "europe": return ac.lat >= 35 && ac.lat <= 75 && ac.lon >= -15 && ac.lon <= 40; case "asia": return ac.lat >= 10 && ac.lat <= 60 && ac.lon >= 70 && ac.lon <= 180; default: return true; } }); } private formatMilitaryAircraft(aircraft: AircraftData): any { return { hex: aircraft.hex, callsign: aircraft.callsign?.trim() || null, registration: aircraft.reg || null, type: aircraft.t || null, position: aircraft.lat && aircraft.lon ? { lat: Math.round(aircraft.lat * 1000) / 1000, lon: Math.round(aircraft.lon * 1000) / 1000 } : null, altitude: aircraft.alt_baro === "ground" ? "ground" : aircraft.alt_baro || null, speed: aircraft.gs || null, heading: aircraft.track || null, squawk: aircraft.squawk || null, seen_seconds: aircraft.seen || null }; } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "search_aircraft", description: "Return live ADS-B targets within a given radius of a point.", inputSchema: { type: "object", properties: { lat: { type: "number", minimum: -90, maximum: 90, description: "Latitude in decimal degrees" }, lon: { type: "number", minimum: -180, maximum: 180, description: "Longitude in decimal degrees" }, radius_nm: { type: "number", minimum: 0, maximum: 250, description: "Search radius in nautical miles (typical buckets 1,5,10,25,50,100,250; any positive value accepted)" } }, required: ["lat", "lon", "radius_nm"] } }, { name: "get_aircraft", description: "Fetch the latest position & metadata for one aircraft by 6-digit ICAO hex.", inputSchema: { type: "object", properties: { hex: { type: "string", pattern: "^[0-9A-Fa-f]{6}$", description: "6-character ICAO hex code" } }, required: ["hex"] } }, { name: "list_military_aircraft", description: "Return a filtered list of military aircraft with essential information only.", inputSchema: { type: "object", properties: { limit: { type: "number", minimum: 1, maximum: 50, default: 20, description: "Maximum number of aircraft to return per page (default: 20)" }, page: { type: "number", minimum: 1, default: 1, description: "Page number for pagination (1-based, default: 1)" }, min_altitude: { type: "number", minimum: 0, description: "Minimum altitude in feet (filters out ground aircraft)" }, region: { type: "string", enum: ["us", "europe", "asia", "all"], default: "all", description: "Geographic region filter" }, aircraft_type: { type: "string", description: "Filter by aircraft type. Use 'types_only' to see available types, or partial matches like: H60 (helicopters), C17/C130/C27J (cargo), K35R/KC135 (tankers), F16/F35 (fighters), B52 (bombers), V22 (tiltrotor)" }, show_available_types: { type: "boolean", default: false, description: "Set to true to return only a list of available aircraft types in the current dataset" } } } }, { name: "get_military_aircraft_types", description: "Get a list of all military aircraft types currently active to help with filtering.", inputSchema: { type: "object", properties: { region: { type: "string", enum: ["us", "europe", "asia", "all"], default: "all", description: "Geographic region to check for aircraft types" } } } } ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "search_aircraft": { const searchSchema = z.object({ lat: z.number().min(-90).max(90), lon: z.number().min(-180).max(180), radius_nm: z.number().min(0).max(250), }); const { lat, lon, radius_nm } = searchSchema.parse(args); const endpoint = `/lat/${lat}/lon/${lon}/dist/${Math.round(radius_nm)}`; const data = await this.makeApiRequest(endpoint); return { content: [ { type: "text", text: JSON.stringify({ query: { lat, lon, radius_nm }, aircraft_count: data.ac?.length || 0, aircraft: data.ac || [] }, null, 2), }, ], }; } case "get_aircraft": { const aircraftSchema = z.object({ hex: z.string().regex(/^[0-9A-Fa-f]{6}$/), }); const { hex } = aircraftSchema.parse(args); const endpoint = `/icao/${hex}`; const data = await this.makeApiRequest(endpoint); return { content: [ { type: "text", text: JSON.stringify({ query: { hex }, aircraft: data.ac?.[0] || null }, null, 2), }, ], }; } case "list_military_aircraft": { const schema = z.object({ limit: z.number().min(1).max(50).default(20), page: z.number().min(1).default(1), min_altitude: z.number().min(0).optional(), region: z.enum(["us", "europe", "asia", "all"]).default("all"), aircraft_type: z.string().optional(), show_available_types: z.boolean().default(false) }); const filters = schema.parse(args); const endpoint = "/mil/"; const data = await this.makeApiRequest(endpoint); let filtered = data.ac || []; // Apply regional filter filtered = this.filterAircraftByRegion(filtered, filters.region); // If only showing types, return unique aircraft types if (filters.show_available_types) { const types = [...new Set(filtered.map(ac => ac.t).filter(Boolean))].sort(); const typeCounts = types.map(type => ({ type, count: filtered.filter(ac => ac.t === type).length, examples: filtered.filter(ac => ac.t === type && ac.callsign).slice(0, 3).map(ac => ac.callsign?.trim()).filter(Boolean) })); return { content: [ { type: "text", text: JSON.stringify({ region: filters.region, total_aircraft: filtered.length, available_types: typeCounts }, null, 2), }, ], }; } // Filter by minimum altitude if (filters.min_altitude !== undefined) { filtered = filtered.filter(ac => ac.alt_baro !== "ground" && ac.alt_baro !== undefined && ac.alt_baro >= filters.min_altitude! ); } // Filter by aircraft type (support partial matches) if (filters.aircraft_type) { const typeFilter = filters.aircraft_type.toUpperCase(); filtered = filtered.filter(ac => ac.t?.toUpperCase().includes(typeFilter) ); } // Sort by most recent activity (lowest 'seen' value) filtered.sort((a, b) => (a.seen || 999) - (b.seen || 999)); // Calculate pagination const totalFiltered = filtered.length; const totalPages = Math.ceil(totalFiltered / filters.limit); const startIndex = (filters.page - 1) * filters.limit; const endIndex = startIndex + filters.limit; // Apply pagination const paginatedResults = filtered.slice(startIndex, endIndex); // Format for cleaner output const formattedAircraft = paginatedResults.map(ac => this.formatMilitaryAircraft(ac)); return { content: [ { type: "text", text: JSON.stringify({ filters_applied: filters, pagination: { current_page: filters.page, total_pages: totalPages, per_page: filters.limit, total_filtered: totalFiltered, showing_range: `${startIndex + 1}-${Math.min(endIndex, totalFiltered)}`, has_next_page: filters.page < totalPages, has_previous_page: filters.page > 1 }, total_military_aircraft: data.ac?.length || 0, aircraft: formattedAircraft }, null, 2), }, ], }; } case "get_military_aircraft_types": { const schema = z.object({ region: z.enum(["us", "europe", "asia", "all"]).default("all") }); const filters = schema.parse(args); const endpoint = "/mil/"; const data = await this.makeApiRequest(endpoint); let filtered = data.ac || []; // Apply regional filter filtered = this.filterAircraftByRegion(filtered, filters.region); // Get aircraft type statistics const typeStats = new Map<string, {count: number, examples: string[]}>(); filtered.forEach(ac => { if (ac.t) { const existing = typeStats.get(ac.t) || {count: 0, examples: []}; existing.count++; if (ac.callsign && existing.examples.length < 3) { existing.examples.push(ac.callsign.trim()); } typeStats.set(ac.t, existing); } }); const sortedTypes = Array.from(typeStats.entries()) .map(([type, stats]) => ({type, ...stats})) .sort((a, b) => b.count - a.count); return { content: [ { type: "text", text: JSON.stringify({ region: filters.region, total_military_aircraft: filtered.length, aircraft_types: sortedTypes, usage_examples: { "Helicopters": "aircraft_type: 'H60' or 'H47'", "Cargo planes": "aircraft_type: 'C17' or 'C130'", "Tankers": "aircraft_type: 'K35' or 'KC135'", "Fighters": "aircraft_type: 'F16' or 'F35'", "Transport": "aircraft_type: 'C27J' or 'V22'" } }, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: "text", text: `Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("Flight MCP server running on stdio"); } } if (import.meta.url === `file://${process.argv[1]}`) { const server = new FlightMCPServer(); server.run().catch(console.error); }

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/sragss/flight-mcp'

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