Inspiration
I work at one of the leading baseball research, technology, and training facilities in the world, where we train and advise high-profile professional players. That work doesn't pause: across a 162-game season, our trainers need to know what happened with their guys every single day — and knowing isn't enough. The job is turning last night's games into actionable insight for tomorrow's training.
We pride ourselves on having the best tools in baseball, but the best tool is worthless if a trainer has to stop coaching to learn it. That's the idea behind Dugout: insights through natural language, in the place our staff already lives — Slack. A trainer follows their athletes and the moments come to them (a home run card seconds after the swing, a nightly digest when the slate goes final, news when it breaks). When they need more, they just ask — a stat line, a whiff heatmap, a one-page advance report, or an open-ended question that takes real reasoning ("is his hot streak sustainable, or is it batted-ball luck?") — and get an answer grounded in data, without opening a single dashboard or learning a single new interface.
This hackathon build runs entirely on public MLB data, but the motive is the workflow itself: if the interface is a conversation, the tool disappears — and what's left is a coach, their player, and the insight.
What it does
- Follow anyone in MLB or the minors by just asking ("follow Julio Rodríguez with home run alerts"), with per-player toggles for HR watch, live at-bat pings, nightly digest, and news.
- Proactive alerts: a background poller watches every live game a followed player is in — home-run cards (exit velo, distance, headshot, Savant video link) land in your DM and in the shared #dugout-home-runs channel seconds after the play; a per-user boxscore digest arrives when the night's slate goes final.
- Ask anything, anywhere: the ✨ assistant panel for personal Q&A with live "Digging up clips…" status, or @mention the bot in any channel — it answers in threads and follows the conversation without re-mentions. Stats and game logs (MLB + 5 minor-league levels), careers, exact date windows, live game state, standings incl. the wild card race, probables, news, streaks, and leaderboards: fWAR (season or weekly), fastest pitches, hardest-hit balls, top bat speeds, a pitcher's every whiff — with video clips for any MLB play.
- Visual analytics on demand: 12 chart types (pitch movement, spray charts on a real stadium outline, velocity boxes, rolling stats…), 12 Gaussian-KDE heatmap stats, custom stat-table cards, and one-page scouting reports — a pitcher advance one-sheet vs L/R hitters and a full hitter profile, with headshots, team logos, and action shots. Work an analyst spends an hour on, composed deterministically in ~30 seconds.
- App Home: a live "what you can ask" capability registry (generated from the tool catalogs so it can't drift), plus your follows with one-click toggles.
- Zero-hallucination design: every number must come from a tool call — the agent is forbidden from answering stats from memory, aggregation happens in code (not in the model), ambiguous player names trigger a clarifying question (there are two active Max Muncys!), and unsupported asks get an honest "not supported" instead of improvisation.
How I built it — the three challenge technologies
- Slack AI capabilities — built on Bolt for Python's
Assistantclass over Socket Mode: assistant threads, contextual suggested prompts, per-tool status updates while the agent works, Block Kit everywhere (alert cards, digests, Home tab), threads rebuilt fromconversations.repliesso restarts lose nothing. - MCP server integration — the agent's brain (Claude Agent SDK) talks to a purpose-built MCP server: 33 MLB tools over streamable-http JSON-RPC, backed by the public MLB Stats API, Baseball Savant Statcast data, and official MLB.com feeds. The boundary is real: the identical server plugs into Claude Desktop over stdio — one data layer, any MCP client.
- Real-Time Search API — a
search_slacktool (exposed to the agent as an in-process MCP server) callsassistant.search.contextwith each event's action token, so the agent can answer "did anyone already post about Julio's homer?" from the workspace's own messages.
Storage is a single SQLite file (WAL) shared by the two systemd-managed processes; alert idempotency comes from a dedup table, so crashes and restarts never double-ping.
Challenges
- Live-game semantics: MLB's feed marks games "Live" during warmups with the probable starter pre-populated — alerting before the first pitch, double-sending after restarts, and "game day" rolling over on US/Pacific (not UTC) all needed careful handling. Even the LLM's own clock disagreed with the baseball calendar.
- Clip resolution: Statcast's bat-tracking CSV has no video IDs — I join it back to the live feed by (game, at-bat, pitch) to recover the Savant playId for every swing.
- Event redelivery: Slack retries events across reconnects; the bot's ⚾ acknowledgment reaction doubles as the duplicate-delivery guard.
What I learned
- Make the model narrate, never compute. My biggest quality jump came from a design rule, not a bigger model: every number must come from a tool call, all aggregation happens in code, and "best game" returns candidates by defined formulas instead of a vibe. When the agent once answered a team-stats question from its training data (wrong league leader, stale numbers), the fix wasn't a prompt tweak — it was building the missing tool and banning memory-stats outright. Deterministic tools turn an LLM from a clever guesser into a reliable interface.
- Observability can be adorable. The bot reacts 👀 when it hears you, 🧠 when it's reasoning, ⚾ when it calls a tool. That reaction trail isn't just UX delight — it's how I caught the hallucination above (🧠 with no ⚾ on a stats question = red flag) and how users learned to trust long-running answers.
- MCP as a real boundary pays rent. Because the brain talks to the data layer over actual MCP (not function calls), I swapped agent internals freely, tested tools with a plain MCP client, and got a bonus deliverable for free: the same server plugs into Claude Desktop over stdio.
- Slack's platform has sharp edges you only find by building: scopes and event subscriptions are separate things that both require reinstalls; events redeliver after reconnects (the ack reaction doubles as the dedup guard); assistant threads, plain DMs, and channel mentions are three different surfaces; and the RTS API needs a per-event action token.
- Public baseball data is deeper than expected — but full of traps: Savant's CSV ships with a BOM that mangles the first header, expected stats lag for in-progress days, and MLB's "game day" rolls over on Pacific time while the model's own clock runs UTC. Every one of those became a deterministic guard.
Accomplishments
A production-grade agent running continuously against live games: 33 MCP tools across four public data sources, 12 chart types + heatmaps + tables + two scouting-report generators, proactive alerts with restart-safe dedup, and a deterministic answer layer — built by one person and an AI pair-programmer in four days, entirely on public data.
What's next
Per-channel team feeds, richer prospect tracking (level-up alerts), the pitcher count-tendencies report page, fantasy-style weekly recaps, and a public MCP server release so any agent can follow baseball. Additional filters, pairing to more data sources, cleaner reports, additional sports?
Non-commercial fan project. Not affiliated with MLB. Data © MLB Advanced Media — see gdx.mlb.com/components/copyright.txt.
Log in or sign up for Devpost to join the conversation.