Skip to main content
Glama
justfeltlikerunning

Sleeper Fantasy MCP

get_historical_scores

Retrieve actual fantasy football points scored by players in previous weeks for performance analysis and roster decisions.

Instructions

Get actual historical fantasy points scored by players

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
leagueNoLeague name (ROAD_TO_GLORY or DYNASTY), defaults to configured default
weekYesWeek number to get historical scores for
seasonNoSeason year (defaults to current NFL season)
playersNoArray of player IDs to get historical scores for (optional - gets your roster if not provided)
positionNoFilter by position (QB, RB, WR, TE, K, DEF)

Implementation Reference

  • Core handler function implementing get_historical_scores: processes args, fetches data from Sleeper API (/rosters, /users, /stats/nfl/regular/{season}/{week}, /players/nfl), computes comprehensive player stats and fantasy points, handles defaults and errors.
    async execute(args: any) {
      const leagueConfig = getLeagueConfig(args.league);
      
      if (!leagueConfig) {
        throw new Error(`League configuration not found for: ${args.league}`);
      }
    
      const week = args.week;
      const season = args.season || this.getCurrentNflSeason();
    
      try {
        let playerIds = args.players;
        
        // If no specific players requested, get user's roster
        if (!playerIds) {
          const [rostersResponse, usersResponse] = await Promise.all([
            fetch(`${config.api.baseUrl}/league/${leagueConfig.id}/rosters`),
            fetch(`${config.api.baseUrl}/league/${leagueConfig.id}/users`)
          ]);
    
          if (!rostersResponse.ok || !usersResponse.ok) {
            throw new Error('Failed to fetch roster data');
          }
    
          const rosters = await rostersResponse.json();
          const users = await usersResponse.json();
          
          const userMap = new Map(users.map((user: any) => [user.user_id, user]));
          const myRoster = rosters.find((roster: any) => {
            const user: any = userMap.get(roster.owner_id);
            return user?.display_name === config.username || 
                   user?.username === config.username ||
                   user?.display_name === leagueConfig.teamName || 
                   user?.username === leagueConfig.teamName;
          });
    
          if (myRoster) {
            playerIds = myRoster.players;
          }
        }
    
        if (!playerIds || playerIds.length === 0) {
          throw new Error('No players found to get historical scores for');
        }
    
        // Fetch historical stats for the specified week/season
        const statsResponse = await fetch(`${config.api.baseUrl}/stats/nfl/regular/${season}/${week}`);
        if (!statsResponse.ok) {
          throw new Error(`Failed to fetch stats for week ${week} of ${season} season`);
        }
        const allStats = await statsResponse.json();
    
        // Fetch player data for player names and info
        const playersResponse = await fetch(`${config.api.baseUrl}/players/nfl`);
        if (!playersResponse.ok) {
          throw new Error('Failed to fetch player data');
        }
        const players = await playersResponse.json();
    
        // Process player historical scores
        const playerScores = [];
        
        for (const playerId of playerIds) {
          const player = players[playerId];
          if (!player) continue;
          
          // Apply position filter if specified
          if (args.position && player.position !== args.position) {
            continue;
          }
    
          const playerStats = allStats[playerId] || {};
          
          playerScores.push({
            playerId,
            name: `${player.first_name} ${player.last_name}`,
            position: player.position,
            team: player.team,
            status: player.status,
            week: week,
            season: season,
            actualPoints: {
              ppr: Number((playerStats.pts_ppr || 0).toFixed(2)),
              halfPpr: Number((playerStats.pts_half_ppr || 0).toFixed(2)),
              standard: Number((playerStats.pts_std || 0).toFixed(2))
            },
            detailedStats: {
              // Passing Stats
              passingYards: playerStats.pass_yd || 0,
              passingTDs: playerStats.pass_td || 0,
              passingInterceptions: playerStats.pass_int || 0,
              passingAttempts: playerStats.pass_att || 0,
              passingCompletions: playerStats.pass_cmp || 0,
              completionPercentage: Number((playerStats.cmp_pct || 0).toFixed(1)),
              passerRating: Number((playerStats.pass_rtg || 0).toFixed(1)),
              yardsPerAttempt: Number((playerStats.pass_ypa || 0).toFixed(2)),
              airYards: playerStats.pass_air_yd || 0,
              sacksTaken: playerStats.pass_sack || 0,
              passingFirstDowns: playerStats.pass_fd || 0,
              
              // Rushing Stats  
              rushingYards: playerStats.rush_yd || 0,
              rushingTDs: playerStats.rush_td || 0,
              rushingAttempts: playerStats.rush_att || 0,
              rushingYardsPerAttempt: Number((playerStats.rush_ypa || 0).toFixed(2)),
              rushingYardsAfterContact: playerStats.rush_yac || 0,
              brokenTackles: playerStats.rush_btkl || 0,
              longestRush: playerStats.rush_lng || 0,
              rushingFirstDowns: playerStats.rush_fd || 0,
              
              // Receiving Stats
              receivingYards: playerStats.rec_yd || 0,
              receivingTDs: playerStats.rec_td || 0,
              receptions: playerStats.rec || 0,
              targets: playerStats.rec_tgt || 0,
              receivingAirYards: playerStats.rec_air_yd || 0,
              yardsAfterCatch: playerStats.rec_yac || 0,
              yardsPerReception: Number((playerStats.rec_ypr || 0).toFixed(2)),
              yardsPerTarget: Number((playerStats.rec_ypt || 0).toFixed(2)),
              drops: playerStats.rec_drop || 0,
              longestReception: playerStats.rec_lng || 0,
              receivingFirstDowns: playerStats.rec_fd || 0,
              
              // Kicking Stats
              fieldGoalsMade: playerStats.fgm || 0,
              fieldGoalsAttempted: playerStats.fga || 0,
              fieldGoalPercentage: playerStats.fgm_pct || 0,
              longestFieldGoal: playerStats.fgm_lng || 0,
              extraPointsMade: playerStats.xpm || 0,
              extraPointsAttempted: playerStats.xpa || 0,
              
              // Miscellaneous
              fumbles: playerStats.fum || 0,
              fumblesLost: playerStats.fum_lost || 0,
              penalties: playerStats.penalty || 0,
              penaltyYards: playerStats.penalty_yd || 0
            },
            snapCounts: {
              offensiveSnaps: playerStats.off_snp || 0,
              teamOffensiveSnaps: playerStats.tm_off_snp || 0,
              teamDefensiveSnaps: playerStats.tm_def_snp || 0,
              teamSpecialTeamsSnaps: playerStats.tm_st_snp || 0,
              specialTeamsSnaps: playerStats.st_snp || 0
            },
            efficiencyMetrics: {
              catchRate: playerStats.rec_tgt > 0 ? Number(((playerStats.rec || 0) / playerStats.rec_tgt * 100).toFixed(1)) : 0,
              snapPercentage: playerStats.tm_off_snp > 0 ? Number(((playerStats.off_snp || 0) / playerStats.tm_off_snp * 100).toFixed(1)) : 0,
              touchdownRate: (playerStats.rush_att || 0) + (playerStats.rec_tgt || 0) > 0 ? 
                Number((((playerStats.rush_td || 0) + (playerStats.rec_td || 0)) / ((playerStats.rush_att || 0) + (playerStats.rec_tgt || 0)) * 100).toFixed(1)) : 0,
              redZoneTargets: playerStats.rec_rz_tgt || 0,
              redZoneCarries: playerStats.rush_rz_att || 0
            },
            rankings: {
              ppr: playerStats.pos_rank_ppr || 999,
              halfPpr: playerStats.pos_rank_half_ppr || 999,
              standard: playerStats.pos_rank_std || 999
            }
          });
        }
        
        // Sort by PPR points (highest first)
        playerScores.sort((a: any, b: any) => b.actualPoints.ppr - a.actualPoints.ppr);
    
        const result = {
          week,
          season,
          league: args.league || config.defaultLeague,
          totalPlayers: playerScores.length,
          historicalScores: playerScores,
          summary: {
            totalPprPoints: playerScores.reduce((sum: number, p: any) => sum + p.actualPoints.ppr, 0),
            averageScore: playerScores.length > 0 ? 
              (playerScores.reduce((sum: number, p: any) => sum + p.actualPoints.ppr, 0) / playerScores.length).toFixed(1) : 0,
            topScorer: playerScores[0] || null,
            playersWhoScored: playerScores.filter((p: any) => p.actualPoints.ppr > 0).length
          }
        };
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        throw new Error(`Failed to get historical scores: ${error instanceof Error ? error.message : String(error)}`);
      }
    }
  • Input schema for the tool defining parameters, types, enums, descriptions, and required 'week' field.
    inputSchema = {
      type: "object",
      properties: {
        league: {
          type: "string",
          description: "League name (ROAD_TO_GLORY or DYNASTY), defaults to configured default",
          enum: ["ROAD_TO_GLORY", "DYNASTY"]
        },
        week: {
          type: "number",
          description: "Week number to get historical scores for",
          minimum: 1,
          maximum: 18
        },
        season: {
          type: "string",
          description: "Season year (defaults to current NFL season)",
          pattern: "^[0-9]{4}$"
        },
        players: {
          type: "array",
          description: "Array of player IDs to get historical scores for (optional - gets your roster if not provided)",
          items: {
            type: "string"
          }
        },
        position: {
          type: "string",
          description: "Filter by position (QB, RB, WR, TE, K, DEF)",
          enum: ["QB", "RB", "WR", "TE", "K", "DEF"]
        }
      },
      required: ["week"]
    };
  • src/index.ts:86-87 (registration)
    Dispatch registration in CallToolRequestSchema handler: routes tool calls matching 'get_historical_scores' to the HistoricalScoresTool instance's execute method.
    case "get_historical_scores":
      return await historicalScoresTool.execute(args);
  • src/index.ts:49-63 (registration)
    Tool registration in ListToolsRequestSchema handler: includes HistoricalScoresTool instance in the list of available tools advertised to clients.
      tools: [
        leagueTool,
        rosterTool,
        matchupTool,
        playerTool,
        projectionsTool,
        matchupProjectionsTool,
        lineupOptimizerTool,
        trendingTool,
        historicalScoresTool,
        playerNewsTool,
        transactionsTool,
        stateScheduleTool,
      ],
    }));
  • Helper method to determine the current NFL season year based on the date (September onwards uses current year, earlier uses previous).
    private getCurrentNflSeason(): string {
      const now = new Date();
      const currentYear = now.getFullYear();
      const currentMonth = now.getMonth() + 1; // getMonth() returns 0-11
      
      // NFL season typically starts in September and runs through February of next year
      // If we're in Jan-July, we're still in the previous year's NFL season
      // If we're in Aug-Dec, we're in the current year's NFL season
      if (currentMonth >= 8) {
        return currentYear.toString();
      } else {
        return (currentYear - 1).toString();
      }
    }
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries full burden for behavioral disclosure. While 'Get' implies a read operation, it doesn't specify whether this requires authentication, has rate limits, returns paginated results, or what format the historical scores come in. For a tool with 5 parameters and no annotation coverage, this leaves significant behavioral questions unanswered.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, efficient sentence that gets straight to the point with zero wasted words. It's appropriately sized for what it communicates and front-loads the essential information about what the tool does.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only tool with comprehensive schema documentation (100% coverage) but no output schema, the description provides basic purpose but lacks important context. It doesn't explain what format the historical scores return, whether results are paginated, or how to interpret the data. With no annotations and no output schema, the description should do more to compensate for these gaps.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema has 100% description coverage, so all parameters are well-documented in the structured fields. The description adds no additional parameter information beyond what's already in the schema. According to scoring rules, when schema_description_coverage is high (>80%), the baseline is 3 even with no param info in the description.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the action ('Get') and resource ('historical fantasy points scored by players'), making the purpose immediately understandable. However, it doesn't differentiate from sibling tools like 'get_player_projections' or 'get_matchup_projections' which might also involve player scoring data, leaving room for confusion about when to use this specific historical data tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides no guidance on when to use this tool versus alternatives. With siblings like 'get_player_projections' and 'get_matchup_projections' that might provide similar or overlapping data, the agent has no indication whether this is for past performance analysis versus future predictions, or what specific use cases warrant selecting this historical data tool.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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/justfeltlikerunning/sleeper-fantasy-mcp'

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