get_trending_players
Identify trending fantasy football players based on recent add/drop activity with position filtering and availability checks for waiver wire decisions.
Instructions
Get trending players with add/drop activity and analysis
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| availableOnly | No | Only show players available in your league (default: true) | |
| league | No | League name (ROAD_TO_GLORY or DYNASTY), defaults to configured default | |
| limit | No | Maximum number of players to return (default: 20) | |
| lookbackHours | No | Hours to look back for trending activity (default: 24) | |
| position | No | Filter by position (QB, RB, WR, TE, K, DEF) | |
| type | No | Type of trending activity (add, drop, both) | both |
Implementation Reference
- src/tools/TrendingTool.ts:47-224 (handler)Core handler implementing get_trending_players: fetches NFL players, trending add/drop activity from external API, filters by league rosters/position/availability, computes net activity, sorts and summarizes trending players.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)}`); } }
- src/tools/TrendingTool.ts:6-45 (schema)Input schema defining parameters for league, activity type, lookback period, limit, position filter, and availability filter.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)Tool registration in the main CallToolRequest handler switch statement, dispatching to TrendingTool.execute.case "get_trending_players": return await trendingTool.execute(args);
- src/index.ts:30-30 (registration)Instantiation of TrendingTool instance used for registration in listTools and callTool handlers.const trendingTool = new TrendingTool();
- src/tools/TrendingTool.ts:226-231 (helper)Helper method to generate position-based summary counts for trending players.private getPositionSummary(players: any[]): Record<string, number> { return players.reduce((summary, player) => { summary[player.position] = (summary[player.position] || 0) + 1; return summary; }, {}); }