koyelia's picture
Upload 3 files
d4b8525 verified
import gradio as gr
import requests
import json
import pandas as pd
from datetime import datetime
# Your Modal MCP Server URL
MODAL_MCP_URL = "https://koyeliaghoshroy1--mcp-stock-analysis-server-web-app.modal.run"
def test_connection():
"""Test connection to Modal MCP server"""
try:
response = requests.get(f"{MODAL_MCP_URL}/health", timeout=10)
if response.status_code == 200:
return "βœ… Connected to Modal MCP Server", response.json()
else:
return f"❌ Connection failed: {response.status_code}", {}
except Exception as e:
return f"❌ Connection error: {str(e)}", {}
def get_available_tools():
"""Get available MCP tools from Modal server"""
try:
response = requests.get(f"{MODAL_MCP_URL}/tools", timeout=10)
if response.status_code == 200:
data = response.json()
tools = data.get("tools", [])
return "βœ… Tools loaded successfully", tools
else:
return f"❌ Failed to load tools: {response.status_code}", []
except Exception as e:
return f"❌ Tools error: {str(e)}", []
def get_stock_price(symbol):
"""Call Modal MCP server to get stock price"""
if not symbol.strip():
return "❌ Please enter a stock symbol", {}
try:
response = requests.post(
f"{MODAL_MCP_URL}/call",
json={
"name": "get_stock_price",
"arguments": {"symbol": symbol.strip().upper()}
},
timeout=30
)
if response.status_code == 200:
data = response.json()
if data.get("success"):
# Parse the result from the MCP server
result_str = data.get("result", ["{}"])[0]
result = json.loads(result_str)
if "error" in result:
return f"❌ {result['error']}", result
else:
status = f"βœ… Stock data retrieved for {result.get('symbol', symbol)}"
return status, result
else:
return f"❌ {data.get('error', 'Unknown error')}", data
else:
return f"❌ Request failed: {response.status_code}", {}
except Exception as e:
return f"❌ Error: {str(e)}", {}
def analyze_stock_comprehensive(symbol):
"""Call Modal MCP server for comprehensive stock analysis"""
if not symbol.strip():
return "❌ Please enter a stock symbol", {}
try:
response = requests.post(
f"{MODAL_MCP_URL}/call",
json={
"name": "analyze_stock_comprehensive",
"arguments": {"symbol": symbol.strip().upper()}
},
timeout=30
)
if response.status_code == 200:
data = response.json()
if data.get("success"):
# Parse the result from the MCP server
result_str = data.get("result", ["{}"])[0]
result = json.loads(result_str)
if "error" in result:
return f"❌ {result['error']}", result
else:
status = f"βœ… Analysis complete for {result.get('symbol', symbol)}"
return status, result
else:
return f"❌ {data.get('error', 'Unknown error')}", data
else:
return f"❌ Request failed: {response.status_code}", {}
except Exception as e:
return f"❌ Error: {str(e)}", {}
def format_stock_data(data):
"""Format stock data for better display"""
if not data or "error" in data:
return data
# Create a formatted summary
summary = {
"Company": data.get("company_name", "N/A"),
"Symbol": data.get("symbol", "N/A"),
"Current Price": f"${data.get('current_price', 0):.2f}",
"Market Cap": f"${data.get('market_cap', 0):,}" if data.get('market_cap') else "N/A",
"Sector": data.get("sector", "N/A")
}
# Add analysis-specific fields if they exist
if "investment_score" in data:
summary.update({
"Investment Score": f"{data.get('investment_score', 0)}/100",
"Recommendation": data.get("recommendation", "N/A"),
"YTD Return": f"{data.get('ytd_return', 0):.2f}%",
"P/E Ratio": data.get("pe_ratio", "N/A")
})
return summary
# Custom CSS for better styling
custom_css = """
.gradio-container {
font-family: 'Arial', sans-serif;
}
.tab-nav button {
font-weight: bold;
}
.json-display {
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 0.375rem;
padding: 1rem;
}
"""
# Create the Gradio interface
with gr.Blocks(
title="MCP Stock Analysis - Hackathon Entry",
theme=gr.themes.Soft(),
css=custom_css
) as demo:
# Header
gr.Markdown("""
# πŸš€ MCP Stock Analysis Server
## Hugging Face Gradio MCP Hackathon Entry
**Architecture**: Gradio Frontend (Hugging Face Spaces) + Modal MCP Backend
This application demonstrates the **Model Context Protocol (MCP)** by connecting a Gradio interface
to a Modal-hosted MCP server for real-time stock analysis.
""")
# Connection Status
with gr.Row():
connection_btn = gr.Button("πŸ”— Test Connection", variant="secondary")
connection_status = gr.Textbox(label="Connection Status", interactive=False)
# Main Interface Tabs
with gr.Tabs():
# Stock Price Tab
with gr.TabItem("πŸ“ˆ Stock Price Lookup"):
gr.Markdown("Get real-time stock price and basic company information.")
with gr.Row():
with gr.Column(scale=2):
price_symbol = gr.Textbox(
label="Stock Symbol",
placeholder="Enter symbol (e.g., AAPL, TSLA, GOOGL)",
value="AAPL"
)
with gr.Column(scale=1):
price_btn = gr.Button("Get Price", variant="primary", size="lg")
price_status = gr.Textbox(label="Status", interactive=False)
price_result = gr.JSON(label="Stock Price Data")
price_summary = gr.JSON(label="Formatted Summary")
# Stock Analysis Tab
with gr.TabItem("πŸ“Š Comprehensive Analysis"):
gr.Markdown("Get detailed stock analysis with investment scoring and recommendations.")
with gr.Row():
with gr.Column(scale=2):
analysis_symbol = gr.Textbox(
label="Stock Symbol",
placeholder="Enter symbol (e.g., AAPL, TSLA, GOOGL)",
value="TSLA"
)
with gr.Column(scale=1):
analysis_btn = gr.Button("Analyze Stock", variant="primary", size="lg")
analysis_status = gr.Textbox(label="Status", interactive=False)
analysis_result = gr.JSON(label="Full Analysis Data")
analysis_summary = gr.JSON(label="Investment Summary")
# MCP Tools Tab
with gr.TabItem("πŸ› οΈ MCP Tools"):
gr.Markdown("View available MCP tools and server information.")
tools_btn = gr.Button("Load Available Tools", variant="secondary")
tools_status = gr.Textbox(label="Status", interactive=False)
tools_result = gr.JSON(label="Available MCP Tools")
# About Tab
with gr.TabItem("ℹ️ About"):
gr.Markdown("""
## About This Project
### πŸ† Hackathon Entry
- **Event**: Hugging Face Gradio MCP Hackathon
- **Architecture**: Distributed MCP implementation
- **Frontend**: Gradio on Hugging Face Spaces
- **Backend**: Modal MCP Server with stock analysis tools
### πŸ”§ Technical Implementation
- **MCP Protocol**: Implements Model Context Protocol for tool discovery and execution
- **Real-time Data**: Uses yfinance for live stock market data
- **Cloud Architecture**: Scalable backend on Modal with Gradio frontend
- **RESTful API**: Standard HTTP/JSON communication between components
### πŸ“ˆ Features
- **Stock Price Lookup**: Real-time price, market cap, sector information
- **Investment Analysis**: Scoring algorithm with buy/hold/sell recommendations
- **Smart Ticker Search**: Company name to ticker symbol mapping
- **Error Handling**: Robust error handling and user feedback
### πŸ› οΈ MCP Tools Available
1. `get_stock_price` - Retrieve current stock price and basic info
2. `analyze_stock_comprehensive` - Full analysis with investment scoring
3. `smart_ticker_search` - Convert company names to ticker symbols
### 🌐 Live Endpoints
- **Modal MCP Server**: `{MODAL_MCP_URL}`
- **Health Check**: `{MODAL_MCP_URL}/health`
- **Tools Discovery**: `{MODAL_MCP_URL}/tools`
- **Tool Execution**: `{MODAL_MCP_URL}/call`
### πŸ’‘ Usage Examples
Try these stock symbols: **AAPL**, **TSLA**, **GOOGL**, **MSFT**, **AMZN**, **META**
Or company names: **apple**, **tesla**, **microsoft**, **amazon**
""")
# Event Handlers
# Connection test
def handle_connection_test():
status, data = test_connection()
return status
connection_btn.click(
fn=handle_connection_test,
outputs=connection_status
)
# Stock price lookup
def handle_price_lookup(symbol):
status, data = get_stock_price(symbol)
summary = format_stock_data(data) if data else {}
return status, data, summary
price_btn.click(
fn=handle_price_lookup,
inputs=price_symbol,
outputs=[price_status, price_result, price_summary]
)
# Stock analysis
def handle_analysis(symbol):
status, data = analyze_stock_comprehensive(symbol)
summary = format_stock_data(data) if data else {}
return status, data, summary
analysis_btn.click(
fn=handle_analysis,
inputs=analysis_symbol,
outputs=[analysis_status, analysis_result, analysis_summary]
)
# Tools discovery
def handle_tools_discovery():
status, tools = get_available_tools()
return status, tools
tools_btn.click(
fn=handle_tools_discovery,
outputs=[tools_status, tools_result]
)
# Auto-test connection on load
demo.load(
fn=handle_connection_test,
outputs=connection_status
)
# Launch the app
if __name__ == "__main__":
demo.launch(
share=False,
server_name="0.0.0.0",
server_port=7860
)