{
  "markdown": "# Brotato Coach\n\n<!-- mcp-name: io.github.brendanlefebvre/spudcoach -->\n\nA deterministic theorycrafter for [Brotato](https://store.steampowered.com/app/1942280/Brotato/),\ndelivered as an MCP server you can chat with from Claude Code (and other MCP clients).\n\nThe design principle: **a deterministic core holds the ground truth** — weapon/item/character\ndata, DPS formulas, stat mechanics — so the language model *looks facts up and computes* instead\nof recalling (and misremembering) them. Every tool returns a finished, verifiable answer or a\nstructured error; there are no baked-in tier lists or opinions, only facts and math.\n\nThe dataset (`data/brotato.json`) is **not committed** — it is derived from copyrighted game\nfiles, so you build it yourself from a local Brotato install (see [Building the dataset](#building-the-dataset)).\nA full build from **Brotato 1.1.15.4** contains **202 weapons, 197 items, 50 characters, and 15\nweapon-class sets**.\n\n## Requirements\n\n- Python **3.11+**\n- [`uv`](https://docs.astral.sh/uv/) for environment/dependency management\n\nInstall dependencies:\n\n```bash\nuv sync\n```\n\n## Quick start\n\nBuild the dataset (needs a local extraction — see [Building the dataset](#building-the-dataset)),\nthen start the server:\n\n```bash\nuv run python build_dataset.py                        # writes data/brotato.json\nuv run python -m brotato_coach.server                 # starts the MCP server over stdio\n```\n\nThe server refuses to start without `data/brotato.json` and tells you to build it first.\n\nRun the tests (the dataset-dependent integration test is skipped when no dataset is built):\n\n```bash\nuv run pytest        # 89 tests (88 passed + 1 skipped without a built dataset)\n```\n\n## Run\n\n```bash\nuvx spudcoach --data /path/to/brotato.json\n```\n\nThe dataset is never distributed — build your own from your Brotato install:\n`uv run python build_dataset.py` (see [docs/extraction-setup.md](docs/extraction-setup.md)).\nGame version auto-detects from the decompiled `recovered/singletons/progress_data.gd`, and\n`generated_at` defaults to the current UTC time — pass `--game-version`/`--generated-at`\nexplicitly to override either. `SPUDCOACH_DATA` works as an env-var alternative to `--data`.\n\n## Use as a Claude Code plugin\n\nThe MCP server is described by [`plugin/.mcp.json`](plugin/.mcp.json):\n\n```json\n{\n  \"mcpServers\": {\n    \"spudcoach\": {\n      \"command\": \"uv\",\n      \"args\": [\"run\", \"python\", \"-m\", \"brotato_coach.server\"],\n      \"cwd\": \"${CLAUDE_PLUGIN_ROOT}\"\n    }\n  }\n}\n```\n\nThe server reads the (locally built) `data/brotato.json` relative to its working directory, so\n**it must run with the repository root as its `cwd`** (the manifest handles this via\n`${CLAUDE_PLUGIN_ROOT}` when bundled as a plugin), and you must\n[build the dataset](#building-the-dataset) first.\n\nTo register it directly in Claude Code without packaging, add the server pointed at your checkout,\ne.g.:\n\n```bash\nclaude mcp add spudcoach -- uv run --directory /path/to/spud-coach python -m brotato_coach.server\n```\n\nOnce connected, just ask in natural language — the model routes your question to the tools below:\n\n- *\"Does Handcuffs fit my Ranger run? I'm at 7 ranged damage, 65 HP.\"*\n- *\"Minigun T4 vs Revolver T4 at 20 ranged damage — which hits harder?\"*\n- *\"Is attack speed ever dead weight? Can I let knockback go negative on a gun build?\"*\n- *\"What does the Ranger's ranged-damage bonus do to a raw stat of 6?\"*\n- *\"Here's my run.json — how's this build doing?\"* (post-mortem a whole save at once)\n\n## Use with Claude Desktop\n\nClaude Desktop can launch the server with [`uvx`](https://docs.astral.sh/uv/) in two forms: fetch\nstraight from this repo (auto-updates on restart, but needs `git` reachable — see the Windows note\nbelow), or point at a **local checkout** (nothing fetched at runtime; the most reliable form on\nWindows). Either way you supply your own locally built `brotato.json` — the dataset is never\ndistributed.\n\n1. Install `uv` on the machine running Claude Desktop (`winget install astral-sh.uv` on Windows,\n   or the [standalone installer](https://docs.astral.sh/uv/getting-started/installation/)).\n2. Open the config file and add the `spud-coach` server:\n   - **Windows:** `%APPDATA%\\Claude\\claude_desktop_config.json`\n   - **macOS:** `~/Library/Application Support/Claude/claude_desktop_config.json`\n\n   Fetch-from-repo form:\n\n   ```json\n   {\n     \"mcpServers\": {\n       \"spud-coach\": {\n         \"command\": \"uvx\",\n         \"args\": [\n           \"--from\", \"git+https://github.com/brendanlefebvre/spud-coach\",\n           \"spudcoach\",\n           \"--data\", \"C:\\\\Users\\\\<you>\\\\path\\\\to\\\\brotato.json\"\n         ]\n       }\n     }\n   }\n   ```\n\n   Point `--data` at your built dataset (`SPUDCOACH_DATA` works as an env-var alternative). On macOS\n   use a POSIX path like `/Users/<you>/brotato.json`.\n3. Fully restart Claude Desktop (quit from the tray, not just close the window).\n\n### Windows: \"Git executable not found\" (or `uvx` not found)\n\nThe `git+https://…` form makes uv shell out to a `git` executable. **Claude Desktop does not pass\nyour shell — or even your System `PATH` — to the MCP subprocess;** it spawns servers with its own\ntrimmed environment. (The `PATH` it prints in the logs is its command-resolution list, *not* what\nthe child process receives.) So a `git` that runs fine in PowerShell, installed in a System-`PATH`\ndirectory, can still come back \"not found\" here — and a bare `\"command\": \"uvx\"` can fail to resolve\nfor the same reason. Two fixes:\n\n- **Point at a local checkout — no runtime git (recommended).** Clone once in a terminal where git\n  works, then use `--from <folder>` instead of `--from git+…`:\n\n  ```powershell\n  git clone https://github.com/brendanlefebvre/spud-coach C:\\Users\\<you>\\src\\spud-coach\n  ```\n  ```json\n  {\n    \"mcpServers\": {\n      \"spud-coach\": {\n        \"command\": \"uvx\",\n        \"args\": [\"--from\", \"C:\\\\Users\\\\<you>\\\\src\\\\spud-coach\", \"spudcoach\",\n                 \"--data\", \"C:\\\\Users\\\\<you>\\\\path\\\\to\\\\brotato.json\"]\n      }\n    }\n  }\n  ```\n\n  Update later with `git pull` in that folder, then restart Desktop.\n\n- **Or force the tools onto the server's `PATH`.** Keep the `git+https` form and add an `env` block\n  that hands the child an explicit `PATH` — git, plus uv's bin and the winget-links dir:\n\n  ```json\n  \"env\": {\n    \"PATH\": \"C:\\\\Program Files\\\\Git\\\\cmd;C:\\\\Users\\\\<you>\\\\.local\\\\bin;C:\\\\Users\\\\<you>\\\\AppData\\\\Local\\\\Microsoft\\\\WinGet\\\\Links;C:\\\\Windows\\\\System32\"\n  }\n  ```\n\n  If `uvx` itself still isn't found, also set `\"command\"` to its absolute path (`Get-Command uvx` to\n  locate it, e.g. `C:\\Users\\<you>\\AppData\\Local\\Microsoft\\WinGet\\Links\\uvx.exe`).\n\n## Available tools\n\nAll tools return a JSON object. Lookups that miss return `{\"error\": \"not_found\", \"did_you_mean\": [...]}`.\n\n22 tools. Arguments marked `?` are optional.\n\n### Start here\n\n| Tool | Arguments | Returns |\n|------|-----------|---------|\n| `read_me` | — | Session-start primer: how Brotato's core loop works, the source-verified stat mechanics, and what every precomputed field assumes. Call once, before anything else |\n\n### Data lookups\n\n| Tool | Arguments | Returns |\n|------|-----------|---------|\n| `get_weapon` | `name`, `tier?` | Weapon record incl. raw stat-aware fields, on-hit `effects`, and weapon-class `sets`; `{matches:[...]}` if `tier` omitted and several tiers match |\n| `get_item` | `name` | Item record: effects, tags, `archetype`, `frozen_stat` |\n| `get_character` | `name` | Character kit: `wanted_tags`, `banned_item_groups`, `flat_bonuses`, `gain_modifiers`, `special_effects`, `class_bonuses`, `starting_weapon_pool` |\n| `get_weapon_class_set` | `class_name` | Weapon-**class** set bonuses (Blade, Gun, Elemental, …), by equipped count |\n| `list_weapons` | `scaling_stat?`, `tier?` | `{weapons:[...]}` filtered summaries |\n| `list_items` | `tag?`, `scaling_stat?`, `archetype?`, `tier?` | `{items:[...]}` filtered summaries |\n| `list_characters` | `scaling_stat?`, `wanted_tag?`, `banned_item_group?`, `special_effect?`, `class_bonus_set?`, `can_start_with?`, `detail?` | `{characters:[...]}` — **full records**, not summaries (see below) |\n| `get_filter_options` | — | Valid filter values in the dataset: item tags, archetypes, scaling stats, tiers, weapon-class names, enemy abilities / attack kinds / zones, and the character filter vocabularies |\n\n### DPS & comparison\n\n| Tool | Arguments | Returns |\n|------|-----------|---------|\n| `weapon_dps` | `name`, `tier`, `stats`, `aoe_enemies_hit?`, `character?`, `weapon_count?`, `engagement_distance?`, `loadout?`, `apply_set_bonuses?` | Realized, stat-aware DPS at your build, split into `base_dps` + `proc_dps`, with a `cadence` breakdown and the `assumptions` baked in |\n| `compare_weapons` | `names_with_tiers`, `stats`, plus the same optional arguments as `weapon_dps` | `{ranking:[...]}` sorted by DPS descending, at one shared stat block |\n| `compare_merge_paths` | `weapon_name`, `path_a`, `path_b`, `stats?` | Winner or `crossover_rd` for two tier-merge paths (lists of tiers) |\n| `stat_gradient` | `weapons`, `stats`, `step?`, `character?`, `aoe_enemies_hit?`, `engagement_distance?` | Which stat to buy next: stats ranked by the DPS gain from `+step` of each. DPS only — survivability is out of scope |\n\n### Build evaluation\n\n| Tool | Arguments | Returns |\n|------|-----------|---------|\n| `evaluate_item_for_build` | `item_name`, `character_name`, `current_stats` | Per-effect verdict — **live / wasted / harmful** — with reasons, plus a summary |\n| `loadout_set_bonuses` | `weapon_names` | Per-class set progress across a whole loadout: equipped count, active bonuses, and next threshold |\n| `explain_stat` | `stat` | Verified stat mechanics: caps, special behavior, neglectable / never-negative flags |\n| `stat_display_value` | `character`, `stat`, `raw_value` | Displayed value after the character's gain modifiers (e.g. Ranger RD 6 → 9) |\n\n### Bestiary\n\n| Tool | Arguments | Returns |\n|------|-----------|---------|\n| `get_enemy` | `name`, `wave?` | Enemy record: base stats, per-wave stat slopes, attack profile, ability tags. Pass `wave` (1-20) to also resolve `effective` stats at that wave |\n| `list_enemies` | `appears_in?`, `ability?`, `attack_kind?` | `{enemies:[...]}` filtered summaries |\n| `wave_composition` | `wave`, `danger?` | Base-game (Crash Zone) composition for a wave: enemy groups, base counts, first-spawn timing, repeats |\n\n### Run analysis & metadata\n\n| Tool | Arguments | Returns |\n|------|-----------|---------|\n| `evaluate_run` | `path?` **or** `run_json?` | One-call post-mortem of a whole Brotato `run.json` save: run context (character, wave, danger), realized stats, weapon-DPS ranking, set progress, and per-item verdicts |\n| `check_dataset_version` | — | `game_version`, `generated_at`, `schema_version` |\n\n**Batch lookups.** `get_weapon`, `get_item` and `get_character` accept `name` as\neither a string or a **list of strings**. A list returns `{\"results\": [...]}` in\nthe order given, each entry a record or its own `not_found` — so detail on\nseveral entities costs one round-trip, not N.\n\n**`list_characters` returns full records**, unlike `list_weapons` / `list_items`,\nwhich return summaries. The whole 50-character corpus is only ~7.7K tokens, so a\nsurvey answers in a single call with no follow-up `get_character`; pass\n`detail=\"summary\"` for `{id, name}` rows instead. Its `scaling_stat` filter is a\n**derived union** — characters have no scaling-stat field — matching a positive\nstat-gain modifier, a positive flat bonus, or a matching `wanted_tag`. Each\nmatched record carries a `scaling_match` object naming which signal fired, e.g.\nEngineer `{\"gain_pct\": 25, \"flat\": 10, \"wanted\": true}` vs Technomage\n`{\"wanted\": true}`, so the derivation is visible rather than asserted.\nCharacters *penalised* in that stat are excluded from the match.\n\n`starting_weapon_pool` is the run-start weapon-selection pool, **not** a fixed\nstarting weapon: most characters offer a dozen or more, Arms Dealer offers only\nthe Pistol, and Beast Master and Bull offer none.\n\n`stats` / `current_stats` are objects keyed by short stat name (e.g. `{\"ranged_damage\": 7, \"max_hp\": 65}`).\n`names_with_tiers` is a list of `[name, tier]` pairs. `path_a` / `path_b` are lists of tier numbers.\nNote the two stat-name forms: `stats` / `current_stats` use the **short** name (`ranged_damage`), while the `stat` argument of `explain_stat` and `stat_display_value` uses the **`stat_`-prefixed** form (`stat_ranged_damage`). `get_filter_options` returns the valid filter values so you don't have to guess (all filters are case-sensitive exact matches).\n\n`evaluate_run` takes exactly one input: pass the save's contents as `run_json` (e.g. an uploaded/pasted `run.json`) **or** its location as `path` (e.g. a file in your Brotato save directory). The save is read-only — it is never modified. Ids the loaded dataset doesn't recognize (e.g. content newer than your build) are listed under `notes` rather than dropped; a malformed save returns `{\"error\": \"bad_run_file\", ...}`.\n\n## Building the dataset\n\nThe dataset is **not committed** — it is built from an extraction of a real game install. The raw\n`extracted/`, the decompiled `recovered/`, the copyrighted `game_files/`, and the derived\n`data/brotato.json` are all gitignored (see [`docs/extraction-setup.md`](docs/extraction-setup.md)\nfor how the extraction is produced). Once `extracted/` is present at the repo root:\n\n```bash\nuv run python build_dataset.py\n```\n\nThis writes `data/brotato.json`. Game version auto-detects from the decompiled\n`recovered/singletons/progress_data.gd` (its `VERSION` constant), and `generated_at` defaults to\nthe current UTC time. Pass `--game-version <ver>` or `--generated-at <iso8601>` explicitly to\noverride either — e.g. if `recovered/` isn't present, or to pin a reproducible value for a test\nor release script. Re-run after each patch to refresh your local copy — it stays gitignored, so\ndon't commit it.\n\n## How it works\n\n```\nextracted/  (gitignored, regenerable)          raw .tres game data\n     │\n     ▼   build_dataset.py   (offline, per patch)\ndata/brotato.json  (gitignored, built locally)  the deterministic core artifact\n     │\n     ▼   loaded at startup\nbrotato_coach.server (FastMCP)                  16 tools over the pure functions\n     │\n     ▼   connected as a plugin\nClaude Code / Desktop / Web                     chat frontend\n```\n\n- `brotato_coach/tres.py` — a minimal Godot `.tres` parser.\n- `brotato_coach/builders/` — turn parsed `.tres` into enriched records (weapons with precomputed DPS\n  lines, items with archetype flags, characters with gain modifiers, sets, and the verified\n  `stat_mechanics` table).\n- `brotato_coach/calc.py` — pure DPS / merge math (no I/O), unit-tested against hand-verified values.\n- `brotato_coach/{query,answers,evaluate}.py` — pure functions that produce finished answers.\n- `brotato_coach/runfile.py` — pure parser that normalizes a Brotato `run.json` save into a build\n  (character, weapons, items, realized stats) for `evaluate_run`; the only I/O is reading the save file.\n- `brotato_coach/server.py` — thin FastMCP wrappers over those functions.\n\nReference material on the game mechanics the coach encodes lives in [`docs/`](docs/)\n(extraction setup, weapon-merge DPS methodology, run post-mortem methodology, stat mechanics).\n\n## Disclaimer\n\nThis is an unofficial, fan-made tool. It is **not affiliated with, endorsed by, or sponsored by\nBlobfish**, the developer of Brotato, or any of its partners. *Brotato* and all related names,\nmarks, and assets are the property of their respective owners.\n\nThis project ships **no game assets and no game data**. The stat dataset it operates on is\ngenerated locally, by you, from a copy of the game you already own (`build_dataset.py` reads an\nextraction of your own install). Nothing derived from the game's copyrighted files is distributed\nin this repository.\n\nThe software is provided \"as is\", without warranty of any kind (see [LICENSE](LICENSE)). Its\nrecommendations are computed from datamined values and may be incomplete or wrong; use your own\njudgment.\n\n## License\n\n[MIT](LICENSE) © 2026 Brendan LeFebvre. This license covers the code and documentation in this\nrepository only — it does not grant any rights to Brotato or its assets.\n",
  "bytes": 16283,
  "sha": "872ad5457be73b9acce13e013938cd56ab146fc6d2ce30dce9f2a5660f479b3f",
  "repo_slug": "brendanlefebvre/spud-coach",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_brendanlefebvre_spudcoach_2a3d7d5c/readme"
}