Skip to main content
Glama
justfeltlikerunning

Sleeper Fantasy MCP

get_trending_players

Identify trending fantasy football players by analyzing recent add/drop activity across Sleeper leagues to inform roster decisions.

Instructions

Get trending players with add/drop activity and analysis

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
leagueNoLeague name (ROAD_TO_GLORY or DYNASTY), defaults to configured default
typeNoType of trending activity (add, drop, both)both
lookbackHoursNoHours to look back for trending activity (default: 24)
limitNoMaximum number of players to return (default: 20)
positionNoFilter by position (QB, RB, WR, TE, K, DEF)
availableOnlyNoOnly show players available in your league (default: true)

Implementation Reference

  • The execute method that implements the tool logic: fetches player data, trending adds/drops from API based on parameters, filters by availability/position, computes net activity, sorts, and returns formatted JSON with summaries.
    async execute(args: any) {
      const leagueConfig = getLeagueConfig(args.league);
      
      if (!leagueConfig) {
        throw new Error(`League configuration not found for: ${args.league}`);
      }
    
      const type = args.type || 'both';
      const lookbackHours = args.lookbackHours || 24;
      const limit = args.limit || 20;
      const position = args.position;
      const availableOnly = args.availableOnly !== false;
    
      try {
        const fetchPromises = [
          fetch(`${config.api.baseUrl}/players/nfl`)
        ];
    
        // Fetch trending data based on type
        if (type === 'add' || type === 'both') {
          fetchPromises.push(
            fetch(`${config.api.baseUrl}/players/nfl/trending/add?lookback_hours=${lookbackHours}&limit=100`)
          );
        }
        
        if (type === 'drop' || type === 'both') {
          fetchPromises.push(
            fetch(`${config.api.baseUrl}/players/nfl/trending/drop?lookback_hours=${lookbackHours}&limit=100`)
          );
        }
    
        // If availableOnly is true, fetch rosters to filter out owned players
        if (availableOnly) {
          fetchPromises.push(
            fetch(`${config.api.baseUrl}/league/${leagueConfig.id}/rosters`)
          );
        }
    
        const responses = await Promise.all(fetchPromises);
        
        if (responses.some(r => !r.ok)) {
          throw new Error('Failed to fetch trending player data');
        }
    
        const players = await responses[0].json();
        
        let trendingAdd: any[] = [];
        let trendingDrop: any[] = [];
        let rosters: any[] = [];
        
        let responseIndex = 1;
        
        if (type === 'add' || type === 'both') {
          trendingAdd = await responses[responseIndex].json();
          responseIndex++;
        }
        
        if (type === 'drop' || type === 'both') {
          trendingDrop = await responses[responseIndex].json();
          responseIndex++;
        }
        
        if (availableOnly) {
          rosters = await responses[responseIndex].json();
        }
    
        // Create set of owned players if filtering by availability
        const ownedPlayerIds = availableOnly ? 
          new Set(rosters.flatMap((roster: any) => roster.players || [])) : new Set();
    
        // Combine trending data
        const trendingPlayerMap = new Map();
        
        trendingAdd.forEach((trend: any) => {
          trendingPlayerMap.set(trend.player_id, {
            playerId: trend.player_id,
            addCount: trend.count,
            dropCount: 0,
            netActivity: trend.count,
            trend: 'Adding'
          });
        });
    
        trendingDrop.forEach((trend: any) => {
          const existing = trendingPlayerMap.get(trend.player_id);
          if (existing) {
            existing.dropCount = trend.count;
            existing.netActivity = existing.addCount - trend.count;
            existing.trend = existing.addCount > trend.count ? 'Net Adding' : 'Net Dropping';
          } else {
            trendingPlayerMap.set(trend.player_id, {
              playerId: trend.player_id,
              addCount: 0,
              dropCount: trend.count,
              netActivity: -trend.count,
              trend: 'Dropping'
            });
          }
        });
    
        // Filter and enhance with player details
        const trendingPlayers = Array.from(trendingPlayerMap.values())
          .map((trending: any) => {
            const player = players[trending.playerId];
            
            if (!player) return null;
            
            // Apply filters
            if (position && player.position !== position) return null;
            if (availableOnly && ownedPlayerIds.has(trending.playerId)) return null;
            if (player.status === 'Inactive' || player.status === 'PUP') return null;
    
            return {
              playerId: trending.playerId,
              name: `${player.first_name} ${player.last_name}`,
              position: player.position,
              team: player.team,
              status: player.status,
              age: player.age,
              addCount: trending.addCount,
              dropCount: trending.dropCount,
              netActivity: trending.netActivity,
              trend: trending.trend,
              activityScore: Math.abs(trending.netActivity),
              isAvailable: !ownedPlayerIds.has(trending.playerId)
            };
          })
          .filter(Boolean)
          .sort((a: any, b: any) => {
            // Sort by activity score (absolute value of net activity) descending
            if (b.activityScore !== a.activityScore) {
              return b.activityScore - a.activityScore;
            }
            // Then by net activity (positive is better)
            return b.netActivity - a.netActivity;
          })
          .slice(0, limit);
    
        const result = {
          league: args.league || config.defaultLeague,
          filters: {
            type,
            lookbackHours,
            position: position || 'ALL',
            availableOnly
          },
          timeframe: `Last ${lookbackHours} hours`,
          totalTrending: trendingPlayers.length,
          players: trendingPlayers,
          summary: {
            byTrend: {
              adding: trendingPlayers.filter((p: any) => p?.trend?.includes('Adding')).length,
              dropping: trendingPlayers.filter((p: any) => p?.trend?.includes('Dropping')).length
            },
            byPosition: this.getPositionSummary(trendingPlayers),
            topAdds: trendingPlayers
              .filter((p: any) => p?.addCount > 0)
              .slice(0, 5)
              .map((p: any) => ({ name: p?.name, position: p?.position, adds: p?.addCount })),
            topDrops: trendingPlayers
              .filter((p: any) => p?.dropCount > 0)
              .slice(0, 5)
              .map((p: any) => ({ name: p?.name, position: p?.position, drops: p?.dropCount }))
          }
        };
    
        return {
          content: [
            {
              type: "text",
              text: JSON.stringify(result, null, 2),
            },
          ],
        };
      } catch (error) {
        throw new Error(`Failed to get trending players: ${error instanceof Error ? error.message : String(error)}`);
      }
    }
  • Defines the input schema for the tool parameters including league, type (add/drop/both), lookback hours, limit, position filter, and availableOnly flag.
    inputSchema = {
      type: "object",
      properties: {
        league: {
          type: "string",
          description: "League name (ROAD_TO_GLORY or DYNASTY), defaults to configured default",
          enum: ["ROAD_TO_GLORY", "DYNASTY"]
        },
        type: {
          type: "string",
          description: "Type of trending activity (add, drop, both)",
          enum: ["add", "drop", "both"],
          default: "both"
        },
        lookbackHours: {
          type: "number",
          description: "Hours to look back for trending activity (default: 24)",
          minimum: 1,
          maximum: 168,
          default: 24
        },
        limit: {
          type: "number",
          description: "Maximum number of players to return (default: 20)",
          minimum: 1,
          maximum: 50,
          default: 20
        },
        position: {
          type: "string",
          description: "Filter by position (QB, RB, WR, TE, K, DEF)",
          enum: ["QB", "RB", "WR", "TE", "K", "DEF"]
        },
        availableOnly: {
          type: "boolean",
          description: "Only show players available in your league (default: true)",
          default: true
        }
      }
    };
  • src/index.ts:84-85 (registration)
    Registers the tool name in the switch statement to dispatch calls to the TrendingTool's execute method.
    case "get_trending_players":
      return await trendingTool.execute(args);
  • src/index.ts:48-63 (registration)
    Includes the TrendingTool instance in the list of available tools returned by the list tools handler.
    server.setRequestHandler(ListToolsRequestSchema, async () => ({
      tools: [
        leagueTool,
        rosterTool,
        matchupTool,
        playerTool,
        projectionsTool,
        matchupProjectionsTool,
        lineupOptimizerTool,
        trendingTool,
        historicalScoresTool,
        playerNewsTool,
        transactionsTool,
        stateScheduleTool,
      ],
    }));
  • src/index.ts:30-30 (registration)
    Instantiates the TrendingTool class for use in registration and dispatch.
    const trendingTool = new TrendingTool();
  • Helper method to generate position-based summary counts from the trending players list.
    private getPositionSummary(players: any[]): Record<string, number> {
      return players.reduce((summary, player) => {
        summary[player.position] = (summary[player.position] || 0) + 1;
        return summary;
      }, {});
    }

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