jpl_horizons_file
Retrieve ephemeris data for solar system objects using JPL Horizons data. Specify target objects, time ranges, and observation parameters to generate position and motion information.
Instructions
JPL Horizons - Solar system objects ephemeris data (File Input)
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| format | No | Response format (json, text) | |
| COMMAND | Yes | Target object identifier (e.g., '499' for Mars, '1' for Ceres, 'C/2020 F3' for Comet NEOWISE) | |
| OBJ_DATA | No | Include object data | |
| MAKE_EPHEM | No | Generate ephemeris | |
| EPHEM_TYPE | No | Type of ephemeris (OBSERVER, VECTORS, ELEMENTS) | |
| CENTER | No | Coordinate center (e.g., '500@399' for Earth) | |
| START_TIME | No | Start time for ephemeris (e.g., '2023-01-01') | |
| STOP_TIME | No | Stop time for ephemeris (e.g., '2023-01-02') | |
| STEP_SIZE | No | Step size for ephemeris points (e.g., '1d' for daily, '1h' for hourly) | |
| QUANTITIES | No | Observable quantities to include (e.g., 'A' for all, or '1,2,20,23' for specific ones) | |
| OUT_UNITS | No | Output units for vector tables |
Implementation Reference
- src/handlers/jpl/horizons_file.ts:14-100 (handler)The core handler function `horizonsFileHandler` that executes the `jpl_horizons_file` tool. It formats input parameters into a Horizons-compatible text file, submits via multipart POST to the JPL API, processes the response, adds it as a resource, and returns formatted output.export async function horizonsFileHandler(args: Record<string, any>) { try { // Base URL for the Horizons File API (POST) const baseUrl = 'https://ssd.jpl.nasa.gov/api/horizons_file.api'; // Format arguments into the key='value' text format for the input file // DO NOT include format here, it's a separate form field. const formattedArgs = { ...args }; delete formattedArgs.format; // Remove format if present let fileContent = '!$$SOF\n'; // Add !SOF marker for (const [key, value] of Object.entries(formattedArgs)) { let formattedValue: string | number; const upperKey = key.toUpperCase(); // Leave numbers unquoted if (typeof value === 'number') { formattedValue = value; } // Quote ALL other values (strings, including YES/NO) else { formattedValue = `'${String(value).replace(/'/g, "\'")}'`; } fileContent += `${upperKey}=${formattedValue}\n`; } fileContent += '!$$EOF\n'; // Correct !EOF marker // Create FormData payload const form = new FormData(); // Add format as a separate field form.append('format', args.format || 'json'); // Add the file content under the 'input' field name form.append('input', fileContent, { filename: 'horizons_input.txt', // Required filename, content doesn't matter contentType: 'text/plain', }); // Make the API request using POST with multipart/form-data const response = await axios.post(baseUrl, form, { headers: { ...form.getHeaders(), // Important for correct boundary }, }); const data = response.data; // Assume response is JSON based on 'format=json' // Create a resource URI that represents this query (similar to GET handler) let resourceUri = 'jpl://horizons-file'; // Distinguish from GET let resourceName = 'JPL Horizons file-based ephemeris data'; if (args.COMMAND) { resourceUri += `/object/${encodeURIComponent(args.COMMAND)}`; resourceName = `${args.COMMAND} ephemeris data (file input)`; if (args.START_TIME && args.STOP_TIME) { resourceName += ` (${args.START_TIME} to ${args.STOP_TIME})`; } } // Add response to resources addResource(resourceUri, { name: resourceName, mimeType: "application/json", // Assuming JSON response text: JSON.stringify(data, null, 2) }); // Format the response return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (error: any) { let errorMessage = `Error accessing JPL Horizons File API: ${error.message}`; if (error.response) { // Include more detail from the API response if available errorMessage += `\nStatus: ${error.response.status}\nData: ${JSON.stringify(error.response.data)}`; } return { content: [{ type: "text", text: errorMessage }], isError: true }; } }
- src/index.ts:1290-1347 (schema)Input schema and metadata definition for the `jpl_horizons_file` tool provided in the `tools/list` MCP endpoint response.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"] } },
- src/index.ts:1896-1932 (registration)Dynamic registration and dispatch logic in `handleToolCall` for JPL tools. Dynamically imports `./handlers/jpl/horizons_file.js` (endpoint='horizons_file') and executes its default export handler function.} 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 }; } }
- src/index.ts:2151-2157 (registration)Global MCP tool registration for `mcp__jplhorizons_file` that routes calls to the `jpl/horizons_file` handler via `handleToolCall`.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); });