@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
JavaScript
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;
}
}