Skip to main content
Glama

get_mars_rover_photos

Retrieve Mars rover photos by specifying rover name, sol (Martian day), or earth date. Filter by camera and paginate results for Curiosity, Opportunity, or Spirit rovers using NASA APIs.

Instructions

Get photos from a Mars rover (Curiosity, Opportunity, Spirit). Specify either sol (Martian day) or earth_date (YYYY-MM-DD), but not both.

Args: rover_name: Name of the rover (curiosity, opportunity, spirit). sol: Martian sol (day number, starting from landing). Use if not using earth_date. earth_date: Earth date in YYYY-MM-DD format. Use if not using sol. camera: Filter by camera abbreviation (e.g., FHAZ, RHAZ, MAST, NAVCAM, PANCAM). See documentation for full list per rover. page: Page number for results (25 photos per page).

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
cameraNo
earth_dateNo
pageNo
rover_nameYes
solNo

Implementation Reference

  • The core handler function decorated with @mcp.tool(), implementing the logic to query the NASA Mars Rover Photos API, validate inputs using ROVER_CAMERAS, handle responses, and format output.
    @mcp.tool() async def get_mars_rover_photos(rover_name: str, sol: int = None, earth_date: str = None, camera: str = None, page: int = 1) -> str: """Get photos from a Mars rover (Curiosity, Opportunity, Spirit). Specify either sol (Martian day) or earth_date (YYYY-MM-DD), but not both. Args: rover_name: Name of the rover (curiosity, opportunity, spirit). sol: Martian sol (day number, starting from landing). Use if not using earth_date. earth_date: Earth date in YYYY-MM-DD format. Use if not using sol. camera: Filter by camera abbreviation (e.g., FHAZ, RHAZ, MAST, NAVCAM, PANCAM). See documentation for full list per rover. page: Page number for results (25 photos per page). """ rover_name = rover_name.lower() if rover_name not in ROVER_CAMERAS: return f"Invalid rover name. Available rovers: {', '.join(ROVER_CAMERAS.keys())}" if sol is not None and earth_date is not None: return "Error: Specify either sol or earth_date, but not both." if sol is None and earth_date is None: return "Error: Specify either sol or earth_date." params = {"page": page} if sol is not None: params["sol"] = sol if earth_date is not None: params["earth_date"] = earth_date if camera: camera = camera.upper() if camera not in ROVER_CAMERAS[rover_name]: return f"Invalid camera '{camera}' for rover '{rover_name}'. Available cameras: {', '.join(ROVER_CAMERAS[rover_name])}" params["camera"] = camera url = f"{NASA_API_BASE}/mars-photos/api/v1/rovers/{rover_name}/photos" data = await make_nasa_request(url, params) if not data: return f"Could not retrieve Mars Rover photos for {rover_name} due to a connection error." # Check for error response (must be a dictionary) if isinstance(data, dict) and "error" in data: return f"API Error: {data.get('error')} - Details: {data.get('details', 'N/A')}" if isinstance(data, dict) and data.get("binary_content"): return f"Received unexpected binary content from Mars Rover Photos API. URL: {data.get('url')}" # The response should be a dictionary containing a 'photos' list if not isinstance(data, dict) or "photos" not in data: logger.error(f"Unexpected response format from Mars Rover Photos API: {data}") return "Received unexpected data format from Mars Rover Photos API." try: photos = data.get("photos", []) if not photos: query_details = f"sol={sol}" if sol is not None else f"earth_date={earth_date}" if camera: query_details += f", camera={camera}" return f"No photos found for rover '{rover_name}' with criteria: {query_details}, page {page}." result = [f"Mars Rover Photos for '{rover_name}' (Page {page}): {len(photos)} found on this page."] display_limit = 10 # Limit display per page in the result string count = 0 for photo in photos: if count >= display_limit: result.append(f"n... and {len(photos) - display_limit} more photos on this page.") break result.append(f"nPhoto ID: {photo.get('id', 'Unknown')}") result.append(f"Sol: {photo.get('sol', 'Unknown')}") result.append(f"Earth Date: {photo.get('earth_date', 'Unknown')}") result.append(f"Camera: {photo.get('camera', {}).get('name', 'Unknown')} ({photo.get('camera', {}).get('full_name', 'N/A')})") result.append(f"Image URL: {photo.get('img_src', 'Not available')}") result.append(f"Rover: {photo.get('rover', {}).get('name', 'Unknown')} (Status: {photo.get('rover', {}).get('status', 'N/A')}) ") result.append("-" * 40) count += 1 return "n".join(result) except Exception as e: logger.error(f"Error processing Mars Rover Photos data: {str(e)}") return f"Error processing Mars Rover Photos data: {str(e)}"
  • Supporting constant dictionary used for validating rover names and available cameras in the get_mars_rover_photos tool.
    "curiosity": ["FHAZ", "RHAZ", "MAST", "CHEMCAM", "MAHLI", "MARDI", "NAVCAM"], "opportunity": ["FHAZ", "RHAZ", "NAVCAM", "PANCAM", "MINITES"], "spirit": ["FHAZ", "RHAZ", "NAVCAM", "PANCAM", "MINITES"] }
  • General helper function used by the tool (and others) to make authenticated HTTP requests to NASA APIs, handling JSON, images, and errors.
    async def make_nasa_request(url: str, params: dict = None) -> Union[dict[str, Any], List[Any], None]: """Make a request to the NASA API with proper error handling. Handles both JSON and binary (image) responses. """ logger.info(f"Making request to: {url} with params: {params}") if params is None: params = {} # Ensure API key is included in parameters if "api_key" not in params: params["api_key"] = API_KEY async with httpx.AsyncClient() as client: try: response = await client.get(url, params=params, timeout=30.0, follow_redirects=True) response.raise_for_status() # Raise an exception for bad status codes (4xx or 5xx) content_type = response.headers.get("Content-Type", "").lower() if "application/json" in content_type: try: return response.json() except json.JSONDecodeError as json_err: logger.error(f"JSON decode error for URL {response.url}: {json_err}") logger.error(f"Response text: {response.text[:500]}") # Log beginning of text return {"error": "Failed to decode JSON response", "details": str(json_err)} elif content_type.startswith("image/"): logger.info(f"Received binary image content ({content_type}) from {response.url}") # Return a dictionary indicating binary content was received return { "binary_content": True, "content_type": content_type, "url": str(response.url) # Return the final URL after redirects } else: # Handle other unexpected content types logger.warning(f"Unexpected content type '{content_type}' received from {response.url}") return {"error": f"Unexpected content type: {content_type}", "content": response.text[:500]} except httpx.HTTPStatusError as http_err: logger.error(f"HTTP error occurred: {http_err} - {http_err.response.status_code} for URL {http_err.request.url}") try: # Try to get more details from response body if possible error_details = http_err.response.json() except Exception: error_details = http_err.response.text[:500] return {"error": f"HTTP error: {http_err.response.status_code}", "details": error_details} except httpx.RequestError as req_err: logger.error(f"Request error occurred: {req_err} for URL {req_err.request.url}") return {"error": "Request failed", "details": str(req_err)} except Exception as e: logger.error(f"An unexpected error occurred: {str(e)}") return {"error": "An unexpected error occurred", "details": str(e)}
  • The @mcp.tool() decorator registers the get_mars_rover_photos function as an MCP tool.
    @mcp.tool()

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/AnCode666/nasa-mcp'

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