How it's built.
Muffed is an NFL media site: 33 podcast feeds, stat pages, a weekly email, a natural-language stat lookup and a public MCP server. Public NFL data comes in, I rebuild it into a verified panel where every figure carries its source, and every episode, page and answer reads from that panel, not from a model's memory. I have built and run it alone with Claude Code since February 2026.
Architecture
The app is Next.js 16 and TypeScript on Vercel, with Supabase for Postgres, auth and audio storage. Python builders turn seven nflverse feeds into one verified file per season, and a reshaping step turns those into the panel: one row per entity, season and metric. GitHub Actions rebuilds the current season weekly and reaches main only through a pull request I merge, loads the panel on merge, and refreshes a research warehouse daily. Vercel cron refreshes the stat pages daily.
The LLM layer
Two models run in production. Claude Opus 5 writes: every generated-episode prompt family, the fact audit and the /ask parser. Claude Sonnet 5 edits, runs web-search retrieval and reads rosters from screenshots. Episodes go writer, editor, audit: Opus drafts, Sonnet tightens and may cut but not add facts, and Opus checks every numeric claim against the verified facts with stated tolerances. Failures go back to the writer for up to two retries. On /ask, Opus makes one parse-only call that turns a question into a metric, season, order and limit; it never sees a row, and only 225 of 574 logged questions needed that call.
I write prompts as criteria and anti-patterns, not steps, and each is a pair: a system block of instructions with no per-call values, and a user turn with the data. That split is what makes prompt caching work. A script counts tokens in all 14 cached blocks and fails any under the model's cache minimum, and a smoke test passes only if a second real call reads from cache. The app provides tools more than it calls them; the one it calls is Anthropic's hosted web search, with a set search budget. In-season episodes are drafted in an agent session: Claude Fable drafts, Opus subagents research, and a separate subagent verifies.
The MCP server
muffed.ai/api/mcp is stateless, anonymous and read-only, with no model in the request path: every figure is a precomputed read. Its 14 tools cover stat leaders, player and team metrics, comparisons, metric definitions, a query tool that labels its output computed rather than verified, a Sleeper league hub, and verify_claim, which returns the actual value for a wrong figure. It is listed in the MCP Registry; ChatGPT and Claude call it as a connector, and /ask on the site shares its query layer. From September 4 to 16 it served 2,828 calls, 1,946 from ChatGPT and 206 from Claude.
Quality
The panel gate runs in CI on every pull request. Four Python stages re-derive all 239,830 rows from source with exact equality, check each row's licence credit against the feed its builder reads, and compare figures with what the source itself publishes. Nothing loads on red.
For generated scripts, a deterministic number audit parses every figure, spelled-out numbers included, and checks it against the expected stats; in QA it is a hard gate. Scripts built from a facts sheet fail on any digit not on the sheet, and the in-season loader is all-or-nothing: one bad block writes nothing. Number gates can't read prose, so drafts also get an adversarial pass: a subagent given the raw feeds and none of the drafter's reasoning, told to falsify every claim. In two rehearsals it found 14 errors, then 4, every one in prose.
Operations
PostHog carries product events and server-side error tracking. Every /ask and MCP question logs a verdict and a reason code, PostHog alerts on Sonnet spend, and two scripts turn the log into a Monday health read and a Tuesday triage list. A failed scheduled rebuild files a GitHub issue, because a cron failure emails no one. Unattended each week: the rebuild, the panel load, the daily warehouse and stat-page refreshes, and twice-weekly beat-reporting pulls. By design, I merge the data PR, load episodes and send the Wednesday email. 3,900 tests run in CI and a pre-push hook, and the operating rules live in a versioned CLAUDE.md.
By the numbers
- 886 since February 28, 2026
- 539
- 153,659 lines
- 50,518 lines
- 28,944 lines
- 18,432 lines
- 4,960 lines
- 1,203 lines
- 3,900 passing across 186 files
- 239,830 rows · 694 metrics · 2,075 entities · 2016–2025
- 1,601 published, all with audio
- 2,828 from September 4 to 16, 2026