UNPKG

@bdmarvin/mcp-server-sleeper

Version:

A Model Context Protocol (MCP) server providing access to the Sleeper Fantasy Football API.

249 lines (248 loc) 13.3 kB
import axios from 'axios'; import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; // Added for ESM __dirname equivalent const BASE_URL = 'https://api.sleeper.app/v1'; // Helper function to safely get a value from a nested path function getNestedValue(obj, pathString) { if (typeof pathString !== 'string') return undefined; return pathString.split('.').reduce((acc, part) => acc && acc[part], obj); } // ESM equivalent for __dirname const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); export class SleeperService { allPlayersData = null; constructor() { this.loadPlayersFromFile(); } loadPlayersFromFile() { try { const playersJsonPath = path.resolve(__dirname, 'players.json'); console.error(`Sleeper Service: Attempting to load players.json from: ${playersJsonPath}`); if (fs.existsSync(playersJsonPath)) { const fileContent = fs.readFileSync(playersJsonPath, 'utf-8'); console.error(`Sleeper Service: players.json file content length: ${fileContent.length}`); this.allPlayersData = JSON.parse(fileContent); console.error('Sleeper Service: Player data parsed successfully from local players.json.'); // Enhanced logging for data structure if (this.allPlayersData && typeof this.allPlayersData === 'object') { const keys = Object.keys(this.allPlayersData); console.error(`Sleeper Service: Top-level keys in players.json (first 5): ${keys.slice(0, 5).join(', ')}`); if (keys.length > 0 && this.allPlayersData[keys[0]] && typeof this.allPlayersData[keys[0]] === 'object') { console.error(`Sleeper Service: Structure of the first player entry (key: ${keys[0]}): ${JSON.stringify(this.allPlayersData[keys[0]], null, 2).substring(0, 300)}...`); } else if (keys.length === 0) { console.error('Sleeper Service: players.json was parsed but resulted in an empty object (no keys).'); } else { console.error('Sleeper Service: The first entry in players.json is not structured as expected (not an object), or players.json is not a player ID map.'); console.error(`Sleeper Service: Value of the first key (${keys[0]}): ${JSON.stringify(this.allPlayersData[keys[0]])}`); } } else { console.error('Sleeper Service: players.json did not parse into an object.'); } } else { console.error(`Sleeper Service: players.json not found at ${playersJsonPath}. get_all_players will return empty.`); this.allPlayersData = {}; } } catch (error) { console.error('Sleeper Service: Error loading or parsing players.json:', error); this.allPlayersData = {}; } } async getUser(identifier) { const response = await axios.get(`${BASE_URL}/user/${identifier}`); return response.data; } async getUserLeagues(userId, sport, season) { const response = await axios.get(`${BASE_URL}/user/${userId}/leagues/${sport}/${season}`); return response.data; } async getLeague(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}`); return response.data; } async getLeagueRosters(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/rosters`); return response.data; } async getLeagueUsers(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/users`); return response.data; } async getLeagueMatchups(leagueId, week) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/matchups/${week}`); return response.data; } async getLeagueWinnersBracket(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/winners_bracket`); return response.data; } async getLeagueLosersBracket(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/losers_bracket`); return response.data; } async getLeagueTransactions(leagueId, round) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/transactions/${round}`); return response.data; } async getLeagueTradedPicks(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/traded_picks`); return response.data; } async getNFLState() { const response = await axios.get(`${BASE_URL}/state/nfl`); return response.data; } async getAllPlayers(filters, fields) { if (!this.allPlayersData) { console.error('Sleeper Service (getAllPlayers): allPlayersData is null, attempting to load...'); this.loadPlayersFromFile(); } if (!this.allPlayersData || typeof this.allPlayersData !== 'object' || Object.keys(this.allPlayersData).length === 0) { console.error('Sleeper Service (getAllPlayers): No player data available or not in expected object format. players.json might be empty, not loaded properly, or not a map of player IDs to player objects.'); return []; } console.error('Sleeper Service (getAllPlayers): Initial allPlayersData type:', typeof this.allPlayersData, 'Number of keys:', Object.keys(this.allPlayersData).length); let playersArray; try { playersArray = Object.values(this.allPlayersData); console.error(`Sleeper Service (getAllPlayers): Successfully converted allPlayersData to array. Array length: ${playersArray.length}`); if (playersArray.length > 0) { console.error('Sleeper Service (getAllPlayers): First element of playersArray:', JSON.stringify(playersArray[0], null, 2).substring(0, 300)); if (typeof playersArray[0] !== 'object' || playersArray[0] === null) { console.error('Sleeper Service (getAllPlayers): CRITICAL - First element of playersArray is NOT AN OBJECT or is null. This indicates players.json is not a direct map of player IDs to player objects.'); return []; // Abort if structure is wrong } } else { console.error('Sleeper Service (getAllPlayers): playersArray is empty after Object.values(). This likely means allPlayersData was an empty object.'); return []; } } catch (e) { console.error('Sleeper Service (getAllPlayers): Error during Object.values(this.allPlayersData) or initial checks:', e.message); console.error('Sleeper Service (getAllPlayers): Structure of allPlayersData that caused error:', JSON.stringify(this.allPlayersData, null, 2).substring(0, 500)); return []; } if (filters && filters.length > 0) { playersArray = playersArray.filter(player => { // Ensure player is an object before trying to access properties if (typeof player !== 'object' || player === null) { console.warn('Sleeper Service (getAllPlayers filter): Encountered a non-object in playersArray during filtering:', player); return false; } return filters.every(filter => { const playerValue = getNestedValue(player, filter.field); const filterValue = filter.value; if (filter.operator === 'exists') return playerValue !== undefined; if (filter.operator === 'notExists') return playerValue === undefined; if (playerValue === undefined) return false; switch (filter.operator) { case 'eq': return String(playerValue).toLowerCase() === String(filterValue).toLowerCase(); case 'ne': return String(playerValue).toLowerCase() !== String(filterValue).toLowerCase(); case 'contains': if (typeof playerValue === 'string' && typeof filterValue === 'string') { return playerValue.toLowerCase().includes(filterValue.toLowerCase()); } if (Array.isArray(playerValue) && typeof filterValue === 'string') { return playerValue.some(v => String(v).toLowerCase().includes(filterValue.toLowerCase())); } return false; case 'gt': return typeof playerValue === 'number' && typeof filterValue === 'number' && playerValue > filterValue; case 'gte': return typeof playerValue === 'number' && typeof filterValue === 'number' && playerValue >= filterValue; case 'lt': return typeof playerValue === 'number' && typeof filterValue === 'number' && playerValue < filterValue; case 'lte': return typeof playerValue === 'number' && typeof filterValue === 'number' && playerValue <= filterValue; case 'in': if (Array.isArray(filterValue)) { return filterValue.map(v => String(v).toLowerCase()).includes(String(playerValue).toLowerCase()); } return false; default: console.warn(`Sleeper Service: Unknown filter operator: ${filter.operator}`); return false; } }); }); } console.error(`Sleeper Service (getAllPlayers): Player array length after filtering: ${playersArray.length}`); if (fields && fields.length > 0) { playersArray = playersArray.map((player) => { const selectedPlayer = {}; // Ensure player is an object before trying to access properties if (typeof player !== 'object' || player === null) { console.warn('Sleeper Service (getAllPlayers field selection): Encountered a non-object in playersArray:', player); return {}; // Return empty object or handle as error } fields.forEach(field => { const fieldValue = getNestedValue(player, field); if (fieldValue !== undefined) { let currentSelected = selectedPlayer; const parts = field.split('.'); parts.forEach((part, index) => { if (index === parts.length - 1) { currentSelected[part] = fieldValue; } else { currentSelected[part] = currentSelected[part] || {}; currentSelected = currentSelected[part]; } }); } else if (player.hasOwnProperty(field)) { selectedPlayer[field] = player[field]; } }); return selectedPlayer; }); } console.error(`Sleeper Service (getAllPlayers): Final players array length before returning: ${playersArray.length}`); return playersArray; } async getTrendingPlayers(sport, type, lookbackHours, limit) { let url = `${BASE_URL}/players/${sport}/trending/${type}`; const params = new URLSearchParams(); if (lookbackHours) params.append('lookback_hours', lookbackHours.toString()); if (limit) params.append('limit', limit.toString()); if (params.toString()) url += `?${params.toString()}`; const response = await axios.get(url); return response.data; } async getUserDrafts(userId, sport, season) { const response = await axios.get(`${BASE_URL}/user/${userId}/drafts/${sport}/${season}`); return response.data; } async getLeagueDrafts(leagueId) { const response = await axios.get(`${BASE_URL}/league/${leagueId}/drafts`); return response.data; } async getDraft(draftId) { const response = await axios.get(`${BASE_URL}/draft/${draftId}`); return response.data; } async getDraftPicks(draftId) { const response = await axios.get(`${BASE_URL}/draft/${draftId}/picks`); return response.data; } async getDraftTradedPicks(draftId) { const response = await axios.get(`${BASE_URL}/draft/${draftId}/traded_picks`); return response.data; } }