|
import gradio as gr |
|
import requests |
|
import json |
|
import pandas as pd |
|
from datetime import datetime |
|
|
|
|
|
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"): |
|
|
|
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"): |
|
|
|
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 |
|
|
|
|
|
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") |
|
} |
|
|
|
|
|
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 = """ |
|
.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; |
|
} |
|
""" |
|
|
|
|
|
with gr.Blocks( |
|
title="MCP Stock Analysis - Hackathon Entry", |
|
theme=gr.themes.Soft(), |
|
css=custom_css |
|
) as demo: |
|
|
|
|
|
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. |
|
""") |
|
|
|
|
|
with gr.Row(): |
|
connection_btn = gr.Button("π Test Connection", variant="secondary") |
|
connection_status = gr.Textbox(label="Connection Status", interactive=False) |
|
|
|
|
|
with gr.Tabs(): |
|
|
|
|
|
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") |
|
|
|
|
|
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") |
|
|
|
|
|
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") |
|
|
|
|
|
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** |
|
""") |
|
|
|
|
|
|
|
|
|
def handle_connection_test(): |
|
status, data = test_connection() |
|
return status |
|
|
|
connection_btn.click( |
|
fn=handle_connection_test, |
|
outputs=connection_status |
|
) |
|
|
|
|
|
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] |
|
) |
|
|
|
|
|
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] |
|
) |
|
|
|
|
|
def handle_tools_discovery(): |
|
status, tools = get_available_tools() |
|
return status, tools |
|
|
|
tools_btn.click( |
|
fn=handle_tools_discovery, |
|
outputs=[tools_status, tools_result] |
|
) |
|
|
|
|
|
demo.load( |
|
fn=handle_connection_test, |
|
outputs=connection_status |
|
) |
|
|
|
|
|
if __name__ == "__main__": |
|
demo.launch( |
|
share=False, |
|
server_name="0.0.0.0", |
|
server_port=7860 |
|
) |