Soccer MCP Server

by obinopaul
Verified
from mcp.server.fastmcp import FastMCP import time import signal import sys from pydantic import BaseModel, Field, field_validator, ValidationError from typing import Optional, List, Dict, Any from datetime import datetime, timedelta import pandas as pd import os import requests # print(f"Python executable: {sys.executable}", file=sys.stderr) # print(f"Python path: {sys.path}", file=sys.stderr) print(f"Current working directory: {os.getcwd()}", file=sys.stderr) # Handle SIGINT (Ctrl+C) gracefully def signal_handler(sig, frame): print("Shutting down server gracefully...") sys.exit(0) signal.signal(signal.SIGINT, signal_handler) # Create an MCP server with increased timeout mcp = FastMCP( name="soccer_server", # host="127.0.0.1", # port=5000, # Add this to make the server more resilient timeout=30 # Increase timeout to 30 seconds ) @mcp.tool() def get_league_fixtures(league_id: int, season: int) -> Dict[str, Any]: """Retrieves all fixtures for a given league and season. Args: league_id (int): The ID of the league. season (int): The year of the season (e.g., 2023 for the 2023-2024 season). Returns: Dict[str, Any]: A dictionary containing fixture data or an error message. Key fields: * "response" (List[Dict[str, Any]]): A list of fixture dictionaries, as returned by the API. * "error" (str): An error message if the request failed. Example: ```python get_league_fixtures(league_id=39, season=2023) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } fixtures_url = f"{base_url}/fixtures" fixtures_params = {"league": league_id, "season": season} try: response = requests.get(fixtures_url, headers=headers, params=fixtures_params, timeout=30) # Increased timeout response.raise_for_status() # Raise HTTPError for bad responses (4xx or 5xx) return response.json() except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_league_id_by_name(league_name: str) -> Dict[str, Any]: """Retrieve the league ID for a given league name. This tool searches for a league by its name and returns its ID. It uses the `/leagues` endpoint of the API-Football API. **Args:** league_name (str): The name of the league (e.g., "Premier League", "La Liga"). **Returns:** Dict[str, Any]: A dictionary containing the league ID, or an error message. Key fields: * "league_id" (int): The ID of the league, if found. * "error" (str): An error message if the league is not found or an error occurs. **Example:** ``` get_league_id_by_name(league_name="Premier League") # Expected output (may vary): {"league_id": 39} ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } try: leagues_url = f"{base_url}/leagues" leagues_params = {"search": league_name} resp = requests.get(leagues_url, headers=headers, params=leagues_params, timeout=15) resp.raise_for_status() data = resp.json() if not data.get("response"): return {"error": f"No leagues found matching '{league_name}'."} league_id = data["response"][0]["league"]["id"] return {"league_id": league_id} except Exception as e: return {"error": str(e)} @mcp.tool() def get_all_leagues_id(country: Optional[List[str]] = None) -> Dict[str, Any]: """Retrieve a list of all football leagues with IDs, optionally filtered by country. This tool retrieves a list of football leagues and their IDs. It can be filtered by providing a list of country names. Uses the `/leagues` endpoint. **Args:** country (Optional[List[str]]): A list of country names to filter the leagues. Use ["all"] to retrieve leagues from all countries. If None (default), no filtering is applied (though this is the same behavior as ["all"]). **Returns:** Dict[str, Any]: A dictionary containing league information, or an error message. Key fields: * "leagues" (Dict[str, Dict[str, Any]]): A dictionary where keys are league names and values are dictionaries containing "league_id" and "country". * "error" (str): An error message if the request fails. **Example:** ```python get_all_leagues_id(country = ["England", "Spain"]) # Expected sample Output (will have many more entries): # { # "leagues": { # "Premier League": {"league_id": 39, "country": "England"}, # "La Liga": {"league_id": 140, "country": "Spain"}, # ... # } # } ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } try: leagues_url = f"{base_url}/leagues" response = requests.get(leagues_url, headers=headers, timeout=15) response.raise_for_status() data = response.json() leagues: Dict[str, Dict[str, Any]] = {} for league_info in data.get("response", []): league_name = league_info["league"]["name"] league_id = league_info["league"]["id"] league_country = league_info["country"]["name"] if country and "all" not in country: if league_country.lower() not in [c.lower() for c in country]: continue leagues[league_name] = { "league_id": league_id, "country": league_country } return {"leagues": leagues} except Exception as e: return {"error": str(e)} @mcp.tool() def get_standings(league_id: Optional[List[int]], season: List[int], team: Optional[int] = None) -> Dict[str, Any]: """Retrieve league standings for multiple leagues and seasons, optionally filtered by team. This tool retrieves the standings table for one or more leagues, across multiple seasons. It can optionally filter the results to show standings for a specific team. Uses the `/standings` endpoint. **Args:** league_id (Optional[List[int]]): A list of league IDs to retrieve standings for. season (List[int]): A list of 4-digit season years (e.g., [2021, 2022]). team (Optional[int]): A specific team ID to filter the standings. **Returns:** Dict[str, Any]: A dictionary containing the standings, or an error message. The structure is: * `{league_id: {season: standings_data}}` `standings_data` is the raw JSON response from the API for the given league and season. If an error occurs for a specific league/season, the `standings_data` will be `{"error": "error message"}`. **Example:** ```python get_standings(league_id=[39, 140], season=[2022, 2023], team=None) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } results: Dict[int, Dict[int, Any]] = {} leagues = league_id if league_id else [] for league in leagues: results[league] = {} for year in season: url = f"{base_url}/standings" params = {"season": year, "league": league} if team is not None: params["team"] = team try: response = requests.get(url, headers=headers, params=params, timeout=30) response.raise_for_status() results[league][year] = response.json() except Exception as e: results[league][year] = {"error": str(e)} return results @mcp.tool() def get_player_id(player_name: str) -> Dict[str, Any]: """Retrieve a list of player IDs and identifying information for players matching a given name. This tool searches for players by either their first *or* last name and returns a list of potential matches. It includes identifying information to help disambiguate players. Uses the `/players/profiles` endpoint. **Args:** player_name (str): The first *or* last name of the player (e.g., "Lionel" or "Messi"). Do *not* provide both first and last names. The name must be at least 3 characters long. **Returns:** Dict[str, Any]: A dictionary containing a list of players or an error message. Key fields: * "players" (List[Dict[str, Any]]): A list of dictionaries, each representing a player. Each player dictionary includes: * "player_id" (int): The player's ID. * "firstname" (str): The player's first name. * "lastname" (str): The player's last name. * "age" (int): The player's age. * "nationality" (str): The player's nationality. * "birth_date" (str): The player's birth date (YYYY-MM-DD). * "birth_place" (str): The player's birth place. * "birth_country" (str) * "height" (str): The player's height (e.g., "170 cm"). * "weight" (str): The player's weight (e.g., "68 kg"). * "error" (str): An error message if no players are found or an error occurs. **Example:** ``` get_player_id(player_name="Messi") ``` """ if " " in player_name.strip(): return {"error": "Please enter only the first *or* last name, not both."} if len(player_name.strip()) < 3: return {"error": "The name must be at least 3 characters long."} api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" url = f"{base_url}/players/profiles" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key, } params = { "search": player_name, } try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() data = response.json() if not data.get("response"): return {"error": f"No players found matching '{player_name}'."} player_list = [] for item in data["response"]: player = item.get("player", {}) player_info = { "player_id": player.get("id"), "firstname": player.get("firstname"), "lastname": player.get("lastname"), "age": player.get("age"), "nationality": player.get("nationality"), "birth_date": player.get("birth", {}).get("date"), "birth_place": player.get("birth", {}).get("place"), "birth_country": player.get("birth", {}).get("country"), "height": player.get("height"), "weight": player.get("weight") } player_list.append(player_info) return {"players": player_list} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_player_profile(player_name: str) -> Dict[str, Any]: """Retrieve a single player's profile information by their last name. This tool retrieves a player's profile by searching for their last name. It uses the `/players/profiles` endpoint. **Args:** player_name (str): The last name of the player to look up. Must be >= 3 characters. **Returns:** Dict[str, Any]: The raw JSON response from the API, or a dictionary with an "error" key if the request fails. **Example:** ```python get_player_profile(player_name = "Messi") ``` """ if len(player_name.strip()) < 3: return {"error": "The name must be at least 3 characters long."} api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" url = f"{base_url}/players/profiles" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } params = { "search": player_name, "page": 1 # Fetch only the first page } try: response = requests.get(url, headers=headers, params=params, timeout=15) response.raise_for_status() return response.json() except Exception as e: return {"error": str(e)} @mcp.tool() def get_player_statistics(player_id: int, seasons: List[int], league_name: Optional[str] = None) -> Dict[str, Any]: """Retrieve detailed player statistics for given seasons and optional league name. This tool retrieves detailed player statistics, including advanced stats, for a specified player ID. It filters the results by a list of seasons and, optionally, by a league name. It uses the /players endpoint. **Args:** player_id (int): The ID of the player. seasons (List[int]): A list of seasons to get statistics for (4-digit years, e.g., [2021, 2022] or [2023]). league_name (Optional[str]): The name of the league (e.g., "Premier League"). If provided, statistics will be retrieved only for this league. If the league name cannot be found for a given season, an error will be included in the results for that season. **Returns:** Dict[str, Any]: A dictionary containing the player statistics or error messages. Key fields: * "player_statistics" (List[Dict[str, Any]]): A list of dictionaries, each representing player statistics for a specific season (and league, if specified). * "error" (str): An error message may be present *within* the `player_statistics` list if there was a problem fetching data for a specific season, or at the top level if no statistics at all could be retrieved. Each dictionary in "player_statistics" contains detailed statistics, grouped by category ("player", "team", "league", "games", "substitutes", "shots", "goals", "passes", "tackles", "duels", "dribbles", "fouls", "cards", "penalty"). """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if isinstance(seasons, int): seasons = [seasons] if league_name is not None and len(league_name.strip()) < 3: return {"error": "League name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" url = f"{base_url}/players" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key, } all_stats = [] def _get_league_id(league_name: str, season: int) -> Optional[int]: """Helper function to get the league ID from the league name.""" url = f"{base_url}/leagues" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key, } params = {"name": league_name, "season": season} try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() data = response.json() if not data.get("response"): return None for league_data in data["response"]: if league_data["league"]["name"].lower() == league_name.lower(): for league_season in league_data["seasons"]: if league_season["year"] == season: return league_data["league"]["id"] return None except requests.exceptions.RequestException: return None except Exception: return None # End of helper function for current_season in seasons: league_id = None if league_name: league_id = _get_league_id(league_name, current_season) if league_id is None: all_stats.append({ "error": f"Could not find league ID for '{league_name}' in season {current_season}." }) continue params: Dict[str, Any] = {"id": player_id, "season": current_season} if league_id: params["league"] = league_id try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() data = response.json() if not data.get("response"): continue for entry in data["response"]: player_info = entry.get("player", {}) for stats in entry.get("statistics", []): extracted_stats: Dict[str, Any] = { "player": { "id": player_info.get("id"), "name": player_info.get("name"), "photo": player_info.get("photo"), }, "team": { "id": stats.get("team", {}).get("id"), "name": stats.get("team", {}).get("name"), "logo": stats.get("team", {}).get("logo"), }, "league": { "id": stats.get("league", {}).get("id"), "name": stats.get("league", {}).get("name"), "season": stats.get("league", {}).get("season"), "country": stats.get("league", {}).get("country"), "flag": stats.get("league", {}).get("flag"), }, "games": { "appearances": stats.get("games", {}).get("appearences"), "lineups": stats.get("games", {}).get("lineups"), "minutes": stats.get("games", {}).get("minutes"), "position": stats.get("games", {}).get("position"), "rating": stats.get("games", {}).get("rating"), }, "substitutes": { "in": stats.get("substitutes", {}).get("in"), "out": stats.get("substitutes", {}).get("out"), "bench": stats.get("substitutes", {}).get("bench"), }, "shots": { "total": stats.get("shots", {}).get("total"), "on": stats.get("shots", {}).get("on"), }, "goals": { "total": stats.get("goals", {}).get("total"), "conceded": stats.get("goals", {}).get("conceded"), "assists": stats.get("goals", {}).get("assists"), "saves": stats.get("goals", {}).get("saves"), }, "passes": { "total": stats.get("passes", {}).get("total"), "key": stats.get("passes", {}).get("key"), "accuracy": stats.get("passes", {}).get("accuracy"), }, "tackles": { "total": stats.get("tackles", {}).get("total"), "blocks": stats.get("tackles", {}).get("blocks"), "interceptions": stats.get("tackles", {}).get("interceptions"), }, "duels": { "total": stats.get("duels", {}).get("total"), "won": stats.get("duels", {}).get("won"), }, "dribbles": { "attempts": stats.get("dribbles", {}).get("attempts"), "success": stats.get("dribbles", {}).get("success"), }, "fouls": { "drawn": stats.get("fouls", {}).get("drawn"), "committed": stats.get("fouls", {}).get("committed"), }, "cards": { "yellow": stats.get("cards", {}).get("yellow"), "red": stats.get("cards", {}).get("red"), }, "penalty": { "won": stats.get("penalty", {}).get("won"), "committed": stats.get("penalty", {}).get("committed"), "scored": stats.get("penalty", {}).get("scored"), "missed": stats.get("penalty", {}).get("missed"), "saved": stats.get("penalty", {}).get("saved"), }, } all_stats.append(extracted_stats) except requests.exceptions.RequestException as e: all_stats.append({"error": f"Request failed for season {current_season}: {e}"}) except Exception as e: all_stats.append({"error": f"An unexpected error occurred for season {current_season}: {e}"}) if not all_stats: return { "error": f"No statistics found for player ID {player_id} for the specified seasons/league." } return {"player_statistics": all_stats} @mcp.tool() def get_player_statistics_2(player_id: int, seasons: List[int], league_id: Optional[int] = None) -> Dict[str, Any]: """Retrieve detailed player statistics for given seasons and optional league ID. This tool retrieves detailed player statistics, including advanced stats, for a specified player ID. It filters the results by a list of seasons and, optionally, by a league ID. It uses the /players endpoint. **Args:** player_id (int): The ID of the player. seasons (List[int]): A list of seasons to get statistics for (4-digit years, e.g., [2021, 2022] or [2023]). league_id (Optional[int]): The ID of the league. **Returns:** Dict[str, Any]: A dictionary containing the player statistics or error messages. Key fields: * "player_statistics" (List[Dict[str, Any]]): A list of dictionaries where each dictionary contains statistics for a single season. * "error" (str): An error is returned if the API key is missing, a season is invalid, or if no statistics are found. Each dictionary in "player_statistics" contains detailed statistics, grouped by category ("player", "team", "league", "games", "substitutes", "shots", "goals", "passes", "tackles", "duels", "dribbles", "fouls", "cards", "penalty"). """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if isinstance(seasons, int): seasons = [seasons] base_url = "https://api-football-v1.p.rapidapi.com/v3" url = f"{base_url}/players" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key, } all_stats = [] for current_season in seasons: params: Dict[str, Any] = {"id": player_id, "season": current_season} if league_id: params["league"] = league_id try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() data = response.json() if not data.get("response"): continue for entry in data["response"]: player_info = entry.get("player", {}) for stats in entry.get("statistics", []): extracted_stats: Dict[str, Any] = { "player": { "id": player_info.get("id"), "name": player_info.get("name"), "photo": player_info.get("photo"), }, "team": { "id": stats.get("team", {}).get("id"), "name": stats.get("team", {}).get("name"), "logo": stats.get("team", {}).get("logo"), }, "league": { "id": stats.get("league", {}).get("id"), "name": stats.get("league", {}).get("name"), "season": stats.get("league", {}).get("season"), "country": stats.get("league", {}).get("country"), "flag": stats.get("league", {}).get("flag"), }, "games": { "appearances": stats.get("games", {}).get("appearences"), "lineups": stats.get("games", {}).get("lineups"), "minutes": stats.get("games", {}).get("minutes"), "position": stats.get("games", {}).get("position"), "rating": stats.get("games", {}).get("rating"), }, "substitutes": { "in": stats.get("substitutes", {}).get("in"), "out": stats.get("substitutes", {}).get("out"), "bench": stats.get("substitutes", {}).get("bench"), }, "shots": { "total": stats.get("shots", {}).get("total"), "on": stats.get("shots", {}).get("on"), }, "goals": { "total": stats.get("goals", {}).get("total"), "conceded": stats.get("goals", {}).get("conceded"), "assists": stats.get("goals", {}).get("assists"), "saves": stats.get("goals", {}).get("saves"), }, "passes": { "total": stats.get("passes", {}).get("total"), "key": stats.get("passes", {}).get("key"), "accuracy": stats.get("passes", {}).get("accuracy"), }, "tackles": { "total": stats.get("tackles", {}).get("total"), "blocks": stats.get("tackles", {}).get("blocks"), "interceptions": stats.get("tackles", {}).get("interceptions"), }, "duels": { "total": stats.get("duels", {}).get("total"), "won": stats.get("duels", {}).get("won"), }, "dribbles": { "attempts": stats.get("dribbles", {}).get("attempts"), "success": stats.get("dribbles", {}).get("success"), }, "fouls": { "drawn": stats.get("fouls", {}).get("drawn"), "committed": stats.get("fouls", {}).get("committed"), }, "cards": { "yellow": stats.get("cards", {}).get("yellow"), "red": stats.get("cards", {}).get("red"), }, "penalty": { "won": stats.get("penalty", {}).get("won"), "committed": stats.get("penalty", {}).get("committed"), "scored": stats.get("penalty", {}).get("scored"), "missed": stats.get("penalty", {}).get("missed"), "saved": stats.get("penalty", {}).get("saved"), }, } all_stats.append(extracted_stats) except requests.exceptions.RequestException as e: return {"error": f"Request failed for season {current_season}: {e}"} except Exception as e: return {"error": f"An unexpected error occurred for season {current_season}: {e}"} if not all_stats: return { "error": f"No statistics found for player ID {player_id} for the specified seasons/league." } return {"player_statistics": all_stats} @mcp.tool() def get_team_fixtures(team_name: str, type: str = "upcoming", limit: int = 5) -> Dict[str, Any]: """Given a team name, returns either the last N or the next N fixtures for that team. **Args:** team_name (str): The team's name to search for. Must be >= 3 characters. type (str, optional): Either 'past' or 'upcoming' fixtures. Defaults to 'upcoming'. limit (int, optional): How many fixtures to retrieve. Defaults to 5. **Returns:** Dict[str, Any]: A dictionary containing the fixture data or an error message. Key fields: * "response" (List[Dict[str,Any]]): List of fixtures, if found. * "error" (str): Error message if the request failed, or the team wasn't found. The structure of each fixture in `response` is the raw JSON response from the API. **Example:** ```python get_team_fixtures(team_name="Manchester United", type="past", limit=3) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(team_name.strip()) < 3: return {"error": "The team name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } # Step 1: Find the Team ID search_url = f"{base_url}/teams" search_params = {"search": team_name} try: search_resp = requests.get(search_url, headers=headers, params=search_params, timeout=15) search_resp.raise_for_status() teams_data = search_resp.json() if not teams_data.get("response"): return {"error": f"No teams found matching '{team_name}'."} # Just pick the first matching team for simplicity first_team = teams_data["response"][0] team_id = first_team["team"]["id"] # Step 2: Fetch fixtures fixtures_url = f"{base_url}/fixtures" fixtures_params = {"team": team_id} if type.lower() == "past": fixtures_params["last"] = limit elif type.lower() == "upcoming": fixtures_params["next"] = limit else: return {"error": "The 'type' parameter must be either 'past' or 'upcoming'."} fixtures_resp = requests.get(fixtures_url, headers=headers, params=fixtures_params, timeout=15) fixtures_resp.raise_for_status() return fixtures_resp.json() except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_fixture_statistics(fixture_id: int) -> Dict[str, Any]: """Retrieves detailed statistics for a specific fixture (game). **Args:** fixture_id (int): The numeric ID of the fixture/game. **Returns:** Dict[str, Any]: A dictionary containing fixture statistics or an error message. Key fields: * "response" (List[Dict[str, Any]]): List of team statistics, if found. * "error" (str): Error message, if any occurred. The structure of the data within `response` is the raw API response. **Example:** ```python get_fixture_statistics(fixture_id=867946) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" url = f"{base_url}/fixtures/statistics" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } params = {"fixture": fixture_id} try: response = requests.get(url, headers=headers, params=params, timeout=15) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_team_fixtures_by_date_range(team_name: str, from_date: str, to_date: str, season: str) -> Dict[str, Any]: """Retrieve all fixtures for a given team within a date range. **Args:** team_name (str): Team name to search for (e.g. 'Arsenal', 'Barcelona'). from_date (str): Start date in YYYY-MM-DD format (e.g. '2023-08-01'). to_date (str): End date in YYYY-MM-DD format (e.g. '2023-08-31'). season (str): Season in YYYY format. **Returns:** Dict[str, Any]: A dictionary containing the fixture data or an error message. Key fields: * "response" (List[Dict[str, Any]]): A list of fixture dictionaries. * "error" (str): An error message. The structure of each dictionary in `response` is the raw API response. **Example:** ```python get_team_fixtures_by_date_range( team_name="Liverpool", from_date="2023-09-01", to_date="2023-09-30", season="2023" ) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(team_name.strip()) < 3: return {"error": "The team name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } # Step 1: find team ID teams_url = f"{base_url}/teams" teams_params = {"search": team_name} try: resp = requests.get(teams_url, headers=headers, params=teams_params, timeout=15) resp.raise_for_status() data = resp.json() if not data.get("response"): return {"error": f"No team found matching '{team_name}'."} team_id = data["response"][0]["team"]["id"] # Step 2: fetch fixtures in date range fixtures_url = f"{base_url}/fixtures" fixtures_params = { "team": team_id, "from": from_date, "to": to_date, "season": season } resp_fixtures = requests.get(fixtures_url, headers=headers, params=fixtures_params, timeout=15) resp_fixtures.raise_for_status() return resp_fixtures.json() except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error":f"An unexpected error occurred: {e}"} @mcp.tool() def get_fixture_events(fixture_id: int) -> Dict[str, Any]: """Retrieves all in-game events for a given fixture ID (e.g. goals, cards, subs). **Args:** fixture_id (int): Numeric ID of the fixture whose events you want. **Returns:** Dict[str, Any]: A dictionary containing the fixture events or an error message. Key fields: * "response" (List[Dict[str, Any]]): List of events, if found. * "error" (str): Error message if the request failed. The structure of the data within `response` is the raw API response. **Example:** ```python get_fixture_events(fixture_id=867946) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" url = f"{base_url}/fixtures/events" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } params = {"fixture": fixture_id} try: response = requests.get(url, headers=headers, params=params, timeout=15) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_multiple_fixtures_stats(fixture_ids: List[int]) -> Dict[str, Any]: """Retrieves stats (shots, possession, etc.) for multiple fixtures at once. **Args:** fixture_ids (List[int]): A list of numeric fixture IDs. **Returns:** Dict[str, Any]: A dictionary containing the statistics for each fixture, or error messages. Key fields: * "fixtures_statistics" (List[Dict[str, Any]]): A list of dictionaries, where each dictionary contains the stats for a fixture (keyed by fixture ID) or an error for that fixture. **Example:** ```python get_multiple_fixtures_stats(fixture_ids=[867946, 867947, 867948]) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } combined_results = [] for f_id in fixture_ids: try: url = f"{base_url}/fixtures/statistics" params = {"fixture": f_id} resp = requests.get(url, headers=headers, params=params, timeout=15) resp.raise_for_status() data = resp.json() combined_results.append({f_id: data}) except requests.exceptions.RequestException as e: combined_results.append({f_id: {"error": f"Request failed: {e}"}}) except Exception as e: combined_results.append({f_id: {"error": f"An unexpected error occurred: {e}"}}) return {"fixtures_statistics": combined_results} @mcp.tool() def get_league_schedule_by_date(league_name: str, date: List[str], season: str) -> Dict[str, Any]: """Retrieves the schedule (fixtures) for a given league on one or multiple specified dates. **Args:** league_name (str): Name of the league (e.g., 'Premier League', 'La Liga'). date (List[str]): List of dates in YYYY-MM-DD format (e.g., ['2024-03-08', '2024-03-09']). season (str): Season in YYYY format (e.g., '2023'). **Returns:** Dict[str, Any]: A dictionary where each key is a date from the input `date` list, and the value is the API response for that date, or an error message. **Example:** ```python get_league_schedule_by_date( league_name="Premier League", date=["2024-03-08", "2024-03-09"], season="2023" ) ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(league_name.strip()) < 3: return {"error": "The league name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } # Step 1: Get league ID by searching name try: leagues_url = f"{base_url}/leagues" leagues_params = {"search": league_name, "season": season} # Include season in league search resp = requests.get(leagues_url, headers=headers, params=leagues_params, timeout=15) resp.raise_for_status() data = resp.json() if not data.get("response"): return {"error": f"No leagues found matching '{league_name}' for season {season}."} # Find the correct league and season league_id = None for league_data in data["response"]: if league_data["league"]["name"].lower() == league_name.lower(): for league_season in league_data["seasons"]: if str(league_season["year"]) == season: league_id = league_data["league"]["id"] break if league_id: break if not league_id: return {"error": f"Could not find {league_name} for season {season}."} results = {} for match_date in date: # Step 2: Get fixtures for that league & date fixtures_url = f"{base_url}/fixtures" fixtures_params = { "league": league_id, "date": match_date, "season": season } resp_fixtures = requests.get(fixtures_url, headers=headers, params=fixtures_params, timeout=15) resp_fixtures.raise_for_status() results[match_date] = resp_fixtures.json() # Store results per date return results # Return structured results with dates as keys except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_live_match_for_team(team_name: str) -> Dict[str, Any]: """Checks if a given team is currently playing live. **Args:** team_name (str): The team's name. Example: 'Arsenal'. Must be >= 3 chars. **Returns:** Dict[str, Any]: If a live match is found, returns a dictionary with a "live_fixture" key containing the fixture data. If no live match is found, returns a dictionary with a "message" key. If an error occurs, returns a dictionary with an "error" key. **Example:** ```python get_live_match_for_team(team_name="Chelsea") ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(team_name.strip()) < 3: return {"error": "The team name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } # Step 1: find team ID try: teams_resp = requests.get( f"{base_url}/teams", headers=headers, params={"search": team_name}, timeout=15 ) teams_resp.raise_for_status() teams_data = teams_resp.json() if not teams_data.get("response"): return {"error": f"No team found matching '{team_name}'."} team_id = teams_data["response"][0]["team"]["id"] # Step 2: look for live matches fixtures_resp = requests.get( f"{base_url}/fixtures", headers=headers, params={"team": team_id, "live": "all"}, timeout=15 ) fixtures_resp.raise_for_status() fixtures_data = fixtures_resp.json() live_fixtures = fixtures_data.get("response", []) if not live_fixtures: return {"message": f"No live match found for '{team_name}' right now."} # Typically only 1, but if multiple, just return the first return {"live_fixture": live_fixtures[0]} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_live_stats_for_team(team_name: str) -> Dict[str, Any]: """Retrieves live in-game stats for a team currently in a match. **Args:** team_name (str): Team name to get live stats for. e.g., 'Arsenal'. **Returns:** Dict[str, Any]: If the team is playing live, returns a dictionary containing the `fixture_id` and `live_stats`. If no live match is found, returns a dictionary with a "message" key. If an error occurs, returns a dictionary with an "error" key. **Example:** ```python get_live_stats_for_team(team_name="Liverpool") ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(team_name.strip()) < 3: return {"error": "The team name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } try: # Step 1: get team ID teams_resp = requests.get( f"{base_url}/teams", headers=headers, params={"search": team_name}, timeout=15 ) teams_resp.raise_for_status() teams_data = teams_resp.json() if not teams_data.get("response"): return {"error": f"No team found matching '{team_name}'."} team_id = teams_data["response"][0]["team"]["id"] # Step 2: check for live fixtures fixtures_resp = requests.get( f"{base_url}/fixtures", headers=headers, params={"team": team_id, "live": "all"}, timeout=15 ) fixtures_resp.raise_for_status() fixtures_data = fixtures_resp.json() live_fixtures = fixtures_data.get("response", []) if not live_fixtures: return {"message": f"No live match for '{team_name}' right now."} fixture_id = live_fixtures[0]["fixture"]["id"] # Step 3: get stats for that fixture stats_resp = requests.get( f"{base_url}/fixtures/statistics", headers=headers, params={"fixture": fixture_id}, timeout=15 ) stats_resp.raise_for_status() stats_data = stats_resp.json() return {"fixture_id": fixture_id, "live_stats": stats_data} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_live_match_timeline(team_name: str) -> Dict[str, Any]: """Retrieves the real-time timeline of events for a team's current live match. **Args:** team_name (str): Team name. **Returns:** Dict[str, Any]: If the team is playing live, returns a dictionary containing `fixture_id` and `timeline_events`. If not, returns a dictionary with a "message" key. If an error occurs, it returns a dictionary with an "error" key. **Example:** ```python get_live_match_timeline(team_name="Manchester City") ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(team_name.strip()) < 3: return {"error": "The team name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } try: # Step 1: team ID teams_resp = requests.get( f"{base_url}/teams", headers=headers, params={"search": team_name}, timeout=15 ) teams_resp.raise_for_status() teams_data = teams_resp.json() if not teams_data.get("response"): return {"error": f"No team found matching '{team_name}'."} team_id = teams_data["response"][0]["team"]["id"] # Step 2: check live fixtures fixtures_resp = requests.get( f"{base_url}/fixtures", headers=headers, params={"team": team_id, "live": "all"}, timeout=15 ) fixtures_resp.raise_for_status() fixtures_data = fixtures_resp.json() live_fixtures = fixtures_data.get("response", []) if not live_fixtures: return {"message": f"No live match for '{team_name}' right now."} fixture_id = live_fixtures[0]["fixture"]["id"] # Step 3: get events timeline events_resp = requests.get( f"{base_url}/fixtures/events", headers=headers, params={"fixture": fixture_id}, timeout=15 ) events_resp.raise_for_status() events_data = events_resp.json() return {"fixture_id": fixture_id, "timeline_events": events_data} except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_league_info(league_name: str) -> Dict[str, Any]: """Retrieve information about a specific football league. **Args:** league_name (str): Name of the league (e.g., 'Champions League'). **Returns:** Dict[str, Any]: A dictionary containing league information or an error message. Key fields: * "response" (List[Dict[str,Any]]): A list of leagues that match the search, if found. * "error" (str): An error message if the request fails or no leagues are found. The structure of data in "response" is the raw API response. **Example:** ```python get_league_info(league_name="Premier League") ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(league_name.strip()) < 3: return {"error": "The league name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } # Fetch league information league_url = f"{base_url}/leagues" params = {"search": league_name} try: resp = requests.get(league_url, headers=headers, params=params, timeout=15) resp.raise_for_status() data = resp.json() if not data.get("response"): return {"error": f"No leagues found matching '{league_name}'."} return data except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} @mcp.tool() def get_team_info(team_name: str) -> Dict[str, Any]: """Retrieve basic information about a specific football team. **Args:** team_name (str): Name of the team (e.g., 'Manchester United'). **Returns:** Dict[str, Any]: A dictionary containing team information or an error message. Key fields: * "response" (List[Dict[str,Any]]): List of teams that match the search name. * "error" (str): If the API request failed or the team is not found. The structure of data in "response" is the raw API response. **Example:** ```python get_team_info(team_name="Real Madrid") ``` """ api_key = os.getenv("RAPID_API_KEY_FOOTBALL") if not api_key: return {"error": "RAPID_API_KEY_FOOTBALL environment variable not set."} if len(team_name.strip()) < 3: return {"error": "The team name must be at least 3 characters long."} base_url = "https://api-football-v1.p.rapidapi.com/v3" headers = { "x-rapidapi-host": "api-football-v1.p.rapidapi.com", "x-rapidapi-key": api_key } # Fetch team information teams_url = f"{base_url}/teams" teams_params = {"search": team_name} try: resp = requests.get(teams_url, headers=headers, params=teams_params, timeout=15) resp.raise_for_status() data = resp.json() if not data.get("response"): return {"error": f"No team found matching '{team_name}'."} return data except requests.exceptions.RequestException as e: return {"error": f"Request failed: {e}"} except Exception as e: return {"error": f"An unexpected error occurred: {e}"} if __name__ == "__main__": try: print("Starting MCP server 'soccer_server' on 127.0.0.1:5000") # Use this approach to keep the server running mcp.run() except Exception as e: print(f"Error: {e}") # Sleep before exiting to give time for error logs time.sleep(5)