|
""" |
|
NASA Space Explorer Assistant - Complete MCP Server + Chat Interface |
|
Gradio Agents & MCP Hackathon 2025 |
|
|
|
Features: |
|
- 3 MCP servers (APOD, NeoWs, Mars Rover) with 15 total tools |
|
- LlamaIndex chat agent with Mistral LLM |
|
- Interactive testing interface |
|
- Cross-MCP query capabilities |
|
""" |
|
|
|
import os |
|
import asyncio |
|
import gradio as gr |
|
from dotenv import load_dotenv |
|
import sys |
|
import logging |
|
|
|
|
|
sys.path.append(os.path.join(os.path.dirname(__file__), 'src')) |
|
|
|
from src.mcp_server import NASAMCPServer |
|
from src.chat_agent import NASAChatAgent |
|
from src.config import Config |
|
|
|
|
|
load_dotenv() |
|
|
|
|
|
mcp_server = None |
|
chat_agent = None |
|
|
|
|
|
logging.basicConfig(level=logging.INFO) |
|
logger = logging.getLogger(__name__) |
|
|
|
async def init_servers(): |
|
"""Initialize both MCP server and chat agent.""" |
|
global mcp_server, chat_agent |
|
|
|
try: |
|
|
|
print("π Initializing MCP Server...") |
|
mcp_server = NASAMCPServer() |
|
await mcp_server.initialize() |
|
print("β
MCP Server initialized with 3 MCPs") |
|
|
|
|
|
if Config.MISTRAL_API_KEY: |
|
print("π€ Initializing Chat Agent...") |
|
|
|
chat_agent = NASAChatAgent(mcp_server=mcp_server) |
|
success = await chat_agent.initialize() |
|
if success: |
|
print("β
Chat Agent initialized with LlamaIndex + Mistral") |
|
else: |
|
print("β οΈ Chat Agent failed to initialize") |
|
chat_agent = None |
|
else: |
|
print("β οΈ MISTRAL_API_KEY not set - chat features disabled") |
|
chat_agent = None |
|
|
|
return mcp_server, chat_agent |
|
|
|
except Exception as e: |
|
print(f"β Initialization error: {str(e)}") |
|
return None, None |
|
|
|
def create_app(): |
|
"""Create and configure the complete Gradio application.""" |
|
|
|
with gr.Blocks( |
|
title="NASA Space Explorer Assistant", |
|
theme=gr.themes.Soft(), |
|
css=""" |
|
.chat-container { |
|
max-height: 600px; |
|
overflow-y: auto; |
|
} |
|
.space-themed { |
|
background: linear-gradient(135deg, #0c0c0c 0%, #1a1a2e 50%, #16213e 100%); |
|
} |
|
.example-btn { |
|
margin: 2px; |
|
} |
|
""" |
|
) as app: |
|
|
|
gr.HTML(""" |
|
<div style="text-align: center; padding: 20px; background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%); border-radius: 10px; margin-bottom: 20px;"> |
|
<h1 style="color: white; margin: 0;">π NASA Space Explorer Assistant</h1> |
|
<p style="color: #cccccc; margin: 10px 0 0 0;">MCP-powered space data access + AI Chat Assistant</p> |
|
</div> |
|
""") |
|
|
|
|
|
with gr.Row(): |
|
with gr.Column(scale=1): |
|
gr.Markdown("### π‘ System Status") |
|
api_status = "π’ NASA API" if Config.NASA_API_KEY != "DEMO_KEY" else "π‘ NASA Demo Key" |
|
chat_status = "π’ Chat Ready" if Config.MISTRAL_API_KEY else "π΄ Chat Disabled" |
|
gr.Markdown(f"**{api_status}** | **{chat_status}**") |
|
|
|
with gr.Column(scale=2): |
|
gr.Markdown("### π οΈ Available Services") |
|
gr.Markdown("π **APOD** (3 tools) | π **NeoWs** (6 tools) | π΄ **Mars Rover** (6 tools) | **Total: 15 MCP tools**") |
|
|
|
with gr.Tabs(): |
|
|
|
|
|
|
|
with gr.Tab("π€ Chat with NASA Assistant") as chat_tab: |
|
if Config.MISTRAL_API_KEY: |
|
gr.Markdown(""" |
|
### π Your Personal NASA Space Expert |
|
|
|
I can access **live NASA data** to answer your questions about space! Try asking me about: |
|
""") |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
gr.Markdown(""" |
|
**π Astronomy** |
|
- Today's space picture |
|
- Historical astronomy images |
|
- Space phenomena explanations |
|
""") |
|
with gr.Column(): |
|
gr.Markdown(""" |
|
**π Asteroids & NEOs** |
|
- Dangerous space rocks |
|
- Asteroid size comparisons |
|
- Threat analysis |
|
""") |
|
with gr.Column(): |
|
gr.Markdown(""" |
|
**π΄ Mars Exploration** |
|
- Rover photos and missions |
|
- Mars surface discoveries |
|
- Mission statistics |
|
""") |
|
|
|
|
|
chatbot = gr.Chatbot( |
|
value=[], |
|
elem_classes=["chat-container"], |
|
height=500, |
|
show_label=False, |
|
type="messages" |
|
) |
|
|
|
with gr.Row(): |
|
msg = gr.Textbox( |
|
placeholder="Ask me anything about space... π", |
|
show_label=False, |
|
scale=4, |
|
container=False |
|
) |
|
send_btn = gr.Button("Send π", variant="primary", scale=1) |
|
clear_btn = gr.Button("Clear ποΈ", variant="secondary", scale=1) |
|
|
|
|
|
gr.Markdown("### π― Quick Examples") |
|
with gr.Row(): |
|
example_btn1 = gr.Button("π What's today's astronomy picture?", elem_classes=["example-btn"]) |
|
example_btn2 = gr.Button("π Any dangerous asteroids this week?", elem_classes=["example-btn"]) |
|
|
|
with gr.Row(): |
|
example_btn3 = gr.Button("π΄ Show me latest Mars rover photos", elem_classes=["example-btn"]) |
|
example_btn4 = gr.Button("π Give me today's space summary", elem_classes=["example-btn"]) |
|
|
|
with gr.Row(): |
|
example_btn5 = gr.Button("π
What space events happened on January 1, 2024?", elem_classes=["example-btn"]) |
|
example_btn6 = gr.Button("π Compare sizes of this week's largest asteroids", elem_classes=["example-btn"]) |
|
|
|
|
|
async def respond(message, history): |
|
"""Handle chat response.""" |
|
if not message.strip(): |
|
return history, "" |
|
|
|
if not chat_agent: |
|
error_msg = "β Chat agent not available. Please check Mistral API key configuration." |
|
|
|
return history + [ |
|
{"role": "user", "content": message}, |
|
{"role": "assistant", "content": error_msg} |
|
], "" |
|
|
|
|
|
history = history + [{"role": "user", "content": message}] |
|
|
|
|
|
try: |
|
print(f"π€ Processing: {message}") |
|
response = await chat_agent.chat(message) |
|
|
|
history.append({"role": "assistant", "content": response}) |
|
print(f"β
Response generated ({len(response)} chars)") |
|
except Exception as e: |
|
error_response = f"β Sorry, I encountered an error: {str(e)}" |
|
history.append({"role": "assistant", "content": error_response}) |
|
print(f"β Chat error: {str(e)}") |
|
|
|
return history, "" |
|
|
|
def clear_chat(): |
|
"""Clear chat history.""" |
|
return [], "" |
|
|
|
def set_example_text(text): |
|
"""Set example text in input.""" |
|
return text |
|
|
|
|
|
msg.submit( |
|
respond, |
|
inputs=[msg, chatbot], |
|
outputs=[chatbot, msg] |
|
) |
|
|
|
send_btn.click( |
|
respond, |
|
inputs=[msg, chatbot], |
|
outputs=[chatbot, msg] |
|
) |
|
|
|
clear_btn.click(clear_chat, outputs=[chatbot, msg]) |
|
|
|
|
|
example_btn1.click( |
|
lambda: "What's today's astronomy picture?", |
|
outputs=[msg] |
|
) |
|
example_btn2.click( |
|
lambda: "Are there any potentially dangerous asteroids approaching Earth this week?", |
|
outputs=[msg] |
|
) |
|
example_btn3.click( |
|
lambda: "Show me the latest photos from Curiosity rover on Mars", |
|
outputs=[msg] |
|
) |
|
example_btn4.click( |
|
lambda: "Give me a comprehensive space summary for today", |
|
outputs=[msg] |
|
) |
|
example_btn5.click( |
|
lambda: "What space events happened on January 1st, 2024? Show me the astronomy picture, any asteroids, and Mars rover activity for that date.", |
|
outputs=[msg] |
|
) |
|
example_btn6.click( |
|
lambda: "What are the largest asteroids approaching Earth this week? Compare their sizes to familiar objects.", |
|
outputs=[msg] |
|
) |
|
|
|
else: |
|
|
|
gr.Markdown(""" |
|
### π Chat Feature Disabled |
|
|
|
To enable the AI chat assistant, you need to add your Mistral API key: |
|
|
|
1. **Get API Key**: Visit [console.mistral.ai](https://console.mistral.ai/api-keys) |
|
2. **Add to .env file**: |
|
``` |
|
MISTRAL_API_KEY=your_api_key_here |
|
``` |
|
3. **Restart the application** |
|
|
|
The chat assistant will then be able to: |
|
- Answer questions about space in natural language |
|
- Access all 15 NASA MCP tools automatically |
|
- Provide cross-correlations and insights |
|
- Generate comprehensive space summaries |
|
""") |
|
|
|
gr.HTML(""" |
|
<div style="text-align: center; padding: 20px; background: #000000; border-radius: 10px; margin-top: 20px;"> |
|
<h3>π Preview: What the chat can do</h3> |
|
<p><strong>User:</strong> "What's today's astronomy picture?"</p> |
|
<p><strong>Assistant:</strong> "Today's Astronomy Picture of the Day is..." <em>[Uses APOD MCP]</em></p> |
|
<br> |
|
<p><strong>User:</strong> "Are there dangerous asteroids this week?"</p> |
|
<p><strong>Assistant:</strong> "Let me check current asteroid data..." <em>[Uses NeoWs MCP]</em></p> |
|
<br> |
|
<p><strong>User:</strong> "What space events happened on my birthday?"</p> |
|
<p><strong>Assistant:</strong> "I'll check astronomy pictures, asteroids, and Mars activity for that date..." <em>[Uses all 3 MCPs]</em></p> |
|
</div> |
|
""") |
|
|
|
|
|
|
|
|
|
with gr.Tab("π οΈ MCP Server Dashboard") as mcp_tab: |
|
gr.Markdown("### π§ MCP Server Status & Management") |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
gr.Markdown("#### π Server Status") |
|
status_button = gr.Button("π Check Server Status", variant="secondary") |
|
status_output = gr.JSON(label="Detailed Status") |
|
|
|
async def check_status(): |
|
if mcp_server: |
|
return await mcp_server.get_server_status() |
|
else: |
|
return {"error": "MCP server not initialized"} |
|
|
|
status_button.click( |
|
lambda: asyncio.run(check_status()), |
|
outputs=[status_output] |
|
) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
gr.Markdown("### π§ͺ Interactive Tool Testing") |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
tool_dropdown = gr.Dropdown( |
|
choices=[ |
|
"apod_get_apod_today", |
|
"apod_get_apod_by_date", |
|
"neows_get_asteroids_today", |
|
"neows_get_potentially_hazardous", |
|
"marsrover_get_latest_photos", |
|
"marsrover_get_rover_status" |
|
], |
|
label="Select Tool to Test", |
|
value="apod_get_apod_today" |
|
) |
|
|
|
args_input = gr.JSON( |
|
label="Tool Arguments (JSON)", |
|
value={} |
|
) |
|
|
|
test_button = gr.Button("π§ͺ Execute Tool", variant="primary") |
|
|
|
with gr.Column(): |
|
test_output = gr.JSON(label="Tool Response", height=400) |
|
|
|
async def test_tool(tool_name, arguments): |
|
if mcp_server: |
|
return await mcp_server.call_tool(tool_name, arguments) |
|
else: |
|
return {"error": "MCP server not initialized"} |
|
|
|
test_button.click( |
|
lambda tool, args: asyncio.run(test_tool(tool, args)), |
|
inputs=[tool_dropdown, args_input], |
|
outputs=[test_output] |
|
) |
|
|
|
|
|
with gr.Row(): |
|
quick_test1 = gr.Button("π Test APOD Today", size="sm") |
|
quick_test2 = gr.Button("π Test Asteroids", size="sm") |
|
quick_test3 = gr.Button("π΄ Test Mars Status", size="sm") |
|
|
|
quick_test1.click( |
|
lambda: ("apod_get_apod_today", {}), |
|
outputs=[tool_dropdown, args_input] |
|
) |
|
quick_test2.click( |
|
lambda: ("neows_get_asteroids_today", {}), |
|
outputs=[tool_dropdown, args_input] |
|
) |
|
quick_test3.click( |
|
lambda: ("marsrover_get_rover_status", {"rover": "curiosity"}), |
|
outputs=[tool_dropdown, args_input] |
|
) |
|
|
|
|
|
|
|
|
|
with gr.Tab("π Cross-MCP Queries") as cross_tab: |
|
gr.Markdown("### π Advanced Cross-MCP Capabilities") |
|
gr.Markdown("These queries combine data from multiple NASA APIs for unique insights:") |
|
|
|
with gr.Row(): |
|
with gr.Column(): |
|
cross_query_type = gr.Dropdown( |
|
choices=["space_summary", "correlate_date"], |
|
label="Cross-MCP Query Type", |
|
value="space_summary" |
|
) |
|
|
|
cross_args = gr.JSON( |
|
label="Query Arguments", |
|
value={"date": "2024-01-01"} |
|
) |
|
|
|
cross_button = gr.Button("π Execute Cross-MCP Query", variant="primary") |
|
|
|
|
|
gr.Markdown("#### π― Preset Queries") |
|
preset_summary = gr.Button("π Today's Space Summary", variant="secondary") |
|
preset_correlate = gr.Button("π Correlate Jan 1, 2024", variant="secondary") |
|
|
|
with gr.Column(): |
|
cross_output = gr.JSON(label="Cross-MCP Response", height=500) |
|
|
|
async def test_cross_query(query_type, arguments): |
|
if mcp_server: |
|
return await mcp_server.handle_cross_mcp_query(query_type, arguments) |
|
else: |
|
return {"error": "MCP server not initialized"} |
|
|
|
cross_button.click( |
|
lambda query, args: asyncio.run(test_cross_query(query, args)), |
|
inputs=[cross_query_type, cross_args], |
|
outputs=[cross_output] |
|
) |
|
|
|
preset_summary.click( |
|
lambda: ("space_summary", {"date": "2025-06-07"}), |
|
outputs=[cross_query_type, cross_args] |
|
) |
|
preset_correlate.click( |
|
lambda: ("correlate_date", {"date": "2024-01-01"}), |
|
outputs=[cross_query_type, cross_args] |
|
) |
|
|
|
|
|
|
|
|
|
with gr.Tab("π Documentation") as docs_tab: |
|
gr.Markdown(""" |
|
### π NASA Space Explorer Documentation |
|
|
|
#### ποΈ Architecture |
|
``` |
|
User Interface (Gradio) |
|
β |
|
Chat Agent (LlamaIndex + Mistral) |
|
β |
|
MCP Server (NASA Space Explorer) |
|
βββ APOD MCP βββ NASA APOD API |
|
βββ NeoWs MCP βββ NASA Asteroids API |
|
βββ Mars Rover MCP βββ NASA Mars Rover API |
|
``` |
|
|
|
#### π οΈ Available MCP Tools |
|
|
|
**APOD MCP (3 tools):** |
|
- `apod_get_apod_today` - Get today's astronomy picture |
|
- `apod_get_apod_by_date` - Get picture for specific date |
|
- `apod_get_apod_date_range` - Get pictures for date range |
|
|
|
**NeoWs MCP (6 tools):** |
|
- `neows_get_asteroids_today` - Today's approaching asteroids |
|
- `neows_get_asteroids_week` - This week's asteroids |
|
- `neows_get_asteroids_date_range` - Asteroids for date range |
|
- `neows_get_potentially_hazardous` - Only dangerous asteroids |
|
- `neows_get_largest_asteroids_week` - Largest asteroids this week |
|
- `neows_analyze_asteroid_danger` - Detailed threat analysis |
|
|
|
**Mars Rover MCP (6 tools):** |
|
- `marsrover_get_rover_status` - Rover mission status |
|
- `marsrover_get_latest_photos` - Most recent photos |
|
- `marsrover_get_photos_by_earth_date` - Photos by Earth date |
|
- `marsrover_get_photos_by_sol` - Photos by Martian day |
|
- `marsrover_get_photos_by_camera` - Photos by specific camera |
|
- `marsrover_compare_rovers` - Compare all rovers |
|
|
|
#### π Cross-MCP Features |
|
- **Space Summary**: Combine APOD + asteroids + Mars data for any date |
|
- **Date Correlation**: Find all space events for a specific date |
|
- **Intelligent Analysis**: AI-powered insights across multiple data sources |
|
|
|
#### π‘ Example Conversations |
|
|
|
**Simple Queries:** |
|
- "What's today's astronomy picture?" |
|
- "Are there dangerous asteroids this week?" |
|
- "Show me latest Curiosity photos" |
|
|
|
**Cross-MCP Queries:** |
|
- "What space events happened on my birthday?" |
|
- "Compare asteroid sizes to Mars rover scale" |
|
- "Give me a complete space summary for July 4th, 2023" |
|
|
|
**Advanced Analysis:** |
|
- "Analyze the threat level of asteroid 2023 BU" |
|
- "How has Curiosity's mission progressed over the years?" |
|
- "Correlate astronomy pictures with asteroid activity" |
|
|
|
#### βοΈ Technical Details |
|
|
|
**MCP Protocol:** 2024-11-05 |
|
**Chat LLM:** Mistral Large (via API) |
|
**Framework:** LlamaIndex + Gradio |
|
**NASA APIs:** APOD, NeoWs, Mars Rover Photos |
|
**Rate Limiting:** 950 requests/hour (NASA limit buffer) |
|
**Error Handling:** Comprehensive retry logic and validation |
|
""") |
|
|
|
|
|
gr.HTML(""" |
|
<div style="text-align: center; padding: 20px; margin-top: 30px; border-top: 1px solid #ddd;"> |
|
<p style="color: #666;"> |
|
π <strong>Built for Gradio Agents & MCP Hackathon 2025</strong> π<br> |
|
NASA Space Explorer Assistant - Making space data accessible to everyone |
|
</p> |
|
</div> |
|
""") |
|
|
|
return app |
|
|
|
async def main(): |
|
"""Main application entry point.""" |
|
print("π NASA Space Explorer Assistant") |
|
print("=" * 50) |
|
|
|
|
|
print("π Validating configuration...") |
|
config_valid = Config.validate() |
|
|
|
if Config.NASA_API_KEY == "DEMO_KEY": |
|
print("βΉοΈ Using NASA DEMO_KEY (30 requests/hour limit)") |
|
else: |
|
print("β
NASA API key configured") |
|
|
|
if Config.MISTRAL_API_KEY: |
|
print("β
Mistral API key configured - chat enabled") |
|
else: |
|
print("β οΈ Mistral API key missing - chat disabled") |
|
|
|
|
|
print("\nπ Initializing services...") |
|
await init_servers() |
|
|
|
|
|
print("\nπ Creating Gradio interface...") |
|
app = create_app() |
|
|
|
print("π Launch complete!") |
|
print("=" * 50) |
|
print("π‘ MCP Server: http://localhost:8000") |
|
print("π€ Chat Interface: Available if Mistral API key configured") |
|
print("π οΈ Dashboard: Interactive MCP testing available") |
|
print("π Cross-MCP: Advanced query capabilities") |
|
print("=" * 50) |
|
|
|
|
|
app.launch( |
|
server_name="0.0.0.0", |
|
server_port=7860, |
|
|
|
show_error=True, |
|
quiet=False |
|
) |
|
|
|
if __name__ == "__main__": |
|
asyncio.run(main()) |