espn-fantasy-mcp
AGENTS.md1 starsChanged 16 days agoMITView on GitHub
Instructions for coding agents (Claude Code, Codex, Cursor, Gemini CLI and others) working in this repo. CLAUDE.md imports this file. Read-only ESPN fantasy football service, exposed as an MCP server that should work with any MCP client (Claude Code is the primary one). The assistant reads rosters, matchups, free agents and league activity through it and recommends moves; the user makes them in the ESPN app. Start here: - docs/design.md is the source of truth for scope, architecture, the MCP…
- Reads credentials
- Installs packages
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,542 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository5 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- scoreboardSkill
- tradesSkill
- waiversSkill
- weekly-checkSkill
The file
We split this AGENTS.md into the rules it gives an agent
4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 21
If corepack enable fails on permissions, use npm i -g pnpm@11 instead.
- Very specific: line 61
Before calling a change done, run build, typecheck, lint and test.
- Very specific: line 81
Read-only toward ESPN: No write calls. The only thing the service writes is leagues.json, via setLeagueRule.
- Very specific: line 87
The HTTP token is a password: Never log it or echo it into tracked files.
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
AGENTS.md
Instructions for coding agents (Claude Code, Codex, Cursor, Gemini CLI and others) working in this repo. CLAUDE.md imports this file.
Read-only ESPN fantasy football service, exposed as an MCP server that should work with any MCP client (Claude Code is the primary one). The assistant reads rosters, matchups, free agents and league activity through it and recommends moves; the user makes them in the ESPN app.
Start here:
docs/design.mdis the source of truth for scope, architecture, the MCP tool list, skill specs and the trades design.STATUS.mdholds what's done, what's next and open questions. It's the handoff between sessions and machines (agent memory doesn't travel), so update it when a step or notable task finishes.
This repo is public. Keep these out of tracked files: personal league or team names, league ids, cookies, and plans for the hosted web app (which lives in a separate private repo). A private build plan, architecture notes and deployment runbooks (*.private.md) may sit untracked in docs/ (or the repo root), excluded via .git/info/exclude rather than .gitignore so their names stay out of the public repo. Read them for context if present, but never commit them or copy them into tracked files. If they're copied to a new clone, add them to that clone's .git/info/exclude first.
Fresh clone / new machine
nvm install 24 && nvm use # .nvmrc pins 24; pnpm 11 needs Node >= 22.13
corepack enable # provides pnpm@11.8.0 from package.json "packageManager"
pnpm install && pnpm build && pnpm testIf corepack enable fails on permissions, use npm i -g pnpm@11 instead.
Some files never travel through git, so copy them from the other machine by hand (USB, password manager), never through chat or a commit:
.env: ESPN cookies.leagues.json: league ids, team ids, stored rules.- Any private notes: the build plan, architecture notes,
*.private.mdrunbooks. In the new clone, add their names and*.private.mdto.git/info/excludefirst.
.env and leagues.json are gitignored, so a clone doesn't have them. Either copy both from the other machine, or recreate them from .env.example and leagues.example.json following the README. Never ask the user to paste cookie values into chat: they edit .env themselves. Then run pnpm smoke to confirm live access.
The repo's .mcp.json launches apps/mcp/dist/index.js, so build before starting a Claude Code session there, and approve the espn-fantasy server when asked. A session that's already running keeps the old build after pnpm build, so reconnect it with /mcp → Reconnect to pick up changes. New skills only load when a session starts.
Commands
Node 24 is required; the shell's default Node may differ, so run nvm use first. Agent shells are non-interactive and don't load nvm, so prefix commands with source ~/.nvm/nvm.sh && nvm use &&. pnpm 11 comes from corepack.
pnpm build # tsc -b in every package (core depends on espn-client's dist)
pnpm test # vitest, offline, against recorded fixtures
pnpm typecheck # packages + tests + scripts
pnpm lint # eslint
pnpm format # prettier
pnpm smoke # LIVE: hits ESPN for every league in leagues.json; needs .envTo exercise the MCP server without a chat client (LIVE, after pnpm build):
npx @modelcontextprotocol/inspector --cli node apps/mcp/dist/index.js --method tools/list
npx @modelcontextprotocol/inspector --cli node apps/mcp/dist/index.js --method tools/call \
--tool-name get_my_roster --tool-arg league=<alias>To exercise the HTTP entry point locally (LIVE):
MCP_AUTH_TOKEN=$(openssl rand -hex 32) PORT=3999 pnpm start # in one shell
npx @modelcontextprotocol/inspector --cli "http://localhost:3999/mcp/<token>" --transport http --method tools/listBefore calling a change done, run build, typecheck, lint and test. CI (.github/workflows/ci.yml) runs the same on every push and pull request. Run pnpm smoke when a change touches parsing or requests, since fixtures are from 2018–2019 and the live shapes can drift.
Layout and boundaries
packages/espn-client: the only code that knows ESPN's JSON.raw.tsholds the response shapes,parse.tsthe pure parsers,client.tsthe requests,types.tsthe typed output. Nothing outside this package imports raw shapes.packages/core: zod schemas (schemas.ts) are the contract every adapter uses.map.tsmaps client types to schemas,rules.tshandles league rules discovery,service.ts(FantasyService) holds the domain functions, andconfig.tsloadsleagues.json.apps/mcp: a thin MCP adapter overFantasyService(SDK v2,@modelcontextprotocol/server). Tool descriptions must make sense to any model, not just Claude.server.tsregisters the tools and prompts.load.tsfindsleagues.jsonand.env, andseedConfigwritesLEAGUES_JSONtoLEAGUES_CONFIGon first start.index.tsis the stdio entry.http.tsis the HTTP server: the token check (path or bearer),/health, and a bridge to the SDK'screateMcpHandler.serve.tsis its entry point, run bypnpm start.tenants.tsmaps tokens to people: the environment's setup plus one folder per person underMCP_TENANTS_DIR. A folder tenant must never inherit the server'sESPN_S2/SWID(folderTenantEnv).
skills/<name>/SKILL.md: lineup and waiver judgment lives here, not in code. Core has no opinions. Each file is the single source for two forms:- Claude Code loads it through the
.claude/skills/<name>symlink. apps/mcp/src/skills.tsserves the same text as an MCP prompt (weekly-checkbecomesweekly_check).- A test fails if a skill names a tool the server doesn't have.
- Edit the
SKILL.mdand both forms follow. Add a symlink for a new skill.
- Claude Code loads it through the
Rules
- Read-only toward ESPN. No write calls. The only thing the service writes is
leagues.json, viasetLeagueRule. - Secrets:
ESPN_S2andSWIDcome from.envand must never be logged, returned in tool output, or committed..envandleagues.jsonare gitignored and must stay that way. Only*.example*templates are tracked. - Port, don't reinvent. Request shapes (views,
x-fantasy-filterheaders) follow cwendt94/espn-api.constants.tsis generated byscripts/port_espn_constants.py, so regenerate it rather than hand-editing. - Stored rules override ESPN. Rule keys in
RULE_KEYSare dotted paths intoLeagueSettings. Add a new rule by adding its path there. - Compact output. Tool payloads cost tokens, so keep schemas lean and never pass raw ESPN JSON through.
- Hosting-agnostic. Don't put provider-specific deploy config or docs in tracked files: no
railway.json,fly.tomlor provider names. The repo documents the generic contract (pnpm start, the environment variables,/health). A particular deployment's runbook belongs in an untracked private note (*.private.mdis excluded via.git/info/exclude), and its settings live on the host. - The HTTP token is a password. Never log it or echo it into tracked files.
http.tsstrips it from the URL and headers before the MCP handler sees the request.
Conventions
- ESM throughout, so relative imports use the
.jssuffix. Strict TS withnoUncheckedIndexedAccess. Packages are@nscaamano/espn-fantasy-client,-coreand-mcp. The client and core are set up for npm (publishConfig, aprepackthat copies LICENSE, and THIRD_PARTY_NOTICES for the client). Publish withpnpm publishfrom the package folder, soworkspace:*becomes a real version. Bump both versions together. The MCP server stays private until it can findskills/without the repo. - TypeScript is pinned to 6.0 because typescript-eslint doesn't support 7 yet. zod is v4 (
z.partialRecord,z.prettifyError). - Parsers return
nullfor "ESPN didn't say", never a guessed default. Rules discovery depends on that difference. - Tests: parser tests use
packages/espn-client/test/fixtures/. Service and MCP tests usefixtureFetch(), which routes requests to fixtures byview; MCP tests connect a real client overInMemoryTransport. No network in unit tests. Use generic names in tests (no real leagues). - Scripts in
scripts/*.tsrun with Node's native type stripping (node scripts/x.ts), so use erasable TS syntax only (no enums or parameter properties).
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

