Skip to main content
Glama
justfeltlikerunning

Sleeper Fantasy MCP

get_player_news

Retrieve player news, injury updates, and recent status changes for fantasy football rosters, trending players, or league-wide monitoring.

Instructions

Get player news, injury status, and recent updates for your roster or specific players

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
leagueNoLeague name (ROAD_TO_GLORY or DYNASTY), defaults to configured default
scopeNoScope of players to check: 'roster' (your team), 'trending' (add/drop activity), or 'all' (league-wide)roster
hoursBackNoHours back to check for news updates (default: 48)
includeHealthyNoInclude healthy players without injury status (default: false)
playerIdsNoSpecific player IDs to check (optional)

Implementation Reference

  • Main handler function `execute` in PlayerNewsTool class that implements the core logic for fetching player news, injury statuses, trending activity, and generating summaries based on input parameters.
    async execute(args: any) {
      const leagueConfig = getLeagueConfig(args.league);
      
      if (!leagueConfig) {
        throw new Error(`League configuration not found for: ${args.league}`);
      }
    
      const scope = args.scope || 'roster';
      const hoursBack = args.hoursBack || 48;
      const includeHealthy = args.includeHealthy || false;
      const specificPlayerIds = args.playerIds;
      const cutoffTime = Date.now() - (hoursBack * 60 * 60 * 1000);
    
      try {
        // Fetch required data
        const fetchPromises = [
          fetch(`${config.api.baseUrl}/players/nfl`),
          scope === 'roster' || scope === 'all' ? fetch(`${config.api.baseUrl}/league/${leagueConfig.id}/rosters`) : Promise.resolve(null),
          scope === 'trending' || scope === 'all' ? fetch(`${config.api.baseUrl}/players/nfl/trending/add?lookback_hours=${Math.min(hoursBack, 72)}&limit=50`) : Promise.resolve(null),
          scope === 'trending' || scope === 'all' ? fetch(`${config.api.baseUrl}/players/nfl/trending/drop?lookback_hours=${Math.min(hoursBack, 72)}&limit=50`) : Promise.resolve(null)
        ];
    
        const responses = await Promise.all(fetchPromises);
        
        if (!responses[0]?.ok) {
          throw new Error('Failed to fetch player data');
        }
    
        const players = await responses[0].json();
        const rosters: SleeperRoster[] = responses[1] ? await responses[1].json() : [];
        const trendingAdd = responses[2] ? await responses[2].json() : [];
        const trendingDrop = responses[3] ? await responses[3].json() : [];
    
        // Get relevant player IDs based on scope
        let relevantPlayerIds: Set<string>;
        
        if (specificPlayerIds && specificPlayerIds.length > 0) {
          relevantPlayerIds = new Set(specificPlayerIds);
        } else if (scope === 'roster') {
          // Find user's roster by matching owner username or team name
          const users = await fetch(`${config.api.baseUrl}/league/${leagueConfig.id}/users`).then(r => r.json());
          const myUser = users?.find((user: any) => user.display_name === config.username);
          const myRoster = rosters.find(roster => 
            (myUser && roster.owner_id === myUser.user_id)
          );
          relevantPlayerIds = new Set(myRoster?.players || []);
        } else if (scope === 'trending') {
          const trendingPlayerIds = [
            ...trendingAdd.map((p: any) => p.player_id),
            ...trendingDrop.map((p: any) => p.player_id)
          ];
          relevantPlayerIds = new Set(trendingPlayerIds);
        } else {
          // scope === 'all' - check all players, but limit to fantasy-relevant positions
          relevantPlayerIds = new Set();
        }
    
        // Process players and filter for news-worthy updates
        const newsUpdates = Object.entries(players)
          .filter(([playerId, player]: [string, any]) => {
            // Skip if not in relevant player set (unless scope is 'all')
            if (scope !== 'all' && !relevantPlayerIds.has(playerId)) return false;
            
            // Skip non-fantasy positions for 'all' scope
            if (scope === 'all' && (!player.fantasy_positions || 
                !player.fantasy_positions.some((pos: string) => ['QB', 'RB', 'WR', 'TE', 'K', 'DEF'].includes(pos)))) {
              return false;
            }
    
            // Check if player has recent news update
            const hasRecentUpdate = player.news_updated && player.news_updated > cutoffTime;
            
            // Check if player has injury/status information
            const hasInjuryInfo = player.injury_status || 
                                  player.injury_body_part || 
                                  player.practice_participation ||
                                  player.status === 'Injured Reserve' ||
                                  player.status === 'Out' ||
                                  player.status === 'Questionable' ||
                                  player.status === 'Doubtful';
    
            // Include if has recent update OR injury info (unless only wanting healthy players)
            if (!includeHealthy && !hasInjuryInfo && !hasRecentUpdate) return false;
            if (includeHealthy && !hasRecentUpdate && !hasInjuryInfo) return false;
    
            return true;
          })
          .map(([playerId, player]: [string, any]) => {
            const isTrendingAdd = trendingAdd.some((trend: any) => trend.player_id === playerId);
            const isTrendingDrop = trendingDrop.some((trend: any) => trend.player_id === playerId);
            
            // Calculate time since last update
            let timeSinceUpdate = "No recent updates";
            if (player.news_updated) {
              const hoursSince = Math.floor((Date.now() - player.news_updated) / (1000 * 60 * 60));
              if (hoursSince < 1) {
                timeSinceUpdate = "Less than 1 hour ago";
              } else if (hoursSince < 24) {
                timeSinceUpdate = `${hoursSince} hours ago`;
              } else {
                const daysSince = Math.floor(hoursSince / 24);
                timeSinceUpdate = `${daysSince} day${daysSince > 1 ? 's' : ''} ago`;
              }
            }
    
            return {
              playerId,
              name: `${player.first_name || ''} ${player.last_name || ''}`.trim(),
              position: player.position,
              team: player.team || 'FA',
              status: player.status || 'Active',
              injuryStatus: player.injury_status || null,
              injuryBodyPart: player.injury_body_part || null,
              injuryStartDate: player.injury_start_date,
              injuryNotes: player.injury_notes || null,
              practiceParticipation: player.practice_participation || null,
              practiceDescription: player.practice_description || null,
              newsUpdated: player.news_updated,
              timeSinceUpdate,
              trending: {
                isAdding: isTrendingAdd,
                isDropping: isTrendingDrop,
                activity: isTrendingAdd ? 'Being added' : isTrendingDrop ? 'Being dropped' : 'Stable'
              },
              fantasyPositions: player.fantasy_positions || []
            };
          })
          .sort((a, b) => {
            // Sort by most recent news first, then by injury severity
            if (a.newsUpdated && b.newsUpdated) {
              return b.newsUpdated - a.newsUpdated;
            }
            if (a.newsUpdated && !b.newsUpdated) return -1;
            if (!a.newsUpdated && b.newsUpdated) return 1;
            
            // Secondary sort by injury severity
            const getSeverity = (status: string | null) => {
              if (!status) return 0;
              if (status === 'Out' || status === 'Injured Reserve') return 5;
              if (status === 'Doubtful') return 4;
              if (status === 'Questionable') return 3;
              if (status === 'Probable') return 2;
              return 1;
            };
            
            return getSeverity(b.injuryStatus) - getSeverity(a.injuryStatus);
          });
    
        const result = {
          league: args.league || config.defaultLeague,
          scope: scope,
          hoursBack: hoursBack,
          cutoffTime: new Date(cutoffTime).toISOString(),
          totalUpdates: newsUpdates.length,
          filters: {
            includeHealthy: includeHealthy,
            specificPlayers: specificPlayerIds ? specificPlayerIds.length : 0
          },
          updates: newsUpdates,
          summary: {
            withInjuries: newsUpdates.filter(p => p.injuryStatus).length,
            withRecentNews: newsUpdates.filter(p => p.newsUpdated && p.newsUpdated > cutoffTime).length,
            trending: {
              adding: newsUpdates.filter(p => p.trending.isAdding).length,
              dropping: newsUpdates.filter(p => p.trending.isDropping).length
            },
            byStatus: this.getStatusSummary(newsUpdates)
          }
        };
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        throw new Error(`Failed to get player news: ${error instanceof Error ? error.message : String(error)}`);
      }
    }
  • Input schema defining parameters for the get_player_news tool including league, scope, time range, filters, and optional player IDs.
    inputSchema = {
      type: "object",
      properties: {
        league: {
          type: "string",
          description: "League name (ROAD_TO_GLORY or DYNASTY), defaults to configured default",
          enum: ["ROAD_TO_GLORY", "DYNASTY"]
        },
        scope: {
          type: "string",
          description: "Scope of players to check: 'roster' (your team), 'trending' (add/drop activity), or 'all' (league-wide)",
          enum: ["roster", "trending", "all"],
          default: "roster"
        },
        hoursBack: {
          type: "number",
          description: "Hours back to check for news updates (default: 48)",
          minimum: 1,
          maximum: 168,
          default: 48
        },
        includeHealthy: {
          type: "boolean",
          description: "Include healthy players without injury status (default: false)",
          default: false
        },
        playerIds: {
          type: "array",
          description: "Specific player IDs to check (optional)",
          items: {
            type: "string"
          }
        }
      }
    };
  • src/index.ts:88-89 (registration)
    Registration in the CallToolRequestSchema handler switch statement, dispatching calls to get_player_news to the playerNewsTool instance's execute method.
    case "get_player_news":
      return await playerNewsTool.execute(args);
  • src/index.ts:48-63 (registration)
    Registration in the ListToolsRequestSchema handler, including playerNewsTool instance in the list of available tools.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        leagueTool,
        rosterTool,
        matchupTool,
        playerTool,
        projectionsTool,
        matchupProjectionsTool,
        lineupOptimizerTool,
        trendingTool,
        historicalScoresTool,
        playerNewsTool,
        transactionsTool,
        stateScheduleTool,
      ],
    }));
  • src/index.ts:19-32 (registration)
    Import and instantiation of the PlayerNewsTool class for use in tool registration and dispatch.
    import { PlayerNewsTool } from "./tools/PlayerNewsTool.js";
    import { TransactionsTool } from "./tools/TransactionsTool.js";
    import { StateScheduleTool } from "./tools/StateScheduleTool.js";
    
    const leagueTool = new LeagueTool();
    const rosterTool = new RosterTool();
    const matchupTool = new MatchupTool();
    const playerTool = new PlayerTool();
    const projectionsTool = new ProjectionsTool();
    const matchupProjectionsTool = new MatchupProjectionsTool();
    const lineupOptimizerTool = new LineupOptimizerTool();
    const trendingTool = new TrendingTool();
    const historicalScoresTool = new HistoricalScoresTool();
    const playerNewsTool = new PlayerNewsTool();
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. It mentions what data is retrieved (news, injury status, recent updates) but doesn't describe response format, pagination, rate limits, authentication needs, or error conditions. For a tool with 5 parameters and no output schema, this leaves significant behavioral gaps for an AI agent.

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 front-loads the core purpose. Every word earns its place with no redundancy or wasted text. It directly communicates the tool's function without unnecessary elaboration.

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?

Given 5 parameters with 100% schema coverage but no annotations and no output schema, the description is minimally adequate. It states what data is retrieved but doesn't explain the return format, which is a significant gap when no output schema exists. For a news retrieval tool with multiple filtering options, more context about response structure would be helpful.

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?

Schema description coverage is 100%, so the schema already documents all 5 parameters thoroughly. The description adds minimal value beyond the schema - it mentions 'roster or specific players' which relates to the 'scope' and 'playerIds' parameters, but doesn't provide additional syntax, format, or semantic context. Baseline 3 is appropriate when schema does the heavy lifting.

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 tool's purpose: 'Get player news, injury status, and recent updates' with specific resources (roster or specific players). It distinguishes from siblings like get_historical_scores or get_player_projections by focusing on news/injury updates rather than scores or projections. However, it doesn't explicitly differentiate from get_trending_players which might overlap with the 'trending' scope.

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

Usage Guidelines3/5

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

The description implies usage context ('for your roster or specific players') but lacks explicit guidance on when to use this tool versus alternatives. It doesn't mention when to choose get_player_news over get_trending_players (which might provide similar trending data) or get_my_roster (which might provide roster details without news). No exclusions or prerequisites are stated.

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