Skip to main content
Glama

NASA MCP Server

index.ts75.5 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import dotenv from "dotenv"; import { setupHandlers } from "./handlers/setup"; import { setupEnvironment } from "./utils/env-setup"; import { z } from "zod"; import { CallToolRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import path from 'path'; import { nasaApiRequest, jplApiRequest } from './utils/api-client'; import { apodParamsSchema } from './handlers/nasa/apod'; import { resources, addResource as addResourceCore, Resource } from './resources'; // Load environment variables with enhanced setup setupEnvironment(); // Also load with standard dotenv for compatibility dotenv.config(); // Keep a reference to the server for notifications let serverInstance: Server | null = null; // Define resource generator function type type ResourceGenerator = (params: Record<string, string>) => Promise<{ name: string; mimeType: string; text?: string; blob?: Uint8Array; }>; // Resource templates definition export const resourceTemplates: Array<{ uriTemplate: string; name: string; description: string; generator: ResourceGenerator; }> = [ { name: "nasa-apod-image", description: "NASA Astronomy Picture of the Day", uriTemplate: "nasa://apod/image?date={date}", generator: async (params) => { const date = params["date"] || "2023-01-01"; return { name: `Astronomy Picture of the Day (${date})`, mimeType: "application/json", text: JSON.stringify({ date, title: "The Tail of a Christmas Comet", url: "https://apod.nasa.gov/apod/image/2301/CometZTF_Hernandez_1080.jpg", explanation: "Better known as Comet ZTF, this comet was captured on January 1, glowing in the predawn sky." }, null, 2) }; } }, { name: "nasa-epic-image", description: "NASA EPIC Earth observation image", uriTemplate: "nasa://epic/image?date={date}&collection={collection}", generator: async (params) => { const date = params["date"] || "2023-01-01"; const collection = params["collection"] || "natural"; return { name: `EPIC Earth View (${date})`, mimeType: "application/json", text: JSON.stringify({ date, collection, images: [ { identifier: "20230101010203", caption: "Earth from the DSCOVR satellite", image: "https://epic.gsfc.nasa.gov/archive/natural/2023/01/01/png/epic_1b_20230101010203.png" } ] }, null, 2) }; } }, { name: "mars-rover-photo", description: "NASA Mars Rover photograph", uriTemplate: "nasa://mars-rover/photo?rover={rover}&id={id}", generator: async (params) => { const rover = params["rover"] || "curiosity"; const id = params["id"] || "1"; return { name: `NASA Mars Rover photograph`, mimeType: "image/jpeg", text: `https://mars.nasa.gov/msl-raw-images/proj/msl/redops/odyssey/images/${rover}/edr/fcam/${id}.jpg`, blob: new Uint8Array() }; } }, { name: "nasa-image", description: "NASA Image and Video Library item", uriTemplate: "nasa://images/item?nasa_id={nasa_id}", generator: async (params) => { const nasa_id = params["nasa_id"] || "1"; return { name: `NASA Image and Video Library item (${nasa_id})`, mimeType: "image/jpeg", text: `https://images-assets.nasa.gov/image/${nasa_id}/metadata.json`, blob: new Uint8Array() }; } }, { name: "nasa-gibs-imagery", description: "NASA Global Imagery Browse Services (GIBS) satellite image", uriTemplate: "nasa://gibs/imagery?layer={layer}&date={date}", generator: async (params) => { const layer = params["layer"] || "MODIS_Terra_CorrectedReflectance_TrueColor"; const date = params["date"] || "2023-01-01"; return { name: `NASA Global Imagery Browse Services satellite image (${layer}, ${date})`, mimeType: "image/jpeg", text: `https://gibs.earthdata.nasa.gov/wmts/epsg4326/best/${layer}/${date}/default/default.jpg`, blob: new Uint8Array() }; } }, { name: "jpl-asteroid-data", description: "JPL Small-Body Database entry", uriTemplate: "jpl://sbdb?object={object}", generator: async (params) => { const object = params["object"] || "Ceres"; return { name: `JPL Small-Body Database entry (${object})`, mimeType: "application/json", text: `https://ssd.jpl.nasa.gov/api/astorb.api?format=json&number=1&orb=0&fullname=${encodeURIComponent(object)}`, blob: new Uint8Array() }; } }, { name: "nasa-earth-imagery", description: "NASA Earth Landsat satellite imagery", uriTemplate: "nasa://earth/imagery?lon={lon}&lat={lat}&date={date}", generator: async (params) => { const lon = params["lon"] || "-122.4783"; const lat = params["lat"] || "37.8199"; const date = params["date"] || ""; return { name: `Landsat imagery at coordinates (${lon}, ${lat})`, mimeType: "application/json", text: JSON.stringify({ coordinates: { lon, lat }, date: date || "latest", image_url: `https://api.nasa.gov/planetary/earth/imagery?lon=${lon}&lat=${lat}${date ? `&date=${date}` : ''}&api_key=DEMO_KEY` }, null, 2) }; } } ]; // Add some initial example resources function initializeResources() { // Add an example APOD resource addResource("nasa://apod/image?date=2023-01-01", { name: "Astronomy Picture of the Day (2023-01-01)", mimeType: "application/json", text: JSON.stringify({ date: "2023-01-01", title: "The Tail of a Christmas Comet", url: "https://apod.nasa.gov/apod/image/2301/CometZTF_Hernandez_1080.jpg", explanation: "Better known as Comet ZTF, this comet was captured on January 1, glowing in the predawn sky." }, null, 2) }); // Add an example EPIC resource addResource("nasa://epic/image?date=2023-01-01&collection=natural", { name: "EPIC Earth View (2023-01-01)", mimeType: "application/json", text: JSON.stringify({ date: "2023-01-01", collection: "natural", images: [ { identifier: "20230101010203", caption: "Earth from the DSCOVR satellite", image: "https://epic.gsfc.nasa.gov/archive/natural/2023/01/01/png/epic_1b_20230101010203.png" } ] }, null, 2) }); // Add an example NEO resource addResource("nasa://neo/list?date=2023-01-01", { name: "Near Earth Objects (2023-01-01)", mimeType: "application/json", text: JSON.stringify({ date: "2023-01-01", element_count: 2, near_earth_objects: { "2023-01-01": [ { id: "3542519", name: "2054 UR6", absolute_magnitude_h: 20.7, is_potentially_hazardous_asteroid: false }, { id: "3759690", name: "2016 WF9", absolute_magnitude_h: 19.3, is_potentially_hazardous_asteroid: true } ] } }, null, 2) }); } // Define our prompts const nasaPrompts = [ { name: "nasa/get-astronomy-picture", description: "Fetch NASA's Astronomy Picture of the Day with optional date selection", arguments: [ { name: "date", description: "The date of the APOD image to retrieve (YYYY-MM-DD format)", required: false }, { name: "count", description: "Number of random APODs to retrieve", required: false }, { name: "start_date", description: "Start date for date range search (YYYY-MM-DD)", required: false }, { name: "end_date", description: "End date for date range search (YYYY-MM-DD)", required: false }, { name: "thumbs", description: "Return URL of thumbnail for video content", required: false } ] }, { name: "nasa/browse-near-earth-objects", description: "Find near-Earth asteroids within a specific date range", arguments: [ { name: "start_date", description: "Start date for asteroid search (YYYY-MM-DD format)", required: true }, { name: "end_date", description: "End date for asteroid search (YYYY-MM-DD format)", required: true } ] }, { name: "nasa/view-epic-imagery", description: "Browse Earth Polychromatic Imaging Camera views of Earth", arguments: [ { name: "collection", description: "Image collection to view ('natural' or 'enhanced')", required: false }, { name: "date", description: "Date of images to retrieve (YYYY-MM-DD format)", required: false } ] } ]; const jplPrompts = [ { name: "jpl_query-small-body-database", description: "Search the Small-Body Database for asteroids and comets matching specific criteria", arguments: [ { name: "object_name", description: "Name or designation of the object (e.g., 'Ceres')", required: false }, { name: "spk_id", description: "SPK ID of the object", required: false }, { name: "object_type", description: "Type of object ('ast' for asteroid, 'com' for comet)", required: false } ] }, { name: "jpl_find-close-approaches", description: "Find close approaches of asteroids and comets to Earth or other planets", arguments: [ { name: "dist_max", description: "Maximum approach distance in lunar distances (LD)", required: false }, { name: "date_min", description: "Start date for search (YYYY-MM-DD)", required: false }, { name: "date_max", description: "End date for search (YYYY-MM-DD)", required: false }, { name: "body", description: "Body to find close approaches to (default: Earth)", required: false } ] }, { name: "jpl_get-fireball-data", description: "Retrieve data about fireballs detected by US Government sensors", arguments: [ { name: "date_min", description: "Start date for fireball data (YYYY-MM-DD)", required: false }, { name: "date_max", description: "End date for fireball data (YYYY-MM-DD)", required: false }, { name: "energy_min", description: "Minimum energy in kilotons of TNT", required: false } ] } ]; // Define the additional direct MCP prompts const mcpPrompts = [ { name: "apod-daily", description: "Get NASA's Astronomy Picture of the Day with a natural language prompt", arguments: [ { name: "date", description: "The date of the APOD image to retrieve (YYYY-MM-DD format)", required: false }, { name: "count", description: "Number of random APODs to retrieve", required: false }, { name: "start_date", description: "Start date for date range search (YYYY-MM-DD)", required: false }, { name: "end_date", description: "End date for date range search (YYYY-MM-DD)", required: false }, { name: "thumbs", description: "Return URL of thumbnail for video content", required: false } ] } ]; // Combine all prompts const allPrompts = [...nasaPrompts, ...jplPrompts, ...mcpPrompts]; async function startServer() { try { // Initialize resources initializeResources(); // Initialize MCP server with proper capabilities structure const server = new Server( { name: "NASA MCP Server", description: "Model Context Protocol server for NASA APIs", version: "1.0.13" }, { capabilities: { resources: { uriSchemes: ["nasa", "jpl"] }, tools: { callSchema: CallToolRequestSchema }, prompts: { list: allPrompts }, logging: {} } } ); // Store the server instance for global access serverInstance = server; // Register the tools/manifest method handler (important for MCP compliance) server.setRequestHandler( z.object({ method: z.literal("tools/manifest"), params: z.object({}).optional() }), async () => { // Return all tools we support in the MCP required format return { apis: [ { name: "nasa_apod", id: "nasa/apod", description: "Fetch NASA's Astronomy Picture of the Day" }, { name: "nasa_neo", id: "nasa/neo", description: "Information about asteroids and near-Earth objects" }, { name: "nasa_epic", id: "nasa/epic", description: "Earth Polychromatic Imaging Camera views of Earth" }, { name: "nasa_gibs", id: "nasa/gibs", description: "Global Imagery Browse Services satellite imagery" }, { name: "nasa_cmr", id: "nasa/cmr", description: "Search NASA's Common Metadata Repository for satellite data" }, { name: "nasa_firms", id: "nasa/firms", description: "Fire Information for Resource Management System" }, { name: "nasa_images", id: "nasa/images", description: "Search NASA's image and video library" }, { name: "nasa_exoplanet", id: "nasa/exoplanet", description: "Access NASA's Exoplanet Archive data" }, { name: "nasa_donki", id: "nasa/donki", description: "Space Weather Database Of Notifications, Knowledge, Information" }, { name: "nasa_mars_rover", id: "nasa/mars-rover", description: "Browse photos from NASA's Mars rovers" }, { name: "nasa_eonet", id: "nasa/eonet", description: "Earth Observatory Natural Event Tracker" }, { name: "nasa_power", id: "nasa/power", description: "Prediction of Worldwide Energy Resources" }, { name: "jpl_sbdb", id: "jpl/sbdb", description: "Small-Body DataBase (SBDB) - primarily orbital data on all known asteroids and comets" }, { name: "jpl_fireball", id: "jpl/fireball", description: "Fireball atmospheric impact data reported by US Government sensors" }, { name: "jpl_jd_cal", id: "jpl/jd_cal", description: "Julian Day number to/from calendar date/time converter" }, { name: "jpl_nhats", id: "jpl/nhats", description: "Human-accessible NEOs (Near-Earth Objects) data" }, { name: "jpl_cad", id: "jpl/cad", description: "Asteroid and comet close approaches to the planets in the past and future" }, { name: "jpl_sentry", id: "jpl/sentry", description: "JPL Sentry - NEO Earth impact risk assessment data" }, { name: "jpl_horizons", id: "jpl/horizons", description: "JPL Horizons - Solar system objects ephemeris data" }, { name: "jpl_scout", id: "jpl/scout", description: "NEOCP orbits, ephemerides, and impact risk data (Scout)" }, // { // name: "nasa_earth", // id: "nasa/earth", // description: "Earth - Landsat satellite imagery and data" // } ] }; } ); // Register the standard MCP methods // List Resources Handler server.setRequestHandler( z.object({ method: z.literal("resources/list"), params: z.object({}).optional() }), async () => { // Get concrete resources const concreteResources = Array.from(resources.entries()).map(([uri, resource]) => ({ uri: uri, mimeType: resource.mimeType, name: resource.name })); // Get resource templates const resourceTemplatesList = resourceTemplates.map(template => ({ uriTemplate: template.uriTemplate, name: template.name, description: template.description })); // Return combined list return { resources: [...concreteResources, ...resourceTemplatesList] }; } ); // Standard handler using the ListResourcesRequestSchema (may be an alternate way to call the same endpoint) server.setRequestHandler(ListResourcesRequestSchema, async () => { // Get concrete resources const concreteResources = Array.from(resources.entries()).map(([uri, resource]) => ({ uri: uri, mimeType: resource.mimeType, name: resource.name })); // Get resource templates - mapped to have uri property to match protocol requirements const resourceTemplatesList = resourceTemplates.map(template => ({ uri: template.uriTemplate, // Use uriTemplate as uri name: template.name, description: template.description })); // Return combined list return { resources: [...concreteResources, ...resourceTemplatesList] }; }); // Read Resource Handler server.setRequestHandler( z.object({ method: z.literal("resources/read"), params: z.object({ uri: z.string() }) }), async (request) => { const uri = request.params.uri.toString(); const resource = resources.get(uri); if (!resource) { throw new Error(`Resource not found: ${uri}`); } return { contents: [{ uri, mimeType: resource.mimeType, text: resource.text, blob: resource.blob }] }; } ); // Standard handler using the ReadResourceRequestSchema server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri.toString(); // Check if this is a concrete resource const resource = resources.get(uri); if (resource) { return { contents: [{ uri, mimeType: resource.mimeType, text: resource.text, blob: resource.blob }] }; } // If not found as a concrete resource, check if it matches any resource templates for (const template of resourceTemplates) { // Create a regex pattern from the template URI, replacing parameters with capture groups // This is a basic implementation - a more robust one would properly parse URI templates const pattern = template.uriTemplate.replace(/\{([^}]+)\}/g, '([^/]+)'); const regex = new RegExp(`^${pattern}$`); const match = uri.match(regex); if (match) { // Extract parameter values from the URI const paramNames = Array.from(template.uriTemplate.matchAll(/\{([^}]+)\}/g)).map(m => m[1]); const paramValues = match.slice(1); // Skip the first element (full match) const params: Record<string, string> = {}; paramNames.forEach((name, index) => { params[name] = paramValues[index]; }); // Call the parameterized generator function to get the resource try { const generatedResource = await template.generator(params); return { contents: [{ uri, mimeType: generatedResource.mimeType, text: generatedResource.text, blob: generatedResource.blob }] }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); throw new Error(`Failed to generate resource: ${errorMessage}`); } } } // If we get here, the resource was not found throw new Error(`Resource not found: ${uri}`); }); // List Tools Handler - Fixed the method name from "list-tools" to "tools/list" server.setRequestHandler( z.object({ method: z.literal("tools/list"), params: z.object({}).optional() }), async () => { // Return all tools we support in the MCP required format return { tools: [ { name: "nasa_apod", description: "Fetch NASA's Astronomy Picture of the Day", inputSchema: { type: "object", properties: { date: { type: "string", description: "The date of the APOD image to retrieve (YYYY-MM-DD)" }, count: { type: "number", description: "Count of random APODs to retrieve" }, start_date: { type: "string", description: "Start date for date range search (YYYY-MM-DD)" }, end_date: { type: "string", description: "End date for date range search (YYYY-MM-DD)" }, thumbs: { type: "boolean", description: "Return URL of thumbnail for video content" } }, required: ["date"] } }, { name: "nasa_neo", description: "Near Earth Object Web Service - information about asteroids", inputSchema: { type: "object", properties: { start_date: { type: "string", description: "Start date for asteroid search (YYYY-MM-DD)" }, end_date: { type: "string", description: "End date for asteroid search (YYYY-MM-DD)" }, asteroid_id: { type: "string", description: "ID of a specific asteroid" } }, required: ["start_date", "end_date"] } }, { name: "nasa_epic", description: "Earth Polychromatic Imaging Camera - views of Earth", inputSchema: { type: "object", properties: { collection: { type: "string", description: "Image collection (natural or enhanced)" }, date: { type: "string", description: "Date of the image (YYYY-MM-DD)" } } } }, { name: "nasa_gibs", description: "Global Imagery Browse Services - satellite imagery", inputSchema: { type: "object", properties: { layer: { type: "string", description: "Layer name (e.g., MODIS_Terra_CorrectedReflectance_TrueColor)" }, date: { type: "string", description: "Date of imagery (YYYY-MM-DD)" }, format: { type: "string", description: "Image format (png, jpg, jpeg)" }, resolution: { type: "number", description: "Resolution in pixels per degree" } }, required: ["layer", "date"] } }, { name: "nasa_cmr", description: "NASA Common Metadata Repository - search for NASA data collections", inputSchema: { type: "object", properties: { keyword: { type: "string", description: "Search keyword" }, search_type: { type: "string", description: "Search type (collections or granules)", enum: ["collections", "granules"], default: "collections" }, format: { type: "string", description: "Response format", enum: ["json", "umm_json", "atom", "echo10", "iso19115", "iso_smap", "kml"], default: "json" }, limit: { type: "number", description: "Maximum number of results to return" }, page: { type: "number", description: "Page number for pagination" }, sort_key: { type: "string", description: "Field to sort results by" } }, required: ["keyword","search_type", "format"] } }, { name: "nasa_firms", description: "NASA Fire Information for Resource Management System - fire data", inputSchema: { type: "object", properties: { latitude: { type: "number", description: "Latitude coordinate" }, longitude: { type: "number", description: "Longitude coordinate" }, days: { type: "number", description: "Number of days of data to retrieve" } }, required: ["latitude", "longitude"] } }, { name: "nasa_images", description: "NASA Image and Video Library - search NASA's media archive", inputSchema: { type: "object", properties: { q: { type: "string", description: "Search query" }, media_type: { type: "string", description: "Media type (image, video, audio)" }, year_start: { type: "string", description: "Start year for results" }, year_end: { type: "string", description: "End year for results" }, page: { type: "number", description: "Page number for pagination" } }, required: ["q"] } }, { name: "nasa_exoplanet", description: "NASA Exoplanet Archive - data about planets beyond our solar system", inputSchema: { type: "object", properties: { table: { type: "string", description: "Database table to query" }, select: { type: "string", description: "Columns to return" }, where: { type: "string", description: "Filter conditions" }, order: { type: "string", description: "Ordering of results" }, limit: { type: "number", description: "Maximum number of results" } }, required: ["table"] } }, { name: "nasa_donki", description: "Space Weather Database Of Notifications, Knowledge, Information", inputSchema: { type: "object", properties: { type: { type: "string", description: "Type of space weather event" }, startDate: { type: "string", description: "Start date (YYYY-MM-DD)" }, endDate: { type: "string", description: "End date (YYYY-MM-DD)" } }, required: ["type"] } }, { name: "nasa_mars_rover", description: "NASA Mars Rover Photos - images from Mars rovers", inputSchema: { type: "object", properties: { rover: { type: "string", description: "Name of the rover (curiosity, opportunity, spirit, perseverance)" }, sol: { type: "number", description: "Martian sol (day) of the photos" }, earth_date: { type: "string", description: "Earth date of the photos (YYYY-MM-DD)" }, camera: { type: "string", description: "Camera name" }, page: { type: "number", description: "Page number for pagination" } }, required: ["rover"] } }, { name: "nasa_eonet", description: "Earth Observatory Natural Event Tracker - natural events data", inputSchema: { type: "object", properties: { category: { type: "string", description: "Event category (wildfires, volcanoes, etc.)" }, days: { type: "number", description: "Number of days to look back" }, source: { type: "string", description: "Data source" }, status: { type: "string", description: "Event status (open, closed)" }, limit: { type: "number", description: "Maximum number of events to return" } } } }, { name: "nasa_power", description: "Prediction of Worldwide Energy Resources - meteorological data", inputSchema: { type: "object", properties: { parameters: { type: "string", description: "Comma-separated data parameters" }, community: { type: "string", description: "User community (RE, SB, AG, etc.)" }, longitude: { type: "number", description: "Longitude coordinate" }, latitude: { type: "number", description: "Latitude coordinate" }, start: { type: "string", description: "Start date (YYYYMMDD)" }, end: { type: "string", description: "End date (YYYYMMDD)" }, format: { type: "string", description: "Response format (json, csv, etc.)" } }, required: ["parameters", "community", "longitude", "latitude", "start", "end"] } }, { name: "jpl_sbdb", description: "Small-Body Database (SBDB) - asteroid and comet data", inputSchema: { type: "object", properties: { sstr: { type: "string", description: "Search string (e.g., asteroid name, number, or designation)" }, cad: { type: "boolean", description: "Include close approach data" } }, required: ["sstr"] } }, { name: "jpl_fireball", description: "Fireball data - atmospheric impact events", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Maximum number of results to return" }, "date_min": { type: "string", description: "Start date (YYYY-MM-DD)" }, "date_max": { type: "string", description: "End date (YYYY-MM-DD)" } } } }, { name: "jpl_jd_cal", description: "Julian Day number to/from calendar date/time converter", inputSchema: { type: "object", properties: { jd: { type: "string", description: "Julian date to convert to calendar date" }, cd: { type: "string", description: "Calendar date to convert to Julian date (YYYY-MM-DD or YYYY-MM-DDThh:mm:ss format)" } } } }, { name: "jpl_nhats", description: "Human-accessible NEOs (Near-Earth Objects) data", inputSchema: { type: "object", properties: { dv: { type: "number", description: "Minimum total delta-V (km/s). Values: 4-12, default: 12" }, dur: { type: "number", description: "Minimum total mission duration (days). Values: 60-450, default: 450" }, stay: { type: "number", description: "Minimum stay time (days). Values: 8, 16, 24, 32, default: 8" }, launch: { type: "string", description: "Launch window (year range). Values: 2020-2025, 2025-2030, 2030-2035, 2035-2040, 2040-2045, 2020-2045, default: 2020-2045" }, h: { type: "number", description: "Object's maximum absolute magnitude (mag). Values: 16-30" }, occ: { type: "number", description: "Object's maximum orbit condition code. Values: 0-8" }, des: { type: "string", description: "Object designation (e.g., '2000 SG344' or '433')" }, spk: { type: "string", description: "Object SPK-ID (e.g., '2000433')" }, plot: { type: "boolean", description: "Include base-64 encoded plot image" } } } }, { name: "jpl_cad", description: "Asteroid and comet close approaches to the planets in the past and future", inputSchema: { type: "object", properties: { "dist_max": { type: "string", description: "Maximum approach distance (e.g., 0.05, 10LD). Default: 0.05 au" }, "dist_min": { type: "string", description: "Minimum approach distance. Default: none" }, "date_min": { type: "string", description: "Start date for search (YYYY-MM-DD). Default: now" }, "date_max": { type: "string", description: "End date for search (YYYY-MM-DD). Default: +60 days" }, "body": { type: "string", description: "Body to find close approaches to (e.g., Earth, Mars, ALL). Default: Earth" }, "sort": { type: "string", description: "Sort field: date, dist, dist-min, v-inf, v-rel, h, object. Default: date" }, "des": { type: "string", description: "Object designation (e.g., '2000 SG344' or '433')" }, "spk": { type: "string", description: "Object SPK-ID (e.g., '2000433')" }, "neo": { type: "boolean", description: "Limit to NEOs. Default: true" }, "fullname": { type: "boolean", description: "Include full object name in result. Default: false" } } } }, { name: "jpl_sentry", description: "JPL Sentry - NEO Earth impact risk assessment data", inputSchema: { type: "object", properties: { limit: { type: "number", description: "Maximum number of results to return" }, "date_min": { type: "string", description: "Start date (YYYY-MM-DD)" }, "date_max": { type: "string", description: "End date (YYYY-MM-DD)" }, "des": { type: "string", description: "Object designation (e.g., '2011 AG5' or '29075')" }, "spk": { type: "string", description: "Object SPK-ID" }, "h_max": { type: "number", description: "Maximum absolute magnitude (size filter)" }, "ps_min": { type: "string", description: "Minimum Palermo Scale value" }, "ip_min": { type: "string", description: "Minimum impact probability" }, "removed": { type: "boolean", description: "Get objects removed from Sentry monitoring" }, "all": { type: "boolean", description: "Get all virtual impactors data" } } } }, { name: "jpl_horizons", description: "JPL Horizons - Solar system objects ephemeris data", inputSchema: { type: "object", properties: { format: { type: "string", description: "Response format (json, text)", enum: ["json", "text"] }, COMMAND: { type: "string", description: "Target object identifier (e.g., '499' for Mars, '1' for Ceres, 'C/2020 F3' for Comet NEOWISE)" }, OBJ_DATA: { type: "string", description: "Include object data", enum: ["YES", "NO"] }, MAKE_EPHEM: { type: "string", description: "Generate ephemeris", enum: ["YES", "NO"] }, EPHEM_TYPE: { type: "string", description: "Type of ephemeris (OBSERVER, VECTORS, ELEMENTS)", enum: ["OBSERVER", "VECTORS", "ELEMENTS"] }, CENTER: { type: "string", description: "Coordinate center (e.g., '500@399' for Earth)" }, START_TIME: { type: "string", description: "Start time for ephemeris (e.g., '2023-01-01')" }, STOP_TIME: { type: "string", description: "Stop time for ephemeris (e.g., '2023-01-02')" }, STEP_SIZE: { type: "string", description: "Step size for ephemeris points (e.g., '1d' for daily, '1h' for hourly)" }, QUANTITIES: { type: "string", description: "Observable quantities to include (e.g., 'A' for all, or '1,2,20,23' for specific ones)" }, OUT_UNITS: { type: "string", description: "Output units for vector tables", enum: ["KM-S", "AU-D", "KM-D"] } }, required: ["COMMAND"] } }, { name: "jpl_horizons_file", description: "JPL Horizons - Solar system objects ephemeris data (File Input)", inputSchema: { type: "object", properties: { format: { type: "string", description: "Response format (json, text)", enum: ["json", "text"] }, COMMAND: { type: "string", description: "Target object identifier (e.g., '499' for Mars, '1' for Ceres, 'C/2020 F3' for Comet NEOWISE)" }, OBJ_DATA: { type: "string", description: "Include object data", enum: ["YES", "NO"] }, MAKE_EPHEM: { type: "string", description: "Generate ephemeris", enum: ["YES", "NO"] }, EPHEM_TYPE: { type: "string", description: "Type of ephemeris (OBSERVER, VECTORS, ELEMENTS)", enum: ["OBSERVER", "VECTORS", "ELEMENTS"] }, CENTER: { type: "string", description: "Coordinate center (e.g., '500@399' for Earth)" }, START_TIME: { type: "string", description: "Start time for ephemeris (e.g., '2023-01-01')" }, STOP_TIME: { type: "string", description: "Stop time for ephemeris (e.g., '2023-01-02')" }, STEP_SIZE: { type: "string", description: "Step size for ephemeris points (e.g., '1d' for daily, '1h' for hourly)" }, QUANTITIES: { type: "string", description: "Observable quantities to include (e.g., 'A' for all, or '1,2,20,23' for specific ones)" }, OUT_UNITS: { type: "string", description: "Output units for vector tables", enum: ["KM-S", "AU-D", "KM-D"] } }, required: ["COMMAND"] } }, { name: "jpl_periodic_orbits", description: "JPL Three-Body Periodic Orbits Database", inputSchema: { type: "object", properties: { sys: { type: "string", description: "Three-body system (e.g., earth-moon, sun-earth)" }, family: { type: "string", description: "Orbit family name (e.g., halo, dro, lyapunov)" }, libr: { type: "integer", description: "Libration point (1-5, required for some families)" }, branch: { type: "string", description: "Branch within family (N/S, E/W, etc., required for some families)" }, periodmin: { type: "number", description: "Minimum period" }, periodmax: { type: "number", description: "Maximum period" }, periodunits: { type: "string", description: "Units for period (s, h, d, TU)", enum: ["s", "h", "d", "TU"] }, jacobimin: { type: "number", description: "Minimum Jacobi constant" }, jacobimax: { type: "number", description: "Maximum Jacobi constant" }, stabmin: { type: "number", description: "Minimum stability index" }, stabmax: { type: "number", description: "Maximum stability index" } }, required: ["sys", "family"] } }, { name: "nasa_osdr_files", description: "NASA OSDR - Get data files for an OSD study", inputSchema: { type: "object", properties: { accession_number: { type: "string", description: "OSD study accession number (e.g., '87')" } }, required: ["accession_number"] } }, { name: "jpl_scout", description: "Scout - NEOCP orbits, ephemerides, and impact risk data", inputSchema: { type: "object", properties: { tdes: { type: "string", description: "Object temporary designation (e.g., P21Eolo)" }, orbit_id: { type: "string", description: "Scout internal orbit ID" }, limit: { type: "number", description: "Limit number of results" }, file: { type: "string", description: "Type of data file to return (summary, ephem, obs, crit, all)", enum: ["summary", "ephem", "obs", "crit", "all"] }, plot: { type: "boolean", description: "Include plots in the response" }, summary: { type: "boolean", description: "Include summary data in the response" } }, // No required fields explicitly listed, as API might default to list } }, // { // name: "nasa_earth", // description: "Earth - Landsat satellite imagery", // inputSchema: { // type: "object", // properties: { // lon: { // type: "number", // description: "Longitude of the imagery location" // }, // lat: { // type: "number", // description: "Latitude of the imagery location" // }, // date: { // type: "string", // description: "Date of imagery (YYYY-MM-DD format). If not specified, most recent imagery is used" // }, // dim: { // type: "number", // description: "Width and height of image in degrees (0.025 to 0.5)" // }, // cloud_score: { // type: "boolean", // description: "Calculate the percentage of the image covered by clouds" // } // }, // required: ["lon", "lat"] // } // } ] }; } ); // Add prompts/list endpoint server.setRequestHandler( z.object({ method: z.literal("prompts/list"), params: z.object({}).optional() }), async () => { return { prompts: allPrompts }; } ); // Add direct handlers for each NASA API // APOD Handler server.setRequestHandler( z.object({ method: z.literal("nasa/apod"), params: z.object({ date: z.string().optional(), start_date: z.string().optional(), end_date: z.string().optional(), count: z.number().optional(), thumbs: z.boolean().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/apod", request.params || {}); } ); // NEO Handler server.setRequestHandler( z.object({ method: z.literal("nasa/neo"), params: z.object({ start_date: z.string().optional(), end_date: z.string().optional(), asteroid_id: z.string().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/neo", request.params || {}); } ); // EPIC Handler server.setRequestHandler( z.object({ method: z.literal("nasa/epic"), params: z.object({ collection: z.string().optional(), date: z.string().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/epic", request.params || {}); } ); // Mars Rover Handler server.setRequestHandler( z.object({ method: z.literal("nasa/mars-rover"), params: z.object({ rover: z.enum(['curiosity', 'opportunity', 'perseverance', 'spirit']), sol: z.number().int().nonnegative().optional(), earth_date: z.string().optional(), camera: z.string().optional(), page: z.number().int().positive().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/mars-rover", request.params || {}); } ); // GIBS Handler server.setRequestHandler( z.object({ method: z.literal("nasa/gibs"), params: z.object({ layer: z.string(), date: z.string(), format: z.enum(['png', 'jpg', 'jpeg']).optional(), resolution: z.number().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/gibs", request.params || {}); } ); // CMR Handler server.setRequestHandler( z.object({ method: z.literal("nasa/cmr"), params: z.object({ keyword: z.string().optional(), search_type: z.enum(['collections', 'granules']).optional(), format: z.enum(['json', 'umm_json', 'atom', 'echo10', 'iso19115', 'iso_smap', 'kml']).optional(), limit: z.number().optional(), page: z.number().optional(), sort_key: z.string().optional() }).passthrough().optional() }), async (request) => { return await handleToolCall("nasa/cmr", request.params || {}); } ); // FIRMS Handler server.setRequestHandler( z.object({ method: z.literal("nasa/firms"), params: z.object({ days: z.number().optional(), latitude: z.number().optional(), longitude: z.number().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/firms", request.params || {}); } ); // Images Handler server.setRequestHandler( z.object({ method: z.literal("nasa/images"), params: z.object({ q: z.string(), page: z.number().optional(), media_type: z.string().optional(), year_start: z.string().optional(), year_end: z.string().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/images", request.params || {}); } ); // Exoplanet Handler server.setRequestHandler( z.object({ method: z.literal("nasa/exoplanet"), params: z.object({ table: z.string().optional(), select: z.string().optional(), where: z.string().optional(), order: z.string().optional(), limit: z.number().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/exoplanet", request.params || {}); } ); // DONKI Handler server.setRequestHandler( z.object({ method: z.literal("nasa/donki"), params: z.object({ type: z.string(), startDate: z.string().optional(), endDate: z.string().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/donki", request.params || {}); } ); // EONET Handler server.setRequestHandler( z.object({ method: z.literal("nasa/eonet"), params: z.object({ category: z.string().optional(), days: z.number().optional(), source: z.string().optional(), status: z.string().optional(), limit: z.number().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/eonet", request.params || {}); } ); // POWER Handler server.setRequestHandler( z.object({ method: z.literal("nasa/power"), params: z.object({ community: z.string(), parameters: z.union([z.string(), z.array(z.string())]), latitude: z.number(), longitude: z.number(), start: z.string(), end: z.string(), format: z.string().optional() }).optional() }), async (request) => { return await handleToolCall("nasa/power", request.params || {}); } ); // Earth Handler - COMMENTED OUT: NASA Earth API is archived and replaced with GIBS // server.setRequestHandler( // z.object({ // method: z.literal("nasa/earth"), // params: z.object({ // lon: z.number().or(z.string().regex(/^-?\d+(\.\d+)?$/).transform(Number)), // lat: z.number().or(z.string().regex(/^-?\d+(\.\d+)?$/).transform(Number)), // date: z.string().optional(), // dim: z.number().optional(), // cloud_score: z.boolean().optional() // }).optional() // }), // async (request) => { // return await handleToolCall("nasa/earth", request.params || {}); // } // ); // Scout Handler server.setRequestHandler( z.object({ method: z.literal("jpl/scout"), params: z.object({ tdes: z.string().optional(), orbit_id: z.string().optional(), limit: z.number().int().positive().optional(), file: z.enum(['summary', 'ephem', 'obs', 'crit', 'all']).optional(), plot: z.boolean().optional(), summary: z.boolean().optional() }).optional() }), async (request) => { return await handleToolCall("jpl/scout", request.params || {}); } ); // Add CallToolRequestSchema handler (required for MCP compliance) server.setRequestHandler(CallToolRequestSchema, async (request) => { const toolName = request.params.name; const args = request.params.arguments ?? {}; // Call the tool handler function return await handleToolCall(toolName, args); }); // Add handlers for prompts server.setRequestHandler( z.object({ method: z.literal("prompts/execute"), params: z.object({ name: z.string(), arguments: z.record(z.string(), z.any()).optional() }) }), async (request) => { return await handlePrompt(request.params.name, request.params.arguments || {}); } ); // Set up all handlers from the handler setup module setupHandlers(server); // Register the resource templates list handler server.setRequestHandler( z.object({ method: z.literal("resources/templates/list"), params: z.object({}).optional() }), async () => { return { resourceTemplates: resourceTemplates }; } ); // Add a new MCP-compatible prompt for Astronomy Picture of the Day server.setRequestHandler( z.object({ method: z.literal("prompts/get"), params: z.object({ name: z.literal("apod-daily"), arguments: apodParamsSchema.partial().optional() }) }), async (request) => { const params = request.params.arguments || {}; return { messages: [{ role: "user", content: { type: "text", text: `Show me the NASA Astronomy Picture of the Day${params.date ? ` for ${params.date}` : ''}${params.count ? ` (${params.count} random images)` : ''}${params.start_date && params.end_date ? ` from ${params.start_date} to ${params.end_date}` : ''}.` } }] }; } ); // Use stdio transport for this main server const stdioTransport = new StdioServerTransport(); await server.connect(stdioTransport); serverInstance?.sendLoggingMessage({ level: "info", data: "Server started with stdio transport", }); } catch (error) { console.error("Error starting server:", error); process.exit(1); } } // Add a function to handle prompts async function handlePrompt(promptName: string, args: Record<string, any>) { try { serverInstance?.sendLoggingMessage({ level: "info", data: `Handling prompt: ${promptName} with args: ${JSON.stringify(args)}`, }); // Map the prompt name to the appropriate tool call const promptToToolMap: Record<string, string> = { "nasa/get-astronomy-picture": "nasa/apod", "nasa/browse-near-earth-objects": "nasa/neo", "nasa/view-epic-imagery": "nasa/epic", "jpl/query-small-body-database": "jpl/sbdb", "jpl/find-close-approaches": "jpl/cad", "jpl/get-fireball-data": "jpl/fireball" }; const toolName = promptToToolMap[promptName]; if (!toolName) { throw new Error(`Unknown prompt: ${promptName}`); } // Validate the arguments based on the prompt definition const prompt = allPrompts.find(p => p.name === promptName); if (!prompt) { throw new Error(`Prompt definition not found: ${promptName}`); } // Check required arguments const missingArgs = prompt.arguments ?.filter(arg => arg.required && !args[arg.name]) .map(arg => arg.name); if (missingArgs && missingArgs.length > 0) { throw new Error(`Missing required arguments: ${missingArgs.join(', ')}`); } // Execute the corresponding tool return await handleToolCall(toolName, args); } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error executing prompt '${promptName}': ${errorMessage}` }], isError: true }; } } // Add a function to handle tool calls async function handleToolCall(toolName: string, args: Record<string, any>) { try { // Convert toolName format (e.g., nasa_neo -> nasa/neo) const internalToolId = toolName.replace('_', '/'); serverInstance?.sendLoggingMessage({ level: "info", data: `Handling tool call for: ${toolName} (Internal ID: ${internalToolId}) with args: ${JSON.stringify(args)}`, }); // Use internalToolId for routing logic if (internalToolId.startsWith("nasa/")) { // Extract the NASA API endpoint name const endpoint = internalToolId.split("/")[1]; const normalizedEndpoint = endpoint.replace(/-/g, '_'); // Normalize dashes for handler lookup if needed // Log endpoint for debugging serverInstance?.sendLoggingMessage({ level: "info", data: `NASA Endpoint: ${endpoint} (Normalized: ${normalizedEndpoint})`, }); try { // Dynamic import for NASA handlers using the original slash format path const handlerModule = await import(`./handlers/nasa/${endpoint}.js`); serverInstance?.sendLoggingMessage({ level: "info", data: `Successfully imported handler module for: ./handlers/nasa/${endpoint}.js`, }); // Try different potential handler function names const handlerFunctionName = `nasa${endpoint.charAt(0).toUpperCase() + endpoint.slice(1).replace(/-/g, '_')}Handler`; // e.g. nasaMars_roverHandler const simpleHandlerName = `${endpoint.replace(/-/g, '_')}Handler`; // e.g. mars_roverHandler const handlerFunction = handlerModule.default || handlerModule[handlerFunctionName] || handlerModule[simpleHandlerName]; if (typeof handlerFunction === 'function') { serverInstance?.sendLoggingMessage({ level: "info", data: `Executing handler function for ${endpoint}`, }); return await handlerFunction(args); } else { serverInstance?.sendLoggingMessage({ level: "info", data: `No handler function found in module: ${JSON.stringify(Object.keys(handlerModule))}`, }); throw new Error(`No handler function found for NASA endpoint: ${normalizedEndpoint}`); } } catch (importError) { throw new Error(`Failed to import handler for NASA endpoint: ${normalizedEndpoint}. Error: ${importError instanceof Error ? importError.message : String(importError)}`); } } else if (internalToolId.startsWith("jpl/")) { // Extract the JPL API endpoint name const endpoint = internalToolId.split("/")[1]; serverInstance?.sendLoggingMessage({ level: "info", data: `JPL Endpoint: ${endpoint}`, }); try { // Dynamic import for JPL handlers using the original slash format path serverInstance?.sendLoggingMessage({ level: "info", data: `Importing handler module: ./handlers/jpl/${endpoint}.js`, }); const handlerModule = await import(`./handlers/jpl/${endpoint}.js`); // Try to find the handler function in various export formats const handlerFunction = handlerModule.default || handlerModule[`jpl${endpoint.charAt(0).toUpperCase() + endpoint.slice(1)}Handler`] || handlerModule[`${endpoint}Handler`]; if (typeof handlerFunction === 'function') { return await handlerFunction(args); } else { throw new Error(`Handler for ${endpoint} not found in module`); } } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error executing JPL tool '${toolName}': ${errorMessage}` }], isError: true }; } } return { content: [{ type: "text", text: `Unknown tool: ${toolName}` }], isError: true }; } catch (error: unknown) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [{ type: "text", text: `Error executing tool '${toolName}': ${errorMessage}` }], isError: true }; } } // Utility function to add a resource (can be used by handlers to store results) export function addResource(uri: string, resource: Resource) { addResourceCore(uri, resource); // Send notification about resource change if server is initialized if (serverInstance) { serverInstance.notification({ method: "notifications/resources/list_changed" }); } } // Start the server startServer().catch(error => { console.error("Error starting NASA MCP Server:", error); process.exit(1); }); // Handle stdin close for graceful shutdown process.stdin.on("close", () => { serverInstance?.sendLoggingMessage({ level: "info", data: "NASA MCP Server shutting down...", }); if (serverInstance) { serverInstance.close(); } setTimeout(() => { process.exit(0); }, 100); }); // Helper function to register MCP tools export function registerMcpTools() { try { // Define a type for MCP tool handler functions type McpToolHandler = (args: Record<string, any>) => Promise<any>; // Define a typesafe way to assign to global function registerGlobalTool(name: string, handler: McpToolHandler): void { (global as any)[name] = handler; } // Register each NASA API as an MCP tool registerGlobalTool('mcp__nasaapod', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA APOD called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/apod', args); }); registerGlobalTool('mcp__nasaneo', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA NEO called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/neo', args); }); registerGlobalTool('mcp__nasaepic', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA EPIC called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/epic', args); }); registerGlobalTool('mcp__nasagibs', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA GIBS called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/gibs', args); }); registerGlobalTool('mcp__nasacmr', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA CMR called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/cmr', args); }); registerGlobalTool('mcp__nasafirms', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA FIRMS called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/firms', args); }); registerGlobalTool('mcp__nasaimages', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA Images called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/images', args); }); registerGlobalTool('mcp__nasaexoplanet', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA Exoplanet called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/exoplanet', args); }); registerGlobalTool('mcp__nasadonki', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA DONKI called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/donki', args); }); registerGlobalTool('mcp__nasamars_rover', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA Mars Rover called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/mars-rover', args); }); registerGlobalTool('mcp__nasaeonet', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA EONET called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/eonet', args); }); registerGlobalTool('mcp__nasapower', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP NASA POWER called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('nasa/power', args); }); // Register JPL tools registerGlobalTool('mcp__jplsbdb', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL SBDB called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/sbdb', args); }); registerGlobalTool('mcp__jplfireball', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL Fireball called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/fireball', args); }); registerGlobalTool('mcp__jpljd_cal', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL JD Calendar called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/jd_cal', args); }); registerGlobalTool('mcp__jplnhats', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL NHATS called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/nhats', args); }); registerGlobalTool('mcp__jplcad', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL CAD called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/cad', args); }); registerGlobalTool('mcp__jplsentry', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL Sentry called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/sentry', args); }); registerGlobalTool('mcp__jplhorizons', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL Horizons called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/horizons', args); }); // Register Horizons File Tool registerGlobalTool('mcp__jplhorizons_file', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL Horizons File called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/horizons_file', args); }); // Register Periodic Orbits Tool registerGlobalTool('mcp__jplperiodic_orbits', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL Periodic Orbits called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/periodic_orbits', args); }); // Register Earth tool - COMMENTED OUT: NASA Earth API is archived and replaced with GIBS // registerGlobalTool('mcp__nasaearth', async (args: Record<string, any>) => { // serverInstance?.sendLoggingMessage({ // level: "info", // data: `MCP NASA Earth called with args: ${JSON.stringify(args)}`, // }); // return await handleToolCall('nasa/earth', args); // }); // Register Scout tool registerGlobalTool('mcp__jplscout', async (args: Record<string, any>) => { serverInstance?.sendLoggingMessage({ level: "info", data: `MCP JPL Scout called with args: ${JSON.stringify(args)}`, }); return await handleToolCall('jpl/scout', args); }); serverInstance?.sendLoggingMessage({ level: "info", data: "All NASA MCP tools registered", }); } catch (error) { console.error('Error registering MCP tools:', error); } } // Call the registration function registerMcpTools();

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/ProgramComputer/NASA-MCP-server'

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