Skip to main content
Glama
gedin-eth

College Football MCP

by gedin-eth
cfbd_api.py16.6 kB
""" CollegeFootballData API Client This module handles interactions with the CollegeFootballData API to retrieve player and team statistics. """ import requests import logging from typing import Optional, Dict, Any, List from datetime import datetime logger = logging.getLogger(__name__) CFBD_API_BASE_URL = "https://api.collegefootballdata.com" def search_players( api_key: str, search_term: str, team: Optional[str] = None ) -> Optional[List[Dict[str, Any]]]: """ Search for players by name. Args: api_key: CFBD API key search_term: Player name to search for team: Optional team name to narrow search Returns: List of matching players or None if request fails """ if not api_key: logger.error("CFB_API_KEY is not set") return None url = f"{CFBD_API_BASE_URL}/player/search" headers = { "Authorization": f"Bearer {api_key}", "Accept": "application/json" } params = { "searchTerm": search_term } if team: params["team"] = team try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: logger.error(f"Error searching players: {e}") return None def get_player_game_stats( api_key: str, player_id: int, year: Optional[int] = None, season_type: str = "regular" ) -> Optional[List[Dict[str, Any]]]: """ Get player statistics for games in a season. Args: api_key: CFBD API key player_id: Player ID from CFBD year: Season year (defaults to current year if not provided) season_type: Type of season (regular, postseason, both) Returns: List of game statistics or None if request fails """ if not api_key: logger.error("CFB_API_KEY is not set") return None if not year: year = datetime.now().year url = f"{CFBD_API_BASE_URL}/stats/player/season" headers = { "Authorization": f"Bearer {api_key}", "Accept": "application/json" } params = { "playerId": player_id, "year": year, "seasonType": season_type } try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: logger.error(f"Error fetching player game stats: {e}") return None def get_player_game_logs( api_key: str, player_id: int, year: Optional[int] = None ) -> Optional[List[Dict[str, Any]]]: """ Get detailed game-by-game statistics for a player. This endpoint provides game logs with detailed stats per game. Args: api_key: CFBD API key player_id: Player ID from CFBD year: Season year (defaults to current year if not provided) Returns: List of game logs or None if request fails """ if not api_key: logger.error("CFB_API_KEY is not set") return None if not year: year = datetime.now().year # Try the game stats endpoint which may have game-by-game data # If that doesn't work, we'll use the season stats and match with games url = f"{CFBD_API_BASE_URL}/stats/player/game" headers = { "Authorization": f"Bearer {api_key}", "Accept": "application/json" } params = { "playerId": player_id, "year": year } try: response = requests.get(url, headers=headers, params=params, timeout=10) if response.status_code == 200: return response.json() else: # Fallback: try to get season stats and games separately logger.warning(f"Game stats endpoint returned {response.status_code}, trying alternative approach") return None except requests.exceptions.RequestException as e: logger.error(f"Error fetching player game logs: {e}") return None def get_team_games( api_key: str, team: str, year: Optional[int] = None, season_type: str = "regular" ) -> Optional[List[Dict[str, Any]]]: """ Get games for a team in a season. Args: api_key: CFBD API key team: Team name year: Season year (defaults to current year if not provided) season_type: Type of season (regular, postseason, both) Returns: List of games or None if request fails """ if not api_key: logger.error("CFB_API_KEY is not set") return None if not year: year = datetime.now().year url = f"{CFBD_API_BASE_URL}/games" headers = { "Authorization": f"Bearer {api_key}", "Accept": "application/json" } params = { "team": team, "year": year, "seasonType": season_type } try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() return response.json() except requests.exceptions.RequestException as e: logger.error(f"Error fetching team games: {e}") return None def get_team_records( api_key: str, team: str, year: Optional[int] = None ) -> Optional[Dict[str, Any]]: """ Get team season records. Args: api_key: CFBD API key team: Team name year: Season year (defaults to current year if not provided) Returns: Team record data or None if request fails """ if not api_key: logger.error("CFB_API_KEY is not set") return None if not year: year = datetime.now().year url = f"{CFBD_API_BASE_URL}/records" headers = { "Authorization": f"Bearer {api_key}", "Accept": "application/json" } params = { "team": team, "year": year } try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() records = response.json() # Return first record if list, or the record itself if isinstance(records, list) and len(records) > 0: return records[0] return records except requests.exceptions.RequestException as e: logger.error(f"Error fetching team records: {e}") return None def get_team_rankings( api_key: str, team: Optional[str] = None, year: Optional[int] = None, week: Optional[int] = None, season_type: str = "regular", poll: str = "ap" ) -> Optional[List[Dict[str, Any]]]: """ Get team rankings from polls. Args: api_key: CFBD API key team: Optional team name to filter year: Season year (defaults to current year if not provided) week: Week number (defaults to latest if not provided) season_type: Type of season (regular, postseason) poll: Poll type (ap, coaches, cfp) Returns: List of rankings or None if request fails """ if not api_key: logger.error("CFB_API_KEY is not set") return None if not year: year = datetime.now().year url = f"{CFBD_API_BASE_URL}/rankings" headers = { "Authorization": f"Bearer {api_key}", "Accept": "application/json" } params = { "year": year, "seasonType": season_type, "poll": poll } if week: params["week"] = week try: response = requests.get(url, headers=headers, params=params, timeout=10) response.raise_for_status() rankings = response.json() # Filter by team if provided if team and isinstance(rankings, list): team_lower = team.lower() for ranking_entry in rankings: polls = ranking_entry.get("polls", []) for poll_data in polls: rankings_list = poll_data.get("ranks", []) for rank_entry in rankings_list: if rank_entry.get("school", "").lower() == team_lower: return rank_entry return None return rankings except requests.exceptions.RequestException as e: logger.error(f"Error fetching team rankings: {e}") return None def format_team_recent_results( team_name: str, games: List[Dict[str, Any]] ) -> Dict[str, Any]: """ Format team recent game results. Args: team_name: Name of the team games: List of game data (should be sorted by date, most recent first) Returns: Formatted response with last 5 games including scores and outcomes """ from src.team_normalizer import normalize_team_name # Sort games by date (most recent first) # CFBD API typically returns games in chronological order, so reverse for most recent # Handle both camelCase and snake_case for date fields sorted_games = sorted( games, key=lambda x: x.get("startDate") or x.get("start_date", "") or x.get("week", 0), reverse=True ) # Take the last 5 games recent_games = sorted_games[:5] if len(sorted_games) > 5 else sorted_games # Normalize team name for matching team_normalized = normalize_team_name(team_name) team_lower = team_name.lower() formatted_games = [] for game in recent_games: # CFBD API uses camelCase, try both formats home_team = game.get("homeTeam") or game.get("home_team", "") away_team = game.get("awayTeam") or game.get("away_team", "") home_points = game.get("homePoints") or game.get("home_points") away_points = game.get("awayPoints") or game.get("away_points") # Determine if the requested team is home - use flexible matching home_normalized = normalize_team_name(home_team) away_normalized = normalize_team_name(away_team) home_lower = home_team.lower() away_lower = away_team.lower() is_home = ( team_normalized == home_normalized or team_normalized in home_normalized or home_normalized in team_normalized or team_lower == home_lower or team_lower in home_lower or home_lower in team_lower ) team_score = home_points if is_home else away_points opponent_score = away_points if is_home else home_points opponent = away_team if is_home else home_team # Determine result if team_score is not None and opponent_score is not None: if team_score > opponent_score: result = "Win" elif team_score < opponent_score: result = "Loss" else: result = "Tie" else: result = "Scheduled" # Game hasn't been played yet # Handle both camelCase and snake_case for date start_date = game.get("startDate") or game.get("start_date") or game.get("week", "Unknown") game_data = { "date": start_date, "opponent": opponent, "location": "Home" if is_home else "Away", "team_score": team_score, "opponent_score": opponent_score, "result": result, "game_id": game.get("id") } formatted_games.append(game_data) # Calculate win/loss record from recent games wins = sum(1 for g in formatted_games if g["result"] == "Win") losses = sum(1 for g in formatted_games if g["result"] == "Loss") ties = sum(1 for g in formatted_games if g["result"] == "Tie") return { "team": team_name, "recent_results": formatted_games, "recent_record": f"{wins}-{losses}" + (f"-{ties}" if ties > 0 else ""), "total_games": len(games) } def format_team_info( team_name: str, record: Optional[Dict[str, Any]], rankings: Optional[Dict[str, Any]] = None ) -> Dict[str, Any]: """ Format team season information. Args: team_name: Name of the team record: Team record data from CFBD API rankings: Optional rankings data Returns: Formatted response with team season overview """ result = { "team": team_name, "year": datetime.now().year } # Extract record information if record: overall = record.get("total", {}) conference = record.get("conferenceGames", {}) result["overall_record"] = f"{overall.get('wins', 0)}-{overall.get('losses', 0)}" if overall.get("ties", 0) > 0: result["overall_record"] += f"-{overall.get('ties', 0)}" if conference: result["conference_record"] = f"{conference.get('wins', 0)}-{conference.get('losses', 0)}" if conference.get("ties", 0) > 0: result["conference_record"] += f"-{conference.get('ties', 0)}" result["conference"] = record.get("conference", "Unknown") else: result["overall_record"] = "Unknown" result["conference_record"] = None result["conference"] = None # Extract rankings result["rankings"] = {} if rankings: rank = rankings.get("rank") if rank: result["rankings"]["ap_poll"] = rank else: result["rankings"]["ap_poll"] = None # Note: Could fetch coaches and CFP polls separately if needed result["rankings"]["coaches_poll"] = None result["rankings"]["cfp_rank"] = None return result def format_player_recent_stats( player_name: str, team: Optional[str], games: List[Dict[str, Any]] ) -> Dict[str, Any]: """ Format player statistics from recent games. Args: player_name: Name of the player team: Team name (if available) games: List of game statistics (should be sorted by date, most recent first) Returns: Formatted response with last 5 games """ # Take the last 5 games (assuming they're sorted by date) recent_games = games[:5] if len(games) > 5 else games formatted_games = [] for game in recent_games: game_data = { "date": game.get("week", "Unknown"), "opponent": game.get("opponent", "Unknown"), "game_id": game.get("gameId"), } # Extract relevant stats based on position # CFBD API returns different stats for different positions stats = {} # Passing stats if "passing" in game or "passYards" in game: stats["passing_yards"] = game.get("passYards") or game.get("passing", {}).get("yards", 0) stats["pass_touchdowns"] = game.get("passTDs") or game.get("passing", {}).get("touchdowns", 0) stats["interceptions"] = game.get("interceptions") or game.get("passing", {}).get("interceptions", 0) stats["completions"] = game.get("completions") or game.get("passing", {}).get("completions", 0) stats["attempts"] = game.get("attempts") or game.get("passing", {}).get("attempts", 0) # Rushing stats if "rushing" in game or "rushYards" in game: stats["rushing_yards"] = game.get("rushYards") or game.get("rushing", {}).get("yards", 0) stats["rush_touchdowns"] = game.get("rushTDs") or game.get("rushing", {}).get("touchdowns", 0) stats["carries"] = game.get("carries") or game.get("rushing", {}).get("carries", 0) # Receiving stats if "receiving" in game or "recYards" in game: stats["receiving_yards"] = game.get("recYards") or game.get("receiving", {}).get("yards", 0) stats["receptions"] = game.get("receptions") or game.get("receiving", {}).get("receptions", 0) stats["rec_touchdowns"] = game.get("recTDs") or game.get("receiving", {}).get("touchdowns", 0) # General stats stats["total_touchdowns"] = game.get("totalTDs", 0) stats["fumbles"] = game.get("fumbles", 0) game_data["stats"] = stats formatted_games.append(game_data) return { "player": player_name, "team": team or "Unknown", "recent_games": formatted_games, "total_games": len(games) }

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/gedin-eth/cfb-mcp'

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