Skip to main content
Glama
gagandaroach

fantasy-yolo

by gagandaroach

fantasy-yolo

A fantasy football agent server. An MCP server and a matching CLI that let you — or an agent you run — read and manage your own ESPN Fantasy Football team.

CI ESPN canary License: MIT

$ fy check-lineup
1 thing(s) to look at across 9 starters
  projected_zero: Josh Jacobs (RB/WR/TE) is projected 0.0 points by ESPN

$ fy player "Saquon Barkley"
Saquon Barkley (RB, PHI) is rostered by The white Bhatoyas

Ask an agent "anything wrong with my lineup this week?" or "who has Saquon, and what are they thin at?" and get an answer from live league data.


The one thing to understand first

The human is the brain. This server has no optimizer, no projections model, and no opinion about which players are good. It answers questions accurately and executes instructions carefully. It will tell you a starter is projected zero points; it will never tell you to bench him.

That is a deliberate constraint, not a missing feature. See Why no optimizer.

Related MCP server: Sleeper MCP Server

Install

Needs Python 3.13 and uv.

git clone https://github.com/gagandaroach/fantasy-yolo
cd fantasy-yolo
uv sync
uv run fy --help

Then follow docs/setup.md — two cookies out of your browser and a small config file naming your league. Five minutes.

As an MCP server

{
  "mcpServers": {
    "fantasy-yolo": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/fantasy-yolo", "fantasy-yolo-mcp"]
    }
  }
}

No credentials in that stanza — deliberately. They live in a chmod 0600 file the config points at.

Tools

Every tool is available identically over MCP and on the CLI, generated from one definition, so the two cannot drift apart.

Reading — always available

Tool

CLI

What it answers

get_roster

fy roster

Your roster with slots, opponents, projections, injury designations

check_lineup

fy check-lineup

One pre-kickoff check: empty slots, byes, players out, already-locked, projected zero

get_matchup

fy matchup

Who you're playing, projected score both sides, who's yet to play

get_league

fy league

Rules, key dates, waiver system, standings

get_budgets

fy budgets

What every team can still spend, not just you

find_players

fy find-players

Search the pool, ranked on a basis you name

get_player

fy player NAME

One player, and which team rosters them

get_team

fy team NAME

Another team's roster

get_position_counts

fy position-counts

Every team's positional shape, for trade shopping

get_transactions

fy transactions

The league's transaction log, filterable

get_pending

fy pending

Your claims and trades ESPN hasn't processed yet

Writing — off by default

Set write_enabled: true in your config to turn these on. While it's false they are not registered at all, so a model cannot see or offer them.

Preview

Execute

Effect

preview_lineup

execute_lineup

Set your starting lineup

preview_add_drop

execute_add_drop

Add a free agent, drop a player, or both

preview_waiver

execute_waiver

Submit a waiver claim with a FAAB bid

preview_cancel_waiver

cancel_waiver

Cancel a pending claim

Trades are designed but not yet built.

How writes are made safe

ESPN's transaction API has no dry run and no idempotency key. A retried write genuinely submits the move a second time. Everything below exists because of those two facts.

Two calls, one code. Every write is split. preview_* shows the change in player names and returns a short-lived, single-use code bound to that exact transaction. execute_* refuses to run without it. An approval given for one add cannot be spent on another, and a retried execute is refused rather than double-submitted.

Being honest about what that guarantees. It makes a write impossible as a side effect of a question, and forces the preview into the transcript. It does not guarantee a human read it — a model can call preview and execute in the same turn. The real human-in-the-loop guarantee is your MCP client's approval prompt on a tool marked destructiveHint. We set that annotation; your client decides what to do with it.

Nothing is ever retried. Retries are disabled at the HTTP adapter, because requests and urllib3 retry by default and this is the one platform where that silently double-submits a waiver claim. A timeout is reported as unknown — never as success, never as failure — with your roster re-read so you can see for yourself.

Your roster is re-read immediately before every write. If anything moved since the preview — you set a lineup on your phone — the write is refused rather than silently clobbering it.

Legality is checked client-side, since ESPN won't tell you in advance: roster space, slot eligibility, whether a player is actually free or actually yours, your bid against your remaining budget and the league minimum, and whether your league even uses FAAB.

It can only ever touch your own team. isLeagueManager and isActingAsTeamOwner are hard-wired false with no parameter exposing them, and your team id is pinned in config.

Every write is logged — intent before the request leaves, outcome after — so an interrupted write still leaves something to reconcile against. Credentials are redacted from every line.

What this server does not know

Stated in the server's own instructions, so a model driving it is told up front rather than left to invent:

  • No projections beyond ESPN's own number. There is no model here.

  • No injury news, no news feed, no reporter text.

  • No timestamps on injury designations. ESPN reports a status word and nothing else. It will catch a player ruled out on Wednesday; it will not catch a Sunday-morning inactive.

  • No ranking of players on merit.

A missing projection is returned as null, never as 0.0 — because ESPN really does project some players at zero, and conflating the two invites a confident wrong conclusion.

Why no optimizer

Removing the optimizer doesn't remove decision-making from the product — it moves it to you, where the context is. An automated optimizer on this platform would also be operating with no dry run and no undo, which is a bad combination for irreversible moves.

There's a second reason worth naming: if you drive this with an LLM, the decision-making relocates into a general model with no current fantasy data. That model will fill silence with confident, plausible, wrong detail. Hence the section above — telling it loudly what it doesn't know is the compensating control.

Notes on ESPN's private API

Documented here because it is otherwise scattered across gists and half-wrong repos. All verified against live ESPN.

Fact

Detail

Read host

lm-api-reads.fantasy.espn.com

Write host

lm-api-writes.fantasy.espn.com — one POST .../transactions/ endpoint for everything

Auth

espn_s2 + SWID cookies. No OAuth, no API keys, no developer program

X-Fantasy-Role

Not an auth signal. Reads NONE on an authenticated 200 just as on an unauthenticated 401

Dry run

Does not exist. VALIDATE / PREVIEW / DRY_RUN all 400

Idempotency

Does not exist. idempotencyKey and executionKey both 400

Body parsing

Strict, and happens before auth — so an unauthenticated POST returns 401 for a valid shape and 400 for an invalid one. A free, side-effect-free schema oracle

Wire slot names

fromLineupSlotId / toLineupSlotId. ESPN's client calls them fromSlotId/toSlotId internally and renames on the way out; sending the internal names 400s

Errors

Match TRAN_* by prefix — the real constants ship in _ONE / _PLURAL / _LM variants

Three shapes most public implementations get wrong, all confirmed correct here by ESPN's own parser:

  • A future-week lineup is FUTURE_ROSTER, not ROSTER.

  • A standalone drop is ROSTER, not FREEAGENT.

  • The trade verbs (TRADE_ACCEPT, TRADE_DECLINE, …) do exist.

A daily canary checks that ESPN still parses the exact payloads we'd send. ESPN tightened leagueHistory in Aug 2025 and split read/write hosts in Apr 2024, both without notice.

Design

src/fantasy_yolo/
  espn/       vendored ESPN client (football), ours to fix
  read/       read wrappers + the raw fetches espn-api misses
  write/      payload builders, never-retry client, error interpretation
  policy/     lineup planner, legality, confirm tokens, audit log
  tools/      the 19 tools
  cli.py      Typer frontend      ─┐ both generated from one registry,
  mcp/        MCP frontend        ─┘ so they cannot drift

Status

Reads are working and verified against a live league.

Writes are proven. A six-item batched lineup change against a live 14-team league returned 200 EXECUTED on 2026-09-08. The confirmation token survived across two separate CLI processes, the re-read-before-write guard held, and the audit log captured intent and outcome with credentials redacted.

Add/drop and waiver writes are built and their payloads validated by ESPN's own parser, but have not yet been fired authenticated. Trades are designed, not built.

Before you use this

Unofficial. Not affiliated with, endorsed by, or sponsored by ESPN or Disney. It drives a private, undocumented API that can change or break without notice.

It runs on your machine, with your credentials, on your own account. It operates no service and redistributes nothing.

"Runs locally" is not "stays local." Everything this server returns is handed to whatever MCP client and model you configure — for most people, a hosted service. There is no telemetry and no network connection other than to ESPN, but that is not the same as your data staying on your machine.

What you're risking. ESPN's terms restrict unofficial automated access. ESPN can suspend or terminate your account and your league, and neither this project nor its author can restore anything they take. Read Disney's Terms of Use and ESPN's Fair Play and Conduct rules and decide for yourself. Note ESPN ships its own "Auto Control" AI that makes roster moves — automation is not itself against the spirit of the game — but that is not permission to drive the private API from outside.

espn_s2 is a full ESPN/Disney session cookie, not a scoped token. Leaking it is account compromise. Never paste it into an issue, a chat, or a screenshot.

Licence

MIT. Permissive: use it, change it, ship it, sell it. The only thing it asks is that the copyright notice travels with the code.

It covers this project's own code. It grants no rights to ESPN's data, marks, or API, and it is not permission from ESPN.

Vendored code

src/fantasy_yolo/espn/ is a vendored copy of the football half of espn-api by Christian Wendt, also MIT. Its licence is preserved verbatim at LICENSE-espn-api, as MIT requires when redistributing.

It is vendored rather than depended on so defects in the read path can be fixed directly. The first was a POSITION_MAP that could not convert a lineup slot label back to its id for bench, IR or flex — which made lineup writes impossible. Our changes are marked fantasy-yolo: in comments, and its own test suite runs in our CI.

Available Tools

2 tools
get_matchupC
Read-only

Who you are playing this week, and the projected score for both sides.

ParametersJSON Schema
NameRequiredDescriptionDefault
weekNo
leagueNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
summaryYes
opponentYes
provenanceYes
my_projectedYes
their_projectedYes

TDQS

C2.9/5.0
Behavior3/5

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

Annotations already declare readOnlyHint=true and destructiveHint=false, so the safety profile is covered. The description adds the nuance that the score is projected and the context is 'this week', but it does not disclose any additional behavioral traits such as defaulting behavior, rate limits, or authentication needs.

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, concise sentence with no filler words. It is front-loaded with the most important information and every word contributes meaning.

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

Completeness2/5

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

Despite an output schema being present, the complete lack of parameter documentation in both the schema and description leaves significant gaps. An agent cannot confidently invoke the tool correctly without knowing how 'league' is used or what happens when 'week' is null.

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

Parameters1/5

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

Schema description coverage is 0%, and the description does not explain the 'week' or 'league' parameters at all. The phrase 'this week' only hints at the week parameter, while 'league' remains completely unexplained, leaving an agent unable to determine how to set these optional parameters.

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 identifies the resource ('who you are playing') and the output ('projected score for both sides'), making the tool's purpose immediately understandable. It does not explicitly contrast with the sibling get_roster, but the matchup focus is distinct enough on its own.

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 the sibling get_roster, no mention of alternatives, and no exclusions or prerequisites. An agent is left to infer usage solely from the tool name and purpose.

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

get_rosterB
Read-only

Your roster: lineup slots, NFL opponents, projections and injury designations.

Injury designations are exactly what ESPN reports. There is no timestamp and no news text behind them, so do not describe how recent one is (B-02).

ParametersJSON Schema
NameRequiredDescriptionDefault
weekNo
leagueNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
benchYes
summaryYes
startersYes
provenanceYes
open_roster_spotsYes
counts_by_positionYes

TDQS

B3.4/5.0
Behavior4/5

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

Annotations already provide readOnlyHint and destructiveHint, so the safety profile is covered. The description adds valuable behavioral context by explaining that injury designations are exactly as reported by ESPN and that there is no timestamp or news text, explicitly warning the agent not to describe recency. This goes beyond the structured metadata.

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

Conciseness4/5

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

The description is short and front-loaded, with the first sentence stating what the tool returns. The second sentence adds an important caveat without verbosity. The internal reference '(B-02)' is slightly cryptic but does not materially hurt clarity.

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?

With an output schema present and read-only annotations, the description does not need to explain return shapes or safety. It competently covers roster contents and the injury-designation limitation. The main gaps are the undocumented week/league parameters and the lack of any comparison to get_matchup, leaving the selection context incomplete.

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

Parameters2/5

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

Schema description coverage is 0%, and the description does not explain the 'week' or 'league' parameters at all. Their names are mildly suggestive, but the description does not clarify whether they control the roster week, which league, or what null defaults mean. With low schema coverage, the description was expected to compensate and did not.

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 identifies the resource ('roster') and its contents ('lineup slots, NFL opponents, projections and injury designations'), which is specific and informative. It does not use an explicit verb like 'get' or 'list', but 'Your roster' strongly implies retrieval. It distinguishes from get_matchup by subject matter, though it never names the sibling.

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 phrase 'Your roster' gives an implied usage context: use this when the user's lineup, projections, or injury statuses are needed. However, there is no explicit guidance about when not to use it or when to prefer get_matchup instead, so an agent must infer the boundary.

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

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 2 tool updatesv0.1.0
    • First observedget_matchup
    • First observedget_roster

TDQS

B3.3/5.0

Scored across 2 tools

Disambiguation5/5

get_matchup and get_roster target completely different resources: one provides weekly opponent and projected scores, the other provides team roster details. There is no overlap or ambiguity between them.

Naming Consistency5/5

Both tools follow the exact same get_<noun> snake_case pattern, making the naming predictable and easy to understand.

Tool Count3/5

Two tools is borderline: they cover the core read-only queries for a fantasy matchup and roster, but the server is sparse compared to the typical footprint of a fantasy football tool.

Completeness2/5

The server is entirely read-only and limited to matchup and roster information. There are no tools for lineup changes, player news, league standings, or transactions, which are significant gaps for fantasy football workflows.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Connect ESPN & Yahoo fantasy leagues to AI assistants via MCP. Read-only tools for rosters, standings, matchups, free agents, and league info across football and baseball.
    21
    MIT
  • F
    license
    A
    quality
    C
    maintenance
    Enables natural language interaction with Sleeper Fantasy Football API data, allowing queries about leagues, players, matchups, draft results, and trade analysis.
    13
    21
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables Claude to interact with Sleeper fantasy football leagues via MCP tools for roster, waiver, matchup, and transaction queries, plus a dashboard for daily reports, live scoring, and game-day alerts.
    70 npm
    MIT
  • F
    license
    B
    quality
    C
    maintenance
    Provides MCP-compatible LLMs with read-only access to normalized ESPN Fantasy Football league snapshots, team rosters, and free-agent data.
    3
    -