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 )