File size: 11,241 Bytes
d4b8525
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
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
    )