Skip to main content
Glama
neo.ts7.95 kB
import { z } from 'zod'; import { nasaApiRequest } from '../../utils/api-client'; import { addResource } from '../../resources'; // Schema for validating NEO request parameters export const neoParamsSchema = z.object({ start_date: z.string().optional(), end_date: z.string().optional(), asteroid_id: z.string().optional() }); // Define the request parameter type based on the schema export type NeoParams = z.infer<typeof neoParamsSchema>; /** * Handle requests for NASA's Near Earth Object Web Service (NEO WS) */ export async function nasaNeoHandler(params: NeoParams) { try { // If we're looking for a specific asteroid by ID if (params.asteroid_id) { const endpoint = `/neo/rest/v1/neo/${params.asteroid_id}`; const result = await nasaApiRequest(endpoint, {}); // Store the result as a resource addResource(`nasa://neo/${params.asteroid_id}`, { name: `Asteroid: ${result.name}`, mimeType: 'application/json', text: JSON.stringify(result, null, 2) }); // Return formatted result return { content: [ { type: "text", text: formatSingleAsteroidText(result) } ], isError: false }; } // Default to today if no dates specified let startDate = params.start_date; let endDate = params.end_date; if (!startDate) { const today = new Date(); startDate = today.toISOString().split('T')[0]; } // If no end_date, use start_date (same day) if (!endDate) { endDate = startDate; } // API limits feed to 7 days const maxDays = 7; const start = new Date(startDate); const end = new Date(endDate); const daysDiff = Math.floor((end.getTime() - start.getTime()) / (1000 * 60 * 60 * 24)); if (daysDiff > maxDays) { return { content: [ { type: "text", text: `Error: Date range too large. NEO feed is limited to ${maxDays} days.` } ], isError: true }; } // Call the NASA NEO API const endpoint = `/neo/rest/v1/feed?start_date=${startDate}&end_date=${endDate}`; const result = await nasaApiRequest(endpoint, {}); // Process and format the results return processNeoFeedResults(result, startDate, endDate); } catch (error: any) { console.error('Error in NEO handler:', error); return { content: [ { type: "text", text: `Error retrieving NEO data: ${error.message}` } ], isError: true }; } } /** * Format a single asteroid object into a human-readable text */ function formatSingleAsteroidText(asteroid: any): string { // Format the diameter in km const minDiameterKm = asteroid.estimated_diameter?.kilometers?.estimated_diameter_min?.toFixed(3) || 'unknown'; const maxDiameterKm = asteroid.estimated_diameter?.kilometers?.estimated_diameter_max?.toFixed(3) || 'unknown'; const diameterText = `${minDiameterKm} - ${maxDiameterKm} km`; // Create intro text let text = `# Asteroid: ${asteroid.name}\n\n`; text += `**NEO Reference ID:** ${asteroid.id}\n`; text += `**Potentially Hazardous:** ${asteroid.is_potentially_hazardous_asteroid ? '⚠️ YES' : '✓ NO'}\n`; text += `**Estimated Diameter:** ${diameterText}\n\n`; // Add close approach data const closeApproaches = asteroid.close_approach_data || []; if (closeApproaches.length > 0) { text += `## Close Approaches\n\n`; // Only show the first 5 close approaches to avoid excessively long text const displayLimit = 5; const showCount = Math.min(displayLimit, closeApproaches.length); for (let i = 0; i < showCount; i++) { const ca = closeApproaches[i]; const date = ca.close_approach_date; const distance = Number(ca.miss_distance.kilometers).toFixed(3); const lunarDistance = (Number(ca.miss_distance.lunar) || 0).toFixed(2); const velocity = Number(ca.relative_velocity.kilometers_per_second).toFixed(2); text += `- **Date:** ${date}\n`; text += ` **Distance:** ${distance} km (${lunarDistance} lunar distances)\n`; text += ` **Relative Velocity:** ${velocity} km/s\n\n`; } // Add a note if there are more close approaches than we're showing if (closeApproaches.length > displayLimit) { text += `\n*...and ${closeApproaches.length - displayLimit} more close approaches*\n`; } } return text; } /** * Process and format NEO feed results for a date range */ function processNeoFeedResults(feedData: any, startDate: string, endDate: string) { try { // Store the feed data as a resource const resourceId = `neo-feed-${startDate}-${endDate}`; addResource(`nasa://neo/feed/${resourceId}`, { name: `NEO Feed: ${startDate} to ${endDate}`, mimeType: 'application/json', text: JSON.stringify(feedData, null, 2) }); // Format the data for display const text = formatNeoFeedText(feedData, startDate, endDate); return { content: [ { type: "text", text: text } ], isError: false }; } catch (error: any) { console.error('Error processing NEO feed results:', error); return { content: [ { type: "text", text: `Error processing NEO feed data: ${error.message}` } ], isError: true }; } } /** * Format NEO feed data into a human-readable text */ function formatNeoFeedText(feedData: any, startDate: string, endDate: string): string { // Get the count of NEOs const neoCount = feedData.element_count || 0; const dateRangeText = startDate === endDate ? startDate : `${startDate} to ${endDate}`; // Start with a summary let text = `# Near Earth Objects (${dateRangeText})\n\n`; text += `**Found ${neoCount} near-Earth objects**\n\n`; // Get the near earth objects by date const neosByDate = feedData.near_earth_objects || {}; // For each date Object.keys(neosByDate).sort().forEach(date => { const objects = neosByDate[date] || []; text += `## ${date} (${objects.length} objects)\n\n`; // Sort by close approach time objects.sort((a: any, b: any) => { const timeA = a.close_approach_data?.[0]?.close_approach_date_full || ''; const timeB = b.close_approach_data?.[0]?.close_approach_date_full || ''; return timeA.localeCompare(timeB); }); // For each object objects.forEach((neo: any) => { const name = neo.name; const id = neo.id; const isHazardous = neo.is_potentially_hazardous_asteroid; const minDiameterKm = neo.estimated_diameter?.kilometers?.estimated_diameter_min?.toFixed(3) || '?'; const maxDiameterKm = neo.estimated_diameter?.kilometers?.estimated_diameter_max?.toFixed(3) || '?'; // Close approach data const closeApproach = neo.close_approach_data?.[0] || {}; const approachTime = closeApproach.close_approach_date_full || '?'; const distanceKm = Number(closeApproach.miss_distance?.kilometers || 0).toFixed(0); const lunarDistance = Number(closeApproach.miss_distance?.lunar || 0).toFixed(2); const velocityKmps = Number(closeApproach.relative_velocity?.kilometers_per_second || 0).toFixed(2); text += `### ${name} (ID: ${id})\n\n`; text += `- **Potentially Hazardous:** ${isHazardous ? '⚠️ YES' : '✓ NO'}\n`; text += `- **Estimated Diameter:** ${minDiameterKm} - ${maxDiameterKm} km\n`; text += `- **Closest Approach:** ${approachTime}\n`; text += `- **Miss Distance:** ${distanceKm} km (${lunarDistance} lunar distances)\n`; text += `- **Relative Velocity:** ${velocityKmps} km/s\n\n`; }); }); return text; } // Export the handler function directly as default export default nasaNeoHandler;

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/BACH-AI-Tools/NASA-MCP-server'

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