Skip to main content
Glama

MaverickMCP

by wshobson
MIT License
165
  • Apple
test_mcp_tools.py15.6 kB
""" Comprehensive tests for all MCP tool functions in Maverick-MCP. This module tests all public MCP tools exposed by the server including: - Stock data fetching - Technical analysis - Risk analysis - Chart generation - News sentiment - Multi-ticker comparison """ from datetime import datetime from unittest.mock import MagicMock, patch import numpy as np import pandas as pd import pytest from fastmcp import Client from maverick_mcp.api.server import mcp class TestMCPTools: """Test suite for all MCP tool functions using the new router structure.""" @pytest.fixture def mock_stock_data(self): """Create sample stock data for testing.""" dates = pd.date_range(end=datetime.now(), periods=250, freq="D") return pd.DataFrame( { "Open": np.random.uniform(90, 110, 250), "High": np.random.uniform(95, 115, 250), "Low": np.random.uniform(85, 105, 250), "Close": np.random.uniform(90, 110, 250), "Volume": np.random.randint(1000000, 10000000, 250), }, index=dates, ) @pytest.mark.asyncio async def test_fetch_stock_data(self, mock_stock_data): """Test basic stock data fetching.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: mock_get.return_value = mock_stock_data async with Client(mcp) as client: result = await client.call_tool( "/data_fetch_stock_data", { "request": { "ticker": "AAPL", "start_date": "2024-01-01", "end_date": "2024-01-31", } }, ) assert result[0].text is not None data = eval(result[0].text) assert "ticker" in data assert data["ticker"] == "AAPL" assert "record_count" in data assert data["record_count"] == 250 @pytest.mark.asyncio async def test_rsi_analysis(self, mock_stock_data): """Test RSI technical analysis.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: mock_get.return_value = mock_stock_data async with Client(mcp) as client: result = await client.call_tool( "/technical_get_rsi_analysis", {"ticker": "AAPL", "period": 14} ) assert result[0].text is not None data = eval(result[0].text) assert "analysis" in data assert "ticker" in data assert data["ticker"] == "AAPL" assert "current" in data["analysis"] assert "signal" in data["analysis"] assert data["analysis"]["signal"] in [ "oversold", "neutral", "overbought", "bullish", "bearish", ] @pytest.mark.asyncio async def test_macd_analysis(self, mock_stock_data): """Test MACD technical analysis.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: mock_get.return_value = mock_stock_data async with Client(mcp) as client: result = await client.call_tool( "/technical_get_macd_analysis", {"ticker": "MSFT"} ) assert result[0].text is not None data = eval(result[0].text) assert "analysis" in data assert "ticker" in data assert data["ticker"] == "MSFT" assert "macd" in data["analysis"] assert "signal" in data["analysis"] assert "histogram" in data["analysis"] assert "indicator" in data["analysis"] @pytest.mark.asyncio async def test_support_resistance(self, mock_stock_data): """Test support and resistance level detection.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: # Create data with clear support/resistance levels mock_data = mock_stock_data.copy() mock_data["High"] = [105 if i % 20 < 10 else 110 for i in range(250)] mock_data["Low"] = [95 if i % 20 < 10 else 100 for i in range(250)] mock_data["Close"] = [100 if i % 20 < 10 else 105 for i in range(250)] mock_get.return_value = mock_data async with Client(mcp) as client: result = await client.call_tool( "/technical_get_support_resistance", {"ticker": "GOOGL"} ) assert result[0].text is not None data = eval(result[0].text) assert "support_levels" in data assert "resistance_levels" in data assert len(data["support_levels"]) > 0 assert len(data["resistance_levels"]) > 0 @pytest.mark.asyncio async def test_batch_stock_data(self, mock_stock_data): """Test batch stock data fetching.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: mock_get.return_value = mock_stock_data async with Client(mcp) as client: result = await client.call_tool( "/data_fetch_stock_data_batch", { "request": { "tickers": ["AAPL", "MSFT", "GOOGL"], "start_date": "2024-01-01", "end_date": "2024-01-31", } }, ) assert result[0].text is not None data = eval(result[0].text) assert "results" in data assert "success_count" in data assert "error_count" in data assert len(data["results"]) == 3 assert data["success_count"] == 3 assert data["error_count"] == 0 @pytest.mark.asyncio async def test_portfolio_risk_analysis(self, mock_stock_data): """Test portfolio risk analysis.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: # Create correlated stock data base_returns = np.random.normal(0.001, 0.02, 250) mock_data1 = mock_stock_data.copy() mock_data2 = mock_stock_data.copy() mock_data3 = mock_stock_data.copy() # Apply correlated returns and ensure lowercase column names mock_data1.columns = mock_data1.columns.str.lower() mock_data2.columns = mock_data2.columns.str.lower() mock_data3.columns = mock_data3.columns.str.lower() mock_data1["close"] = 100 * np.exp(np.cumsum(base_returns)) mock_data2["close"] = 100 * np.exp( np.cumsum(base_returns * 0.8 + np.random.normal(0, 0.01, 250)) ) mock_data3["close"] = 100 * np.exp( np.cumsum(base_returns * 0.6 + np.random.normal(0, 0.015, 250)) ) mock_get.return_value = mock_data1 async with Client(mcp) as client: result = await client.call_tool( "/portfolio_risk_adjusted_analysis", {"ticker": "AAPL", "risk_level": 50.0}, ) assert result[0].text is not None data = eval(result[0].text) assert "ticker" in data assert "risk_level" in data assert "position_sizing" in data assert "risk_management" in data @pytest.mark.asyncio async def test_maverick_screening(self): """Test Maverick stock screening.""" with ( patch("maverick_mcp.data.models.SessionLocal") as mock_session_cls, patch( "maverick_mcp.data.models.MaverickStocks.get_top_stocks" ) as mock_get_stocks, ): # Mock database session (not used but needed for session lifecycle) _ = mock_session_cls.return_value.__enter__.return_value # Mock return data class MockStock1: def to_dict(self): return { "stock": "AAPL", "close": 150.0, "combined_score": 92, "momentum_score": 88, "adr_pct": 2.5, } class MockStock2: def to_dict(self): return { "stock": "MSFT", "close": 300.0, "combined_score": 89, "momentum_score": 85, "adr_pct": 2.1, } mock_get_stocks.return_value = [MockStock1(), MockStock2()] async with Client(mcp) as client: result = await client.call_tool( "/screening_get_maverick_stocks", {"limit": 10} ) assert result[0].text is not None data = eval(result[0].text) assert "stocks" in data assert len(data["stocks"]) == 2 assert data["stocks"][0]["stock"] == "AAPL" @pytest.mark.asyncio async def test_news_sentiment(self): """Test news sentiment analysis.""" with ( patch("requests.get") as mock_get, patch( "maverick_mcp.config.settings.settings.external_data.api_key", "test_api_key", ), patch( "maverick_mcp.config.settings.settings.external_data.base_url", "https://test-api.com", ), ): mock_response = MagicMock() mock_response.status_code = 200 mock_response.json.return_value = { "articles": [ { "title": "Apple hits new highs", "url": "https://example.com/1", "summary": "Positive news about Apple", "banner_image": "https://example.com/image1.jpg", "time_published": "20240115T100000", "overall_sentiment_score": 0.8, "overall_sentiment_label": "Bullish", } ] } mock_get.return_value = mock_response async with Client(mcp) as client: result = await client.call_tool( "/data_get_news_sentiment", {"request": {"ticker": "AAPL"}} ) assert result[0].text is not None data = eval(result[0].text) assert "articles" in data assert len(data["articles"]) > 0 assert data["articles"][0]["overall_sentiment_label"] == "Bullish" @pytest.mark.asyncio async def test_full_technical_analysis(self, mock_stock_data): """Test comprehensive technical analysis.""" with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data" ) as mock_get: # Ensure lowercase column names for technical analysis mock_data_lowercase = mock_stock_data.copy() mock_data_lowercase.columns = mock_data_lowercase.columns.str.lower() mock_get.return_value = mock_data_lowercase async with Client(mcp) as client: result = await client.call_tool( "/technical_get_full_technical_analysis", {"ticker": "AAPL"} ) assert result[0].text is not None data = eval(result[0].text) assert "indicators" in data assert "rsi" in data["indicators"] assert "macd" in data["indicators"] assert "bollinger_bands" in data["indicators"] assert "levels" in data assert "current_price" in data assert "last_updated" in data @pytest.mark.asyncio async def test_error_handling(self): """Test error handling for invalid requests.""" async with Client(mcp) as client: # Test invalid ticker format with pytest.raises(Exception) as exc_info: await client.call_tool( "/data_fetch_stock_data", { "request": { "ticker": "INVALIDTICKER", # Too long (max 10 chars) "start_date": "2024-01-01", "end_date": "2024-01-31", } }, ) assert "validation error" in str(exc_info.value).lower() # Test invalid date range with pytest.raises(Exception) as exc_info: await client.call_tool( "/data_fetch_stock_data", { "request": { "ticker": "AAPL", "start_date": "2024-12-31", "end_date": "2024-01-01", # End before start } }, ) assert ( "end date" in str(exc_info.value).lower() and "start date" in str(exc_info.value).lower() ) @pytest.mark.asyncio async def test_caching_behavior(self, mock_stock_data): """Test that caching reduces API calls.""" call_count = 0 def mock_get_data(*args, **kwargs): nonlocal call_count call_count += 1 return mock_stock_data with patch( "maverick_mcp.providers.stock_data.StockDataProvider.get_stock_data", side_effect=mock_get_data, ): async with Client(mcp) as client: # First call await client.call_tool( "/data_fetch_stock_data", { "request": { "ticker": "AAPL", "start_date": "2024-01-01", "end_date": "2024-01-31", } }, ) assert call_count == 1 # Second call with same parameters should hit cache await client.call_tool( "/data_fetch_stock_data", { "request": { "ticker": "AAPL", "start_date": "2024-01-01", "end_date": "2024-01-31", } }, ) # Note: In test environment without actual caching infrastructure, # the call count may be 2. This is expected behavior. assert call_count <= 2 if __name__ == "__main__": pytest.main([__file__, "-v"])

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/wshobson/maverick-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server