mcp-fleaflicker
This server does not currently have a deployable release on Glama. Follow the repository's setup instructions to install and configure it for your MCP client, including any required credentials and local dependencies.
mcp-fleaflicker
An MCP server for Fleaflicker fantasy leagues. It reads rosters, standings, matchups, boxscores, and draft boards, and it scores stat lines using the league's own published scoring rules rather than a hardcoded scoring system.
Fleaflicker's read API is public and unauthenticated, so this server needs no API key, holds no upstream credential, and exposes no write surface. It cannot change your league.
The server's own HTTP endpoint is a different matter: it requires a bearer token and refuses to start without one. See Authentication.
Why the scoring engine is the interesting part
Most fantasy tooling hardcodes a scoring system, or offers a handful of presets. This
server fetches FetchLeagueRules and evaluates whatever it finds, so the same code scores
a 6-point-passing-TD superflex league and a standard half-PPR league without a branch.
Three things about Fleaflicker's rule model are load-bearing, and each was verified against the live API rather than assumed:
Zero-valued fields are omitted from the JSON. The API is protobuf-derived and
serializes defaults by leaving them out. The shutout rule ("10 extra points when total
Points Allowed is exactly 0") arrives with no boundLower and no boundUpper key,
because both are 0. Default a missing upper bound to infinity and every defense scores
the shutout bonus every week. Missing bounds mean zero, never "unbounded".
RANGE_LOWER_BOUND is two different rules wearing one name. Which one depends on
category.multiValue. On a single-valued category it is a once-per-game threshold on the
summed total (300 passing yards is +1, once, no matter how far past). On a multi-valued
category it fires once per qualifying event (every touchdown of 80+ yards is +4 each).
A touchdown is two categories, not one. The flat 6 points and the long-distance bonus live under separate category ids that share a name within a group. They are linked automatically by (group, name), so one caller-supplied stat key feeds both.
Score one game at a time
Every threshold rule fires on a single game's box score. A receiver with 2,000 yards across 17 games earns the 150-yard bonus only in the games he actually cleared 150. Feeding the engine a season total silently miscounts every bonus in the rule set.
Ambiguous stat keys fail loudly
"Interception" is a passing category worth −2 (the quarterback threw it) and a defensive
category worth +2 (the defense caught it). Resolving a bare interception to both nets
two picks to exactly zero and reports a confident, wrong number. So any key reaching more
than one scoring group refuses to resolve and names the alternatives instead:
passing_interception and defense_interception.
Related MCP server: espn-fantasy-mcp
Tools
Tool | What it does |
| Roster construction and the complete scoring rule set, including the valid stat keys |
| Records, points for and against, streaks, draft and waiver position |
| One team's lineup by slot, for any week or season |
| A week's head-to-head games and scores |
| Full matchup detail, both lineups, with Fleaflicker's own computed points |
| Every pick in true selection order, snake-aware, with |
| The player pool by name or position, with ranks, projections and ownership |
| The best undrafted players left, ranked by the league's own projection |
| Score a stat line under the league's real rules, with a per-rule breakdown |
All tools are read-only and idempotent. Every tool returns a JSON string in one of two shapes:
// success
{"data": ...}
// failure — code is from a fixed enum
{"error": "human-readable message", "code": "UPSTREAM_DOWN", "details": {...}}Codes: UPSTREAM_DOWN, AUTH_FAILED, INVALID_INPUT, NOT_FOUND, RATE_LIMITED,
INTERNAL. No exception ever reaches the caller.
Quick start
git clone https://github.com/pete-builds/mcp-fleaflicker.git
cd mcp-fleaflicker
cp .env.example .env # set FLEAFLICKER_LEAGUE_ID, or leave it blank
chmod 600 .env
python3 -c "import secrets; print(secrets.token_urlsafe(32))"
# paste that value into .env as MCP_AUTH_TOKEN=<token>
docker compose up -d --build
curl -s localhost:3727/healthzThe token step is not optional. Without MCP_AUTH_TOKEN in .env the container exits
at once with Refusing to start streamable-http with no auth provider.
Then register it with your MCP client, sending the same token:
claude mcp add --transport http --scope user fleaflicker http://localhost:3727/mcp \
--header "Authorization: Bearer <token>"Running from source instead:
python -m venv .venv && .venv/bin/pip install -r requirements-dev.in
MCP_AUTH_TOKEN=<token> .venv/bin/python server.py # HTTP on :3727
MCP_TRANSPORT=stdio .venv/bin/python server.py # stdio, no token neededAuthentication
Over HTTP, which is the default transport and what docker compose runs, a bearer
token is required. There is no setting that turns this off.
No token, no server. If
MCP_AUTH_TOKENis unset, empty, or blank, the process exits non-zero before it opens a port.MCP_AUTH_REQUIRED=falsedoes not change that; it used to mean "run open" and is now ignored for HTTP.Every request to
/mcpmust carry it. A missing or wrongAuthorization: Bearer <token>header is answered401./healthzneeds no token. It sits outside the auth layer so the container healthcheck and an uptime monitor can read it. It reports liveness and configuration only, and never the token.stdio needs no token. It is a local pipe with no network listener. Set
MCP_TRANSPORT=stdioand nothing else.
Generate a token:
python3 -c "import secrets; print(secrets.token_urlsafe(32))"Put it in .env on the host as MCP_AUTH_TOKEN=<token> and make the file readable by
you alone (chmod 600 .env). Keep it out of docker-compose.yml and out of git; .env
is already gitignored and excluded from the image.
A client entry, for clients configured by JSON (.mcp.json and similar):
{
"mcpServers": {
"fleaflicker": {
"type": "http",
"url": "http://localhost:3727/mcp",
"headers": { "Authorization": "Bearer <token>" }
}
}
}From another machine, replace localhost with the host's address. To rotate the token,
change it in .env, run docker compose up -d, and update each client.
Finding your league id
It is in the league URL: fleaflicker.com/nfl/leagues/14153 is league 14153.
Setting FLEAFLICKER_LEAGUE_ID makes every tool's league_id argument optional. Leaving
it unset is also valid: callers then pass league_id per call, which is how one
deployment serves several leagues.
Live-draft notes
Three upstream behaviours matter during a draft, each verified against the live API rather than assumed.
The board states the true pick number; do not count cells. Every cell of
FetchLeagueDraftBoard carries slot = {round, slot, overall}, and that
overall is already snake-aware. The grid renders each team in a fixed column,
so in a snake the even rounds fill right to left and a left-to-right counter
assigns the wrong number to every pick in them. get_draft_board reads the
field and reports draft_type outright.
FetchPlayerListing rejects a season parameter. Any value, including the
current season, returns HTTP 400 with an HTML body while every other endpoint
requires one. search_players and get_available_players still accept
season and deliberately never forward it.
filter.position.label is accepted and ignored. Asking the listing for
tight ends returns quarterbacks and defenses at HTTP 200. Position filtering
happens client-side.
Configuration
Variable | Default | Purpose |
| (unset) | Default league. Optional; every tool takes a per-call override |
|
| Only NFL has been verified |
|
| Override for testing against a mock |
|
| Per-request timeout, seconds |
|
| Listen port |
|
|
|
| (unset) | Required for HTTP. The bearer token every client sends to |
|
|
|
Scoring examples
Get the valid stat keys first:
get_league_rules() -> data.scoring.stat_keysA plain number is a count or total. A list is per-event yardage, which is what distance bonuses need:
score_stat_line(
stats={"passing_yard": 318, "passing_td": [45, 12], "passing_interception": 1},
position="QB",
)That is two passing touchdowns, one of 45 yards and one of 12: the flat rule scores twice
and the 40-to-79 bonus once. Passing {"passing_td": 2} instead scores the flat rule and
reports the distance bonus under unresolved_bonuses, rather than silently scoring it as
zero.
The response carries the arithmetic, not just the total:
{"data": {
"position": "QB",
"total": 31.52,
"breakdown": [
{"category": "passing_yard", "kind": "linear", "points": 8.72,
"detail": "218 / 25 x 1", "rule": "1 point for every 25 Passing Yards (0.04 per)"},
...
],
"unscored_keys": [],
"unresolved_bonuses": []
}}Testing
.venv/bin/python -m pytest -q # 121 tests
.venv/bin/ruff check .Fixtures under tests/fixtures/ are real captures from the live API, trimmed to the
fields the code reads. They are real rather than hand-written on purpose: the behaviours
that bite here — omitted zero fields, one touchdown spanning two category ids, a bonus
whose bounds are both absent — are exactly the ones a hand-written fixture would smooth
over.
The scoring tests include oracle tests that reconstruct real stat lines and assert against the totals Fleaflicker itself published for those games, so the engine is checked against the platform rather than against its own arithmetic.
What this does not do
It builds no model of its own. There is no ranking engine, no waiver recommendation, and no start/sit advice.
It does now surface a projection, but that projection is Fleaflicker's, not this
server's: projected_points is the platform's own viewingProjectedPoints, already
computed under the league's real scoring rules, and it is passed through unchanged.
Read it next to rank_draft, which is the default board the rest of the league drafts
from, and the gap between the two is the whole mispricing signal. For a projection this
server computes, feed your own projected stat line into score_stat_line; those stat
lines still have to come from somewhere else.
It is also unofficial and not affiliated with Fleaflicker. The API it uses is public but undocumented, so a shape change upstream can break a normaliser; the tests are built to catch that quickly.
License
MIT. See LICENSE.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
This server cannot be deployed
Maintenance
Related MCP Connectors
Fantasy analysis for your ESPN, Yahoo, and Sleeper leagues. Reads your leagues, never changes them.
NFL/NBA/MLB/NHL/PGA + DFS and prediction-market data. Browse free; query with a free API key.
Independent DraftKings Pick6 lines/history, DFS salaries, odds, box scores. Free: 100 requests/month
Fantasy football across all your leagues: Yahoo, ESPN, Sleeper. Who to root for, waivers, scores.
1
Related MCP Servers
- AlicenseAqualityDmaintenanceProvides read-only access to the Sleeper Fantasy Sports API for league info, rosters, matchups, drafts, transactions, and player data.18135 npm1MIT
- AlicenseAqualityBmaintenanceEnables AI clients to read and write ESPN fantasy sports leagues using your own session cookies, including managing rosters, lineups, waiver claims, and free agent adds without a browser.171MIT
- AlicenseBqualityBmaintenanceEnables reading Sleeper fantasy football rosters, matchups, standings, news, chat, and pick'em entries, with optional write actions for setting lineups, claiming waivers, and proposing trades.392MIT
- FlicenseAqualityCmaintenanceEnables access to Sleeper fantasy football data, including users, leagues, rosters, matchups, transactions, drafts, and player information.1313 npm-