bbagent
Provides scouting data from MLB StatsAPI for fantasy baseball analysis, including player statistics and performance projections.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@bbagentoptimize my lineup for tonight's games"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
bbagent — autonomous ESPN Fantasy Baseball manager
A competitive agent that owns and operates one team in an ESPN H2H points
league: lineup + IL optimization, waiver add/drop, and trades (proposing fair
offers out, reacting to declines, evaluating incoming). It scouts with live
MLB StatsAPI data blended with ESPN's projections. Every move it decides is
appended to actions.log, and it can email a readable report each run.
It runs two ways — the analysis is identical, only who drives the loop differs:
Deterministic / API | Claude Code (MCP) | |
Driver | built-in logic, or an Anthropic-API LLM loop | Claude Code, on your Pro/Max subscription |
Cost | free ( | your subscription — no API key |
Entry point |
|
|
1. Setup (once)
pip install -r requirements.txt # requests; mcp[cli] for Claude Code; anthropic for `run`
cp .env.example .env # then edit .env — see belowPut your secrets + league identity in .env (gitignored):
BBAGENT_ESPN_S2=<long cookie>
BBAGENT_SWID={<cookie-with-braces>}
BBAGENT_LEAGUE_ID=<number in your league URL>
# optional: BBAGENT_AGENT_TEAM_ID=<id> (otherwise auto-resolved from SWID)Get ESPN_S2 and SWID from a logged-in browser: DevTools → Application →
Cookies on fantasy.espn.com (SWID includes the curly braces).
Non-secret tuning (scouting weight, trade thresholds, slot ids, email host)
lives in bbagent.config.json, which is safe to commit. Config resolves
highest-priority first:
BBAGENT_<KEY> env var → bbagent.local.json → bbagent.config.json → defaultVerify what loaded (secrets masked):
python espn_agent.py config # prints every resolved value + source
python espn_agent.py selftest # offline, no network — proves the logic
python espn_agent.py discover # lists teams; marks your auto-matched team *Related MCP server: Yahoo Fantasy Baseball MCP Server
2. Run it once, manually
a) Deterministic (free, no API key, no Claude)
python espn_agent.py plan # print-only: IL, lineup, waivers, fair tradesplan is read-only by default — it analyzes, logs every recommendation to
actions.log, emails the report if email is configured, and submits nothing.
To actually act, add flags (see §4 enabling writes):
python espn_agent.py plan --execute # simulate the moves (dry-run payloads)
python espn_agent.py plan --execute --commit # really submit moves + trade offersUseful one-off flags: --no-scouting, --scouting-weight 0.7, --max-trades 2,
--email / --no-email.
b) Claude Code (no API key — uses your subscription)
The same tools are exposed over MCP; Claude Code is the agent loop. From this
directory (it ships a .mcp.json, so the bbagent server auto-registers —
approve it once):
pip install "mcp[cli]" # required for the server (the --selftest below skips it)
python bbagent_mcp.py --selftest # offline smoke test of every tool
claude # start Claude Code here — see approval note below
# then ask: "Use bbagent to manage my team to win this week."First run: approve the server. A project-scoped server from
.mcp.jsonstays⏸ Pending approvaluntil you approve it once in an interactiveclaudesession (it prompts on launch;/mcpshows status). Until then its tools don't load and Claude will say it can't find them.claude mcp listshould readbbagent: ... ✓ Connectedonce it's good. (--selftestpasses even when themcppackage is missing — it uses fixtures — so a green selftest does not prove the live server will start; thepip installabove is what does.)
Claude Code calls full_plan, check_il, optimize_lineup, scout_trades,
review_declined_trades, etc. and proposes a batch. Agent guidance lives in
CLAUDE.md. Writes stay dry-run until BBAGENT_COMMIT_WRITES=1 (in .mcp.json
env).
It emails you the same report the deterministic version does: the
email_report tool renders through the identical plain-text + HTML template, so
a recipient can't tell an agent-driven pass from a deterministic one — except for
a single Run by: line (Claude Code agent vs. deterministic engine).
CLAUDE.md tells Claude to call it at the end of every pass, populated with what
it actually decided. (full_plan also auto-emails its dry-run plan when SMTP is
set.) SMTP config: host in bbagent.config.json, SMTP_PASS in .env — see §1.
Without SMTP configured it no-ops with a clear "not configured" message.
c) Anthropic API LLM loop
export ANTHROPIC_API_KEY=sk-ant-...
python espn_agent.py run "Manage my team to win this week."Same tools, driven by an API model instead of Claude Code. Costs credits.
3. Run it on a schedule (host on a server)
Fantasy management is a daily job, so the robust pattern is a scheduled run, not a long-lived daemon. Pick the run method (3.1–3.3) — that decides the command. Then schedule that command with your platform's scheduler (3.4). All of this assumes Setup (§1) is done on the host and, for live writes, that you've completed the write verification (§4).
In the scheduler examples below, RUN_CMD stands for whichever command you
chose. Replace /path/to/bbagent (or C:\path\to\bbagent) with the real path,
and python with python3 if that's your interpreter name.
3.1 Deterministic — recommended for unattended runs
Most reliable to automate: one deterministic command, no model in the loop. Logs
to actions.log and emails the report each run.
RUN_CMD = python espn_agent.py plan --execute --commitDrop --commit (or --execute) to email yourself recommendations without
touching the roster.
3.2 Claude Code (headless) — LLM-driven on your subscription
Drives the same MCP server on your Pro/Max subscription (no API key). The host
must have claude installed and authenticated — either log in interactively once
(claude), or set a CLAUDE_CODE_OAUTH_TOKEN from claude setup-token for a
headless host (same token the container uses, see §3.6). For it to submit moves,
set BBAGENT_COMMIT_WRITES=1 in .mcp.json's env block.
RUN_CMD = claude -p "Use bbagent to run a full management pass: optimize the lineup, work the waivers, and send any fair trade offers. Then summarize what you did." --mcp-config .mcp.json --allowedTools "mcp__bbagent__*"Two things make this work headlessly, and both bite silently if missed:
Approve the server once, interactively, first. A
.mcp.jsonserver is⏸ Pending approvaluntil approved in a realclaudesession — and headless-pcan't show that prompt, so the tools never load and the run does nothing useful. Runclaudeonce on the host and approvebbagent(verify withclaude mcp list→✓ Connected) before scheduling.Pre-grant the tool calls.
--permission-mode acceptEditsonly auto-accepts file edits, not tool/command calls — a headless run under it stalls waiting for approval. Use--allowedTools "mcp__bbagent__*"(allow just the bbagent tools, as above) or--dangerously-skip-permissions(allow everything).
Prefer this mode when you want model judgment on close calls; prefer 3.1 for pure determinism.
3.3 Anthropic API loop
Same tools, driven by an API model; bills credits per run. Needs
ANTHROPIC_API_KEY in the environment (put it in .env).
RUN_CMD = python espn_agent.py run "Run a full management pass and act on it."3.4 Schedule RUN_CMD on your platform
Each example runs daily at 9:00 AM. Secrets come from .env on the host (or
BBAGENT_* env vars) — never hard-code them in the scheduler entry.
macOS — launchd (per-user, survives reboots). Save as
~/Library/LaunchAgents/sh.laggi.bbagent.plist, then
launchctl load ~/Library/LaunchAgents/sh.laggi.bbagent.plist:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>sh.laggi.bbagent</string>
<key>WorkingDirectory</key><string>/path/to/bbagent</string>
<key>ProgramArguments</key>
<array>
<string>/bin/sh</string><string>-lc</string>
<string>python3 espn_agent.py plan --execute --commit</string>
</array>
<key>StartCalendarInterval</key><dict>
<key>Hour</key><integer>9</integer><key>Minute</key><integer>0</integer>
</dict>
<key>StandardOutPath</key><string>/path/to/bbagent/cron.log</string>
<key>StandardErrorPath</key><string>/path/to/bbagent/cron.log</string>
</dict></plist>(macOS also has cron; the Linux line below works there too.)
Linux — cron. crontab -e, then add:
0 9 * * * cd /path/to/bbagent && python3 espn_agent.py plan --execute --commit >> cron.log 2>&1For Claude Code / API on Linux, cron drops most env vars — source .env first:
0 9 * * * cd /path/to/bbagent && set -a && . ./.env && set +a && RUN_CMD >> cron.log 2>&1Windows — Task Scheduler. In PowerShell (one line):
schtasks /create /tn bbagent-daily /sc daily /st 09:00 /tr "cmd /c cd /d C:\path\to\bbagent && python espn_agent.py plan --execute --commit >> cron.log 2>&1"Swap the python ... part for your chosen RUN_CMD. Run schtasks /run /tn bbagent-daily once to test it immediately.
3.5 Container watcher (continuous, any platform)
Platform-agnostic and the easiest to host: one always-on container that watches
the league on a short interval and runs a full pass every tick — no OS scheduler
to wire up. A watcher (not a once-a-day batch) is the right model here because
lineups, injuries, incoming/declined offers, and waiver windows all move on their
own clock; polling every few minutes catches them as they happen. The repo ships
a Dockerfile, a .dockerignore, and run-loop.sh (poll → full pass → sleep
INTERVAL → repeat). Build and run:
docker build -t bbagent .
docker run -d --name bbagent --restart unless-stopped --env-file .env bbagent--env-file .env injects your secrets at run time; .dockerignore keeps .env
out of the image so they're never baked in. --restart unless-stopped is what
makes it "always running" across reboots and crashes.
Running a full pass every tick is safe to repeat: plan reads ESPN's pending
transactions and won't re-offer players already tied up in a pending trade,
declined offers are routed to counter-or-abandon (never blindly re-sent), and
waiver claims are bounded by max_waivers (default 1/pass). So a 10-minute poll
reacts fast without spamming offers or churning the roster.
Override the command or poll cadence with env vars (defaults: the deterministic
plan --execute --commit, every 600s = 10 min):
docker run -d --env-file .env \
-e RUN_CMD="python espn_agent.py plan" \ # watch + recommend only, no writes
-e INTERVAL=300 \ # poll every 5 min
bbagentLogs go to loop.log inside the container (docker logs bbagent also works).
All three run modes work in the container — the image bundles the claude CLI,
so the Claude Code (3.2) path runs in-container too (see §3.6 for headless
auth).
3.6 Claude Code in the container (headless auth)
The image ships Node + the claude CLI, so you can drive the Claude Code run
mode without a host install. The only catch is auth: there's no interactive
browser login in a container, so inject a token at run time instead. Two options:
Subscription (Pro/Max, no API credits). On a machine where you're logged in, run
claude setup-tokenonce and copy the printed token. Pass it asCLAUDE_CODE_OAUTH_TOKEN.API key. Pass
ANTHROPIC_API_KEY(bills credits).
Put whichever you use in .env (it's gitignored and injected with --env-file),
then point RUN_CMD at claude:
docker run -d --name bbagent --restart unless-stopped --env-file .env \
-e RUN_CMD="claude -p 'Use bbagent to run a full management pass: optimize the lineup, work the waivers, and send any fair trade offers. Then summarize what you did.' --mcp-config .mcp.json --dangerously-skip-permissions" \
bbagentTwo gotchas, both unique to running headless in a container — neither has an interactive escape hatch, so get them right up front:
No approval prompt exists. A
.mcp.jsonserver normally needs a one-time interactive approval, which a container can't show — so--mcp-config .mcp.jsonalone leaves the tools⏸ Pendingforever and the pass does nothing.--dangerously-skip-permissionsbypasses both the trust approval and the per-call tool prompts, which is what makes the server load unattended. (On a host you'd instead approve once withclaudeand use the narrower--allowedTools "mcp__bbagent__*"; see §3.2.)The image must have the
mcppackage. It's inrequirements.txt, so the image'spip installcovers it — just don't strip it out. Without it the server fails to start even though--selftest(fixture-based) still passes.
Writes still stay dry-run until BBAGENT_COMMIT_WRITES=1 in .mcp.json's env
block. The watcher reruns this every INTERVAL like any other RUN_CMD.
4. Enabling real writes (one-time verification)
Writes are gated behind the COMMIT_WRITES switch (--commit, or
BBAGENT_COMMIT_WRITES=1) and use ESPN's v3 transactions endpoint. The payload
shapes (lineup ROSTER moves, ADD/DROP, TRADE) follow ESPN's protocol, but the
write API is unofficial — verify once before trusting it:
Log in, open DevTools → Network (Fetch/XHR).
Make a real, harmless lineup move in the UI; submit.
Compare that POST's URL + JSON body to the
would_postpayload every dry-run prints (plan --executewithout--commit).If they match, run with
--commit. If ESPN tweaked the shape, adjustEspnClient._post_txn/ the item builders to match.
Until then, nothing is ever POSTed — you only see the payloads.
Note: writes succeed only for a team your
SWIDowns or co-manages. PointingAGENT_TEAM_IDat another team is fine for analysis/email, but ESPN will reject write attempts to a roster you don't control.
5. Scouting & projections
By default plan blends live MLB StatsAPI rest-of-season projections with
ESPN's own (scouting_weight = the external share). Tune or disable via config
(use_scouting, scouting_weight) or flags (--no-scouting,
--scouting-weight). You can also feed custom projection CSVs:
from espn_agent import EspnClient, plug_projections
import projections as proj
client = EspnClient()
overrides, unmatched = proj.build_overrides(
client, hitters_csv="hitters.csv", pitchers_csv="pitchers.csv")
plug_projections(overrides)Files
espn_agent.py— ESPN client, deterministic core, CLI, API loop, email.bbagent_mcp.py— MCP server exposing the toolset to Claude Code (no API key).scouting.py— MLB-StatsAPI outside source → league-scored ROS values.projections.py— external-CSV → league-scored value pipeline.bbagent.config.json— non-secret tuning knobs (committed)..env— secrets + league identity (gitignored; see.env.example)..mcp.json— Claude Code server config (auto-discovered in this directory).CLAUDE.md— operating instructions Claude Code loads automatically.actions.log— append-only audit of every move (gitignored).Dockerfile— image with Python deps + Node/claudeCLI; runs all three run modes (§3.5–3.6).run-loop.sh— the container watcher loop (poll → full pass → sleepINTERVAL); the image's defaultCMD, also runnable standalone..dockerignore— keeps secrets (.env), logs, and local cruft out of the image.loop.log— the container watcher's output (gitignored;docker logstoo).
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
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/LaggySon/bbagent'
If you have feedback or need assistance with the MCP directory API, please join our Discord server