Skip to main content
Glama

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
NameRequiredDescriptionDefault
formatNoResponse format (json, text)
COMMANDYesTarget object identifier (e.g., '499' for Mars, '1' for Ceres, 'C/2020 F3' for Comet NEOWISE)
OBJ_DATANoInclude object data
MAKE_EPHEMNoGenerate ephemeris
EPHEM_TYPENoType of ephemeris (OBSERVER, VECTORS, ELEMENTS)
CENTERNoCoordinate center (e.g., '500@399' for Earth)
START_TIMENoStart time for ephemeris (e.g., '2023-01-01')
STOP_TIMENoStop time for ephemeris (e.g., '2023-01-02')
STEP_SIZENoStep size for ephemeris points (e.g., '1d' for daily, '1h' for hourly)
QUANTITIESNoObservable quantities to include (e.g., 'A' for all, or '1,2,20,23' for specific ones)
OUT_UNITSNoOutput units for vector tables

Implementation Reference

  • 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
        };
      }
    }
  • 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);
    });
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It mentions 'File Input' but doesn't clarify what that entails—whether it generates downloadable files, requires file uploads, or has specific output handling. There's no information on rate limits, authentication needs, error conditions, or what the tool returns (since no output schema exists). The description is too minimal for a tool with 11 parameters.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient phrase that gets straight to the point without unnecessary words. It's appropriately sized for a tool name but could be more informative. However, it's front-loaded and wastes no space on repetition or fluff.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the complexity (11 parameters, no annotations, no output schema), the description is incomplete. It doesn't explain the tool's behavior, output format, or how it differs from similar tools. For a data retrieval tool with many configuration options, more context is needed to help an agent use it effectively, especially without annotations or output schema to fill gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 100%, so the schema already documents all parameters thoroughly with descriptions and enums. The description adds no additional parameter semantics beyond what's in the schema—it doesn't explain relationships between parameters, default behaviors, or practical examples. With high schema coverage, the baseline is 3, but the description doesn't compensate with extra insights.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose3/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states the tool provides 'Solar system objects ephemeris data (File Input)' from JPL Horizons, which indicates it retrieves positional/observational data for celestial bodies. However, it's vague about what 'File Input' means specifically and doesn't differentiate from sibling tools like 'jpl_horizons' (which likely provides similar functionality). The description lacks a clear verb and specific resource scope.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives. With multiple JPL Horizons-related siblings (e.g., 'jpl_horizons', 'jpl_cad', 'jpl_sbdb'), the description doesn't explain what makes this tool unique or when it's the appropriate choice. There's no mention of prerequisites, constraints, or typical use cases.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

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