index.ts•75.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();