nasa
by rudylimxl
README.md
# NASA MCP Server
A small but complete **Model Context Protocol (MCP)** server you can deploy to
**Google Cloud Run**. It wraps three of NASA's free public APIs as tools an AI
assistant can call.
This is built as a learning project, so the code and these notes explain the
*why*, not just the *how*.
---
## What is an MCP server, briefly
MCP is a standard way to expose **tools** (callable functions), **resources**
(readable data), and **prompts** (templates) to an AI model. Think of it as a
web API designed specifically for LLMs. This project uses **tools** only.
There are two transports:
- **stdio** — the server runs as a local subprocess. Used by desktop apps
(Claude Desktop, Cursor, IDE plugins). Not what we want here.
- **streamable HTTP** — the server runs as a normal web service reachable over
a URL. **This is the one you use to host on Cloud Run.** All MCP traffic flows
through a single endpoint at `/mcp`.
---
## Project structure
```
nasa-mcp-server/
├── server.py # the MCP server + 3 NASA tools
├── requirements.txt # mcp + httpx
├── Dockerfile # container image for Cloud Run
└── .dockerignore
```
The three tools:
| Tool | What it does |
|------|--------------|
| `get_astronomy_picture` | NASA's Astronomy Picture of the Day (optionally for a past date) |
| `get_mars_photos` | Photos from a Mars rover on a given Earth date |
| `get_near_earth_objects` | Asteroids passing near Earth in a date range |
---
## Step 1 — Get a NASA API key
1. Go to <https://api.nasa.gov/> and fill in the short form (name + email).
2. The key is emailed to you within a few minutes. No credit card. Registered
keys allow ~1,000 requests/hour.
You can skip this at first: the code falls back to NASA's shared `DEMO_KEY`,
which works instantly but is heavily rate-limited (you'll see `429` errors fast).
> **Do not paste your key into source code or share it in chat.** The server
> reads it from the `NASA_API_KEY` environment variable. Locally that's an
> `export`; on Cloud Run it's a Secret Manager secret (Step 4).
---
## Step 2 — Run it locally
```bash
cd nasa-mcp-server
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export NASA_API_KEY=your_key_here # omit to use DEMO_KEY
python server.py
```
The server starts on `http://localhost:8080/mcp`.
---
## Step 3 — Test with the MCP Inspector
The Inspector is the official tool for poking at an MCP server by hand. With the
server running, in another terminal:
```bash
npx @modelcontextprotocol/inspector
```
In the Inspector UI: set transport to **Streamable HTTP**, URL to
`http://localhost:8080/mcp`, connect, then open the **Tools** tab and run
`get_astronomy_picture` with no arguments. You should get back today's picture.
---
## Step 4 — Deploy to Google Cloud Run
Prerequisites: a GCP project with billing enabled and the `gcloud` CLI
installed and authenticated (`gcloud auth login`).
```bash
# Set your project and a region close to you
gcloud config set project YOUR_PROJECT_ID
gcloud config set run/region australia-southeast1 # Sydney; pick your own
# Enable the services we need
gcloud services enable run.googleapis.com \
cloudbuild.googleapis.com \
secretmanager.googleapis.com
```
### 4a. Store your NASA key in Secret Manager
Never bake the key into the image. Create a secret instead:
```bash
printf '%s' 'YOUR_NASA_KEY' | gcloud secrets create nasa-api-key --data-file=-
```
### 4b. Deploy from source
`gcloud` builds the container from the `Dockerfile` and deploys it in one step:
```bash
gcloud run deploy nasa-mcp \
--source . \
--allow-unauthenticated \
--set-secrets=NASA_API_KEY=nasa-api-key:latest
```
When prompted to grant the runtime service account access to the secret, say
yes (or run `gcloud secrets add-iam-policy-binding nasa-api-key
--member="serviceAccount:$(gcloud run services describe nasa-mcp
--format='value(spec.template.spec.serviceAccountName)')"
--role="roles/secretmanager.secretAccessor"`).
Notes:
- You do **not** set `PORT` — Cloud Run injects it and `server.py` reads it.
- On success you get a URL like `https://nasa-mcp-xxxxxxxx.run.app`. Your MCP
endpoint is that URL **plus `/mcp`**.
Quick check it's alive:
```bash
curl -X POST https://nasa-mcp-xxxxxxxx.run.app/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1.0"}}}'
```
A JSON response with `"serverInfo":{"name":"nasa",...}` means you're live.
---
## Step 5 — Connect it to Claude
Point the MCP Inspector at your live `https://.../mcp` URL first to confirm the
tools work remotely. To use it in Claude, add it as a **custom connector** in
Claude's settings (Settings → Connectors), pasting your `/mcp` URL. The exact
menu wording changes over time — see the current connector docs at
<https://support.claude.com>. Then ask Claude something like *"what's NASA's
astronomy picture of the day?"* and it will call your tool.
---
## ⚠️ About `--allow-unauthenticated`
The deploy command above makes your server **publicly reachable by anyone with
the URL**. For a learning project that's fine, but be aware: strangers could
call it and burn through your NASA rate limit. Before relying on it, lock it
down. Reasonable next steps, easiest first:
1. **Require a bearer token** — add a check in the server for a shared secret in
the `Authorization` header, and supply that header from the client.
2. **Keep the service private** (drop `--allow-unauthenticated`) and require
Google IAM auth, putting an authenticating proxy in front for Claude.
3. **Full OAuth 2.1**, which the MCP spec defines for remote servers. FastMCP
has helpers for this — it's the "proper" answer but the most work.
---
## How the code maps to MCP concepts
- `FastMCP(name="nasa", ...)` creates the server. `stateless_http=True` and
`json_response=True` are the recommended settings for a scalable HTTP
deployment — Cloud Run may run several container instances, and stateless mode
means none of them rely on in-memory session state.
- Each `@mcp.tool()` function becomes a callable tool. **The docstring is the
description the model sees, and the type hints become the input schema** — so
writing clear docstrings and precise types is the single highest-leverage
thing you can do for tool quality.
- `mcp.run(transport="streamable-http")` serves everything at `/mcp`.
## Ideas to extend it
- Add a tool for NASA's EPIC (full-disc Earth imagery) or EONET (natural-event
tracking) endpoints — same `_nasa_get` helper, new path.
- Swap NASA for a different free API (weather, exchange rates, etc.) — only the
tool bodies change; the server scaffolding and deployment stay identical.
- Add an MCP **resource** (read-only data) alongside the tools.
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues