Skip to main content
Glama
etweisberg

MLB Stats MCP Server

by etweisberg
test_pybaseball.py72.9 kB
""" Tests for pybaseball tools. """ import json import os from pathlib import Path import pytest from dotenv import load_dotenv from mcp import ClientSession from mcp.client.stdio import StdioServerParameters, stdio_client from mlb_stats_mcp.utils.images import display_base64_image from mlb_stats_mcp.utils.logging_config import setup_logging env_path = Path(__file__).parent.parent.parent / ".env" load_dotenv(env_path) logger = setup_logging("[TEST] pybaseball") logger.debug(f"SHOW_IMAGE SET TO {os.environ.get('SHOW_IMAGE')}") def simplify_session_setup(): """Helper to create server params for tests.""" server_path = Path(__file__).parent.parent / "server.py" return StdioServerParameters(command="python", args=[str(server_path)], env=None) @pytest.mark.asyncio async def test_get_statcast_batter_exitvelo_barrels(): """Test the get_statcast_batter_exitvelo_barrels tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_batter_exitvelo_barrels", {"year": 2023, "minBBE": 50} ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_pitcher_exitvelo_barrels(): """Test the get_statcast_pitcher_exitvelo_barrels tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_pitcher_exitvelo_barrels", {"year": 2023, "minBBE": 50} ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_batter_expected_stats(): """Test the get_statcast_batter_expected_stats tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_batter_expected_stats", {"year": 2023, "minPA": 50} ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_pitcher_expected_stats(): """Test the get_statcast_pitcher_expected_stats tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_pitcher_expected_stats", {"year": 2023, "minPA": 50} ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_batter_percentile_ranks(): """Test the get_statcast_batter_percentile_ranks tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool("get_statcast_batter_percentile_ranks", {"year": 2023}) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_pitcher_percentile_ranks(): """Test the get_statcast_pitcher_percentile_ranks tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_pitcher_percentile_ranks", {"year": 2023} ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_batter_pitch_arsenal(): """Test the get_statcast_batter_pitch_arsenal tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_batter_pitch_arsenal", {"year": 2023, "minPA": 50} ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_statcast_pitcher_pitch_arsenal(): """Test the get_statcast_pitcher_pitch_arsenal tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_statcast_pitcher_pitch_arsenal", {"year": 2023, "minP": 50, "arsenal_type": "avg_speed"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" # Test with different arsenal type result = await session.call_tool( "get_statcast_pitcher_pitch_arsenal", {"year": 2023, "minP": 50, "arsenal_type": "avg_spin"}, ) # This should succeed or hit length limit, but not a tool error assert not result.isError, "Expected successful response or length limit handling" # Test with invalid arsenal type result = await session.call_tool( "get_statcast_pitcher_pitch_arsenal", {"year": 2023, "minP": 50, "arsenal_type": "invalid_type"}, ) assert result.isError, "Expected error for invalid arsenal type" @pytest.mark.asyncio async def test_get_statcast_single_game(): """Test the get_statcast_single_game tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters - using a known game ID result = await session.call_tool( "get_statcast_single_game", {"game_pk": 717953}, # Example game from 2023 ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" # Test with invalid game ID result = await session.call_tool("get_statcast_single_game", {"game_pk": 999999999}) assert result.isError, "Expected error response for invalid game ID" @pytest.mark.asyncio async def test_image_create_spraychart_altuve(): """Test the create_spraychart_plot tool following the Altuve example.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() try: # Get statcast batter data for Jose Altuve - using end_row to limit response size batter_result = await session.call_tool( "get_statcast_batter_data", { "player_id": 514888, "start_dt": "2019-05-01", "end_dt": "2019-07-01", "end_row": 100, # Limit to first 100 rows }, ) assert not batter_result.isError batter_data = json.loads(batter_result.content[0].text) # Check if we got successful data or length limit error if "error" in batter_data: # If still too long even with end_row, use smaller limit batter_result = await session.call_tool( "get_statcast_batter_data", { "player_id": 514888, "start_dt": "2019-05-01", "end_dt": "2019-07-01", "end_row": 50, # Even smaller limit }, ) batter_data = json.loads(batter_result.content[0].text) assert "data" in batter_data assert len(batter_data["data"]) > 0 except Exception as e: raise Exception(f"Exception occured in pre-req batter data: {e}") from e # Create spraychart try: spraychart_result = await session.call_tool( "create_spraychart_plot", { "data": batter_data, "team_stadium": "astros", "title": "Jose Altuve: May-June 2019", "colorby": "events", "size": 120, "width": 1024, "height": 1024, }, ) except Exception as e: raise Exception(f"Exception occured in spraychart tool: {e}") from e assert not spraychart_result.isError result_json = json.loads(spraychart_result.content[0].text) # Verify response structure assert result_json["plot_type"] == "spraychart" assert "image_base64" in result_json assert len(result_json["image_base64"]) > 100 assert result_json["hit_count"] > 0 assert result_json["stadium"] == "astros" assert result_json["title"] == "Jose Altuve: May-June 2019" assert "metadata" in result_json assert result_json["metadata"]["colorby"] == "events" assert isinstance(result_json["metadata"]["events"], dict) # Display the image if SHOW_IMAGE environment variable is set to true if os.environ.get("SHOW_IMAGE", "false").lower() == "true": display_base64_image(result_json["image_base64"]) @pytest.mark.asyncio async def test_image_create_spraychart_plot_votto_aquino(): """Test spraychart with Joey Votto vs. Aristedes Aquino data.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() try: # Get statcast batter data for Joey Votto (458015) votto_result = await session.call_tool( "get_statcast_batter_data", { "player_id": 458015, "start_dt": "2019-08-01", "end_dt": "2019-10-01", "end_row": 50, }, ) assert not votto_result.isError votto_data = json.loads(votto_result.content[0].text) # Get statcast batter data for Aristedes Aquino (606157) aquino_result = await session.call_tool( "get_statcast_batter_data", { "player_id": 606157, "start_dt": "2019-08-01", "end_dt": "2019-10-01", "end_row": 50, }, ) assert not aquino_result.isError aquino_data = json.loads(aquino_result.content[0].text) # Combine the data (concatenate the data arrays) combined_data = {"data": votto_data["data"] + aquino_data["data"]} assert len(combined_data["data"]) > 0 except Exception as e: raise Exception(f"Exception occurred in pre-req batter data: {e}") from e # Create spraychart try: spraychart_result = await session.call_tool( "create_spraychart_plot", { "data": combined_data, "team_stadium": "reds", "title": "Joey Votto vs. Aristedes Aquino", "colorby": "player_name", # Color by player "size": 120, "width": 1024, "height": 1024, }, ) except Exception as e: raise Exception(f"Exception occurred in spraychart tool: {e}") from e assert not spraychart_result.isError result_json = json.loads(spraychart_result.content[0].text) # Verify response structure assert result_json["plot_type"] == "spraychart" assert "image_base64" in result_json assert len(result_json["image_base64"]) > 100 assert result_json["hit_count"] > 0 assert result_json["stadium"] == "reds" assert result_json["title"] == "Joey Votto vs. Aristedes Aquino" assert "metadata" in result_json assert result_json["metadata"]["colorby"] == "player_name" assert isinstance(result_json["metadata"]["events"], dict) # Display the image if SHOW_IMAGE environment variable is set to true if os.environ.get("SHOW_IMAGE", "false").lower() == "true": display_base64_image(result_json["image_base64"]) @pytest.mark.asyncio async def test_image_create_bb_profile_plot(): """Test bb_profile plot recreating the example logic.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() try: # Get statcast data for specific date range - using smaller end_row to avoid length limit statcast_result = await session.call_tool( "get_statcast_data", { "start_dt": "2018-05-01", "end_dt": "2018-05-04", "verbose": True, "parallel": True, "end_row": 100, # Smaller limit to avoid length issue }, ) assert not statcast_result.isError statcast_data = json.loads(statcast_result.content[0].text) # Check if we got successful data or still hit length limit if "error" in statcast_data: # If still too long, use even smaller limit statcast_result = await session.call_tool( "get_statcast_data", { "start_dt": "2018-05-01", "end_dt": "2018-05-04", "verbose": True, "parallel": True, "end_row": 50, # Even smaller limit }, ) statcast_data = json.loads(statcast_result.content[0].text) assert "data" in statcast_data assert len(statcast_data["data"]) > 0 except Exception as e: raise Exception(f"Exception occurred in pre-req statcast data: {e}") from e # Create bb_profile plot try: bb_profile_result = await session.call_tool( "create_bb_profile_plot", { "data": statcast_data, "parameter": "launch_angle", }, ) except Exception as e: raise Exception(f"Exception occurred in bb_profile tool: {e}") from e assert not bb_profile_result.isError result_json = json.loads(bb_profile_result.content[0].text) # Verify response structure assert result_json["plot_type"] == "bb_profile" assert "image_base64" in result_json assert len(result_json["image_base64"]) > 100 assert result_json["bb_count"] > 0 assert result_json["parameter"] == "launch_angle" assert "metadata" in result_json assert isinstance(result_json["metadata"]["bb_types"], dict) # Display the image if SHOW_IMAGE environment variable is set to true if os.environ.get("SHOW_IMAGE", "false").lower() == "true": display_base64_image(result_json["image_base64"]) @pytest.mark.asyncio async def test_image_plot_teams(): """Test plotting teams based on team batting data.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Get team batting data for 2023 result = await session.call_tool( "get_team_batting", {"start_season": 2023, "league": "all"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" # Create plot_teams visualization plot_result = await session.call_tool( "create_teams_plot", { "data": data, "x_axis": "HR", "y_axis": "BB", "title": "Team HR vs BB (2023)", }, ) # Verify successful plot response assert not plot_result.isError, "Expected successful plot response" assert plot_result.content, "No content returned from plot tool" assert plot_result.content[0].type == "text", "Expected text response" # Verify plot response structure plot_data = json.loads(plot_result.content[0].text) assert "plot_type" in plot_data, "Response should contain 'plot_type' key" assert plot_data["plot_type"] == "teams" assert "image_base64" in plot_data, "Response should contain 'image_base64' key" assert len(plot_data["image_base64"]) > 100, "Image data should be substantial" assert "team_count" in plot_data, "Response should contain 'team_count' key" assert plot_data["team_count"] > 0, "Should have team data" assert "x_axis" in plot_data, "Response should contain 'x_axis' key" assert plot_data["x_axis"] == "HR" assert "y_axis" in plot_data, "Response should contain 'y_axis' key" assert plot_data["y_axis"] == "BB" # Display the image if SHOW_IMAGE environment variable is set to true if os.environ.get("SHOW_IMAGE", "false").lower() == "true": display_base64_image(plot_data["image_base64"]) @pytest.mark.asyncio async def test_get_pitching_stats_bref(): """Test the get_pitching_stats_bref tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool("get_pitching_stats_bref", {"season": 2023}) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" assert data["count"] > 0, "Should have pitching data" @pytest.mark.asyncio async def test_get_pitching_stats_range(): """Test the get_pitching_stats_range tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_pitching_stats_range", {"start_dt": "2023-04-01", "end_dt": "2023-04-07"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_pitching_stats(): """Test the get_pitching_stats tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_pitching_stats", {"start_season": 2023, "qual": 50}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_schedule_and_record(): """Test the get_schedule_and_record tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_schedule_and_record", {"season": 2023, "team": "LAD"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_player_splits(): """Test the get_player_splits tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters - Mike Trout's Baseball Reference ID result = await session.call_tool( "get_player_splits", {"playerid": "troutmi01", "year": 2023}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_pybaseball_standings(): """Test the get_pybaseball_standings tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_pybaseball_standings", {"season": 2023}, ) # Verify successful response assert not result.isError, "Expected successful response" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" # Verify response structure data = json.loads(result.content[0].text) assert "data" in data, "Response should contain 'data' key" @pytest.mark.asyncio async def test_get_team_batting(): """Test the get_team_batting tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_team_batting", {"start_season": 2023, "league": "all"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_team_fielding(): """Test the get_team_fielding tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_team_fielding", {"start_season": 2023, "league": "all"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_team_pitching(): """Test the get_team_pitching tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_team_pitching", {"start_season": 2023, "league": "all"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_get_top_prospects(): """Test the get_top_prospects tool.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with valid parameters result = await session.call_tool( "get_top_prospects", {"team": "angels", "player_type": "batters"}, ) # Verify successful response or proper length limit handling assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation structure assert "length" in data, "Length limit response should contain 'length' key" assert "limit" in data, "Length limit response should contain 'limit' key" assert "total_rows" in data, "Length limit response should contain 'total_rows' key" else: # Verify successful response structure assert "data" in data, "Response should contain 'data' key" assert "count" in data, "Response should contain 'count' key" assert "columns" in data, "Response should contain 'columns' key" @pytest.mark.asyncio async def test_statcast_length_limit_exceeded(): """Test that statcast tools handle length limit properly when response is too large.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test get_statcast_data with a large date range that should exceed limit result = await session.call_tool( "get_statcast_data", {"start_dt": "2023-04-01", "end_dt": "2023-04-30"}, # Full month ) # Verify response structure - could be success or length limit error assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Verify correct length limitation assert "error" in data assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_data", { "start_dt": "2023-04-01", "end_dt": "2023-04-30", "end_row": new_row_estimate, }, # row limitation ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate @pytest.mark.asyncio async def test_statcast_batter_length_limit_handling(): """Test that get_statcast_batter_data handles length limit with large date range.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with Aaron Judge and a large date range result = await session.call_tool( "get_statcast_batter_data", { "player_id": 592450, # Aaron Judge "start_dt": "2022-04-01", "end_dt": "2022-10-31", # Full 2022 season }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Verify correct length limitation assert "error" in data assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_batter_data", { "player_id": 592450, "start_dt": "2022-04-01", "end_dt": "2022-10-31", "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate @pytest.mark.asyncio async def test_statcast_pitcher_length_limit_handling(): """Test that get_statcast_pitcher_data handles length limit with large date range.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with Gerrit Cole and a large date range result = await session.call_tool( "get_statcast_pitcher_data", { "player_id": 543037, # Gerrit Cole "start_dt": "2022-04-01", "end_dt": "2022-10-31", # Full 2022 season }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Verify correct length limitation assert "error" in data assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_pitcher_data", { "player_id": 543037, "start_dt": "2022-04-01", "end_dt": "2022-10-31", "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate @pytest.mark.asyncio async def test_statcast_batter_exitvelo_barrels_length_limit_handling(): """Test that get_statcast_batter_exitvelo_barrels handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with parameters that should trigger length limit (very low minBBE) result = await session.call_tool( "get_statcast_batter_exitvelo_barrels", { "year": 2023, "minBBE": 1, # Very low threshold to get many batters }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_batter_exitvelo_barrels", { "year": 2023, "minBBE": 1, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_pitcher_exitvelo_barrels_length_limit_handling(): """Test that get_statcast_pitcher_exitvelo_barrels handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with parameters that should trigger length limit (very low minBBE) result = await session.call_tool( "get_statcast_pitcher_exitvelo_barrels", { "year": 2023, "minBBE": 1, # Very low threshold to get many pitchers }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_pitcher_exitvelo_barrels", { "year": 2023, "minBBE": 1, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_batter_expected_stats_length_limit_handling(): """Test that get_statcast_batter_expected_stats handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with parameters that should trigger length limit (very low minPA) result = await session.call_tool( "get_statcast_batter_expected_stats", { "year": 2023, "minPA": 1, # Very low threshold to get many batters }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_batter_expected_stats", { "year": 2023, "minPA": 1, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_pitcher_expected_stats_length_limit_handling(): """Test that get_statcast_pitcher_expected_stats handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with parameters that should trigger length limit (very low minPA) result = await session.call_tool( "get_statcast_pitcher_expected_stats", { "year": 2023, "minPA": 1, # Very low threshold to get many pitchers }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_pitcher_expected_stats", { "year": 2023, "minPA": 1, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_batter_percentile_ranks_length_limit_handling(): """Test that get_statcast_batter_percentile_ranks handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with year that should trigger length limit result = await session.call_tool( "get_statcast_batter_percentile_ranks", { "year": 2023, }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_batter_percentile_ranks", { "year": 2023, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_pitcher_percentile_ranks_length_limit_handling(): """Test that get_statcast_pitcher_percentile_ranks handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with year that should trigger length limit result = await session.call_tool( "get_statcast_pitcher_percentile_ranks", { "year": 2023, }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_pitcher_percentile_ranks", { "year": 2023, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_batter_pitch_arsenal_length_limit_handling(): """Test that get_statcast_batter_pitch_arsenal handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with parameters that might exceed limit (low minPA) result = await session.call_tool( "get_statcast_batter_pitch_arsenal", { "year": 2023, "minPA": 1, # Very low threshold to get many batters }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_batter_pitch_arsenal", { "year": 2023, "minPA": 1, "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_pitcher_pitch_arsenal_length_limit_handling(): """Test that get_statcast_pitcher_pitch_arsenal handles length limit properly.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # Test with parameters that should trigger length limit (very low minP) result = await session.call_tool( "get_statcast_pitcher_pitch_arsenal", { "year": 2023, "minP": 1, # Very low threshold to get many pitchers "arsenal_type": "avg_speed", }, ) # Verify response structure assert not result.isError, "Expected successful response or length limit handling" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" data = json.loads(result.content[0].text) # Check if we got successful data or length limit error if "error" in data: # Verify correct length limitation assert "length" in data assert "limit" in data assert "total_rows" in data # Try new call with end_row (use conservative calculation) bytes_per_row = data["length"] // data["total_rows"] max_safe_rows = (data["limit"] * 0.8) // bytes_per_row # 80% of limit for safety new_row_estimate = max(1, int(max_safe_rows)) result = await session.call_tool( "get_statcast_pitcher_pitch_arsenal", { "year": 2023, "minP": 1, "arsenal_type": "avg_speed", "end_row": new_row_estimate, }, ) data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == new_row_estimate else: # If no length limit hit, just verify we got valid data assert "data" in data assert "count" in data assert "columns" in data @pytest.mark.asyncio async def test_statcast_single_game_with_truncation(): """Test that get_statcast_single_game works with start_row and end_row parameters.""" params = simplify_session_setup() async with stdio_client(params) as (read_stream, write_stream): async with ClientSession(read_stream, write_stream) as session: await session.initialize() # First, get a game that might have a lot of data result = await session.call_tool( "get_statcast_single_game", {"game_pk": 717953, "start_row": 0, "end_row": 10}, # Just first 10 rows ) # Verify successful response assert not result.isError, "Expected successful response" assert result.content, "No content returned from tool" assert result.content[0].type == "text", "Expected text response" # Verify successful response with truncation data = json.loads(result.content[0].text) assert "data" in data assert "count" in data assert "total_rows" in data assert "columns" in data assert data["count"] == 10

Latest Blog Posts

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/etweisberg/mlb-mcp'

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