Tutorial: Building a NASA APOD Server

In this tutorial, we will build a simple Python-based MCP server that connects to NASA's API to fetch the Astronomy Picture of the Day (APOD). This project is a great introduction to building MCP servers and takes about 10 minutes to complete.

Prerequisites

  • Python 3.10+
  • npx (comes with Node.js)
  • uv (a fast Python package installer): pip install uv
  • An API key from api.nasa.gov

Step 1: Set Up Your Environment

First, let's create a new project directory and set up a virtual environment.

# Create project and enter directory
mkdir nasa-mcp-server
cd nasa-mcp-server

# Initialize a uv environment
uv init

# Create and activate the virtual environment
uv venv
source .venv/bin/activate

# Install dependencies
uv add "mcp[cli]" httpx

# Create the server file
touch nasa.py

Step 2: Write the MCP Server Code

Open the nasa.py file you just created and add the following code. Be sure to replace <NASA_API_TOKEN> with your actual key from api.nasa.gov.

from typing import Any
import httpx
from mcp.server.fastmcp import FastMCP

# Initialize FastMCP server
mcp = FastMCP("nasa")

# Constants
NASA_API_BASE = "https://api.nasa.gov"
# Replace with your actual NASA API token
NASA_API_KEY = "<NASA_API_TOKEN>"

async def make_nasa_request(url: str) -> dict[str, Any] | None:
    """Make a request to the NASA API with proper error handling."""
    params = {"api_key": NASA_API_KEY}
    async with httpx.AsyncClient() as client:
        try:
            response = await client.get(url, params=params, timeout=30.0)
            response.raise_for_status()
            return response.json()
        except Exception:
            return None

@mcp.tool()
async def get_astronomy_picture_of_day(date: str = None) -> str:
    """Get NASA's Astronomy Picture of the Day (APOD).

    Args:
        date: Optional date in YYYY-MM-DD format. If not provided, returns today's image.
    """
    url = f"{NASA_API_BASE}/planetary/apod"

    if date:
        url += f"?date={date}"

    data = await make_nasa_request(url)

    if not data:
        return "Unable to fetch APOD data from NASA API."

    image_url = data.get('hdurl') or data.get('url')

    result = f"""
🌌 NASA Astronomy Picture of the Day

📅 Date: {data.get('date', 'Unknown')}
📝 Title: {data.get('title', 'Unknown')}
👨‍🚀 Author: {data.get('copyright', 'NASA')}
📖 Explanation: {data.get('explanation', 'No explanation available')}

{image_url and f"![{data.get('title', 'NASA APOD')}]({image_url})" or "No image available"}
"""

    return result

if __name__ == "__main__":
    mcp.run(transport='stdio')

This script defines a single tool, get_astronomy_picture_of_day, which fetches data from the NASA APOD API and formats the response as Markdown.

Step 3: Test the Server with MCPJam Inspector

Now, let's run your new MCP server and test it with the Inspector.

  1. Launch the Inspector and Connect to Your Server

    Run the following command in your terminal, replacing <FILE_PATH> with the absolute path to your nasa.py file.

    # Make sure your virtual environment is active first!
    npx @mcpjam/inspector@latest uv run fastmcp run <FILE_PATH>/nasa.py

    MCPJam Inspector will start and automatically connect to your nasa server via STDIO.

  2. Configure an LLM Provider

    In the Inspector, navigate to the Settings tab and add an API key for a provider like OpenAI or Anthropic.

  3. Test in the Playground

    Go to the Playground tab and ask a question that should trigger your tool, for example:

    Show me the astronomy picture of the day for 1995-06-20

    The LLM should invoke your get_astronomy_picture_of_day tool and display the formatted result, including the image!

Congratulations! You've built and tested your first MCP server.