eve-fit-mcp
by apettey
README.md
# eve-fit-mcp
An MCP server that wraps [Pyfa](https://github.com/pyfa-org/Pyfa)'s fitting engine so Claude (or any MCP client)
can load EVE Online fits, inspect stats, try what-ifs and run constrained optimisation, iterating until a fit is
as good as it gets.
Pyfa is **not forked**. It's a git submodule at `vendor/pyfa` pinned to upstream `pyfa-org/Pyfa`, and this repo
only wraps it. Every Pyfa import goes through `eve_fit_mcp/pyfa_bridge.py`, so upstream changes can only break
that one file.
## Layout
| Path | What |
|---|---|
| `vendor/pyfa/` | Upstream Pyfa (submodule). `eve.db` is built here from `staticdata/` |
| `eve_fit_mcp/pyfa_bridge.py` | Headless bootstrap of Pyfa, the only place that imports from it |
| `eve_fit_mcp/fitting.py` | `FitHandle`: load EFT, swap modules/charges/drones using Pyfa's own slot and state rules |
| `eve_fit_mcp/stats.py` | Flattens a fit into numeric metrics (DPS, EHP, tank, cap, speed, resources, ...) |
| `eve_fit_mcp/catalog.py` | Item search and candidate generation (meta variants, group, market group, pools) |
| `eve_fit_mcp/pilots.py` | Skill sets: built-in All 0-5, custom levels, ESI snapshots |
| `eve_fit_mcp/esi.py` | EVE SSO login, skill and in-game fitting import ([docs/ESI.md](docs/ESI.md)) |
| `eve_fit_mcp/ships.py` | Hull reference data (slots, attributes, bonuses) |
| `eve_fit_mcp/items.py` | Tagged item index, filtered search, full item attributes |
| `eve_fit_mcp/optimizer.py` | Weighted objective, hard constraints, greedy or exhaustive search |
| `eve_fit_mcp/server.py` | MCP tools |
| `fits/` | Your EFT library (`save_fit` / `load_saved_fit`) |
## Setup
```powershell
git clone --recurse-submodules <this repo>
cd eve-fit-mcp
python -m venv .venv
.venv\Scripts\python -m pip install -e ".[dev]"
.venv\Scripts\python -m pytest # first run builds vendor/pyfa/eve.db (~25s)
```
Register it with Claude Code (user scope = available in every project), pointing at this checkout's venv:
```powershell
claude mcp add --scope user eve-fit -- "<path to eve-fit-mcp>\.venv\Scripts\python.exe" -m eve_fit_mcp.server
```
(On macOS/Linux the interpreter is `.venv/bin/python`. Any MCP client works: the command is just
`python -m eve_fit_mcp.server` over stdio.)
## Skills for Claude Code
Project skills in `.claude/skills/` keep future work true to how eve-fit was built:
| Skill | Use it when |
|---|---|
| `eve-fit-design-fit` | designing, reviewing or optimising a fit (workflow, tool choice, gotchas, delivering `.eft`/`.xml`/EVEMon files) |
| `eve-fit-add-tool` | adding or changing a tool. Founding principles: wrap Pyfa (never fork); Pyfa is the source of truth; one engine thread; additive contracts; evidence-driven; private user data |
| `eve-fit-improve-from-agent-runs` | turning what fitting agents hand-wrote or got wrong into new tools |
| `eve-fit-update-pyfa` | bumping the Pyfa submodule safely |
## Keeping up with upstream Pyfa
```powershell
powershell -File scripts\update-pyfa.ps1
```
This pulls the latest Pyfa `master`, rebuilds `eve.db` if the game data changed, runs the tests, and prints the
commit command. If the tests fail after an update, the fix almost always goes in `pyfa_bridge.py`.
## Tools
- **Fits:** `load_fit`, `get_fit`, `get_stats`, `list_fits`, `clone_fit`, `close_fit`, `compare_fits`, `set_fit_options` (pilot, damage profile, command links)
- **Editing:** `set_module`, `set_charge`, `set_drones`. Each returns the stat changes.
- **Items:** `search_items`, `list_candidates`, `try_candidates` (what-if for every candidate in a slot, without changing the fit), `get_ship_info` (slots, hardpoints, attributes, bonuses; base and with skills)
- **Item database:** `find_items` (filter by name, category, group, slot, size, hardpoint, meta, market path, role tags, `fits_ship`, `charge_for`, attribute ranges; sort by any attribute), `get_item_info` (all attributes, variations, charges, skills; or a fitted module's modified values), `list_item_tags`, `list_item_groups`
- **Pilots:** `list_pilots`, `get_pilot`, `save_pilot` (base level + per-skill overrides), `delete_pilot`
- **Boosts:** `list_command_links`; apply with `command_links` on `load_fit` / `set_fit_options`
- **Ammo and matchups:** `recommend_ammo` (best applied ammo per enemy and range, vs the max-paper choice), `load_best_ammo` (loads it and records the intent in the fit notes / XML description), `matchup_fits` (fit vs fit: applied DPS, EHP vs the other's damage mix, reps, time to kill, win ratio, N vs M; also a `sweep_fit` goal)
- **Enemy doctrines:** `save_doctrine` (pasted EFTs, killmail fits merged per hull, or eve-esi doctrine bundles from zKillboard and battle reports), `analyze_doctrine`, `list_doctrines`, `delete_doctrine`, `merge_fits`, `load_doctrine_ship`, `matchup_vs_doctrine`, `matchup_envelope`
- **Fleet fights:** `fleet_battle` (our fleet vs a doctrine: logi reps on the primary, ECM jam chances, fleet tackle, side-wide command bursts, neut cap-outs, smartbombs popping missiles and drones, focus fire with target switching, kill timeline and the best kill order per range, with insights)
- **Counter fits:** `counter_fit` (search a hull's fit to maximise fleet-battle outcomes against a saved doctrine, with support ships and real pilot skills; before/after battles, changes and the score/DPS/EHP alternatives); goal `fleet:<doctrine>@<km>x<count>` in `sweep_fit`/`optimize_fit`
- **Counter comps:** `recommend_comp` (how many of each of our fits to bring against a doctrine, with pilot skills/counts, role minimums and budget; best comp's battle, runners-up, swap values and reasoning)
- **Integration:** `evaluate_fit` (stateless, versioned contract), `set_pilot_skills` (named pilot from another tool's skill list)
- **ESI (standalone fallback):** `esi_app_info`, `esi_login`, `esi_login_status`, `esi_login_complete`, `esi_refresh_skills`, `esi_list_fittings`, `esi_import_fitting`, `esi_logout`
- **Optimisation:** `optimize_fit`, `list_metrics`
- **Library:** `save_fit`, `list_saved_fits`, `load_saved_fit`
### Optimiser
```json
{
"objective": {"dps_total": 1, "ehp_total": 0.6, "align_time": -0.2},
"constraints": {"cap_stable": 1, "max_speed": {"min": 1800}},
"slots": ["low", "med", "rig"],
"pools": {"low": ["Damage Control II", "Magnetic Field Stabilizer II", "Energized Adaptive Nano Membrane II"]},
"max_meta_level": 5
}
```
- Weights are normalised to the starting fit by default, so `1` vs `0.6` means "a 1% DPS gain is worth more
than a 1% EHP gain". Use a negative weight to minimise a metric.
- CPU, PG, calibration, slots, hardpoints and max-group limits are always enforced. Any fittable result beats any
unfittable one, so the search can start from an over-CPU fit.
- Candidates for each slot come from `mode` (meta `variants` of the current module by default) plus any
`pools`. Pools are how empty slots get filled and how other module types get considered.
- Identical charge-using modules (e.g. a rack of turrets) change together, and ammo is optimised per weapon type.
- `strategy: "greedy"` is coordinate descent (fast, local). `"exhaustive"` tries every combination per rack
within `max_evals`, then polishes the result with greedy.
- The original fit is never modified. The result is loaded as a new `fit_id` you can inspect, tweak and
re-optimise.
## Item database and tags
On first start the server indexes every published module, rig, charge, drone, implant, booster and ship
(about 7,000 items) from Pyfa's data and caches it in `data/item_index.json`. It rebuilds automatically when
the game data changes. Each entry has a slot, size, meta group, fitting cost, market path and **role tags**
derived from CCP's groups, market tree and dogma effects, for example:
| Tag | Meaning |
|---|---|
| `damage`, `turret-damage`, `missile-damage`, `drone-damage` | damage modules and rigs |
| `turret-range`, `turret-tracking`, `application` | weapon application |
| `speed`, `agility`, `propulsion`, `warp-speed` | navigation |
| `tank:armor-buffer`, `tank:shield-active`, `tank:resist`, ... | tank style |
| `tackle`, `point`, `web`, `ewar:damp`, `cap-warfare`, `neut` | combat utility |
| `fitting`, `cpu`, `powergrid`, `cap` | resources |
| `drawback:armor-hp`, `drawback:sig-rad`, ... | rig penalties |
| `ammo:long-range`, `ammo:short-range`, `dmg:mostly-thermal`, ... | ammo profile |
So "low-slot speed modules a Hurricane can fit" is
`find_items(slot="low", tags=["speed"], fits_ship="Hurricane")`, and "720mm ammo by range" is
`find_items(charge_for="720mm Howitzer Artillery II", sort_by="weaponRangeMultiplier")`.
Tags are heuristics. Confirm with `get_item_info` or `try_candidates`.
## Integration
Other servers call eve-fit over stdio. [eve-esi-mcp](https://github.com/apettey/eve-esi-mcp) uses the stateless
`evaluate_fit` tool (contract `eve-fit/evaluate_fit@1`: EFT plus inline skills, implants and boosters in; raw
stats, units and missing skills out). It owns the EVE login. See [docs/INTEGRATION.md](docs/INTEGRATION.md).
## Pilots, skills and boosts
Every fit is calculated for a **pilot**:
- `All 5` (default) to `All 0`: every skill at that level.
- Custom: `save_pilot("Alt", base_level=4, skills={"Drones": 5, "Gallente Cruiser": 3})`.
- Real characters: log in with EVE SSO and the trained skills are imported. See **[docs/ESI.md](docs/ESI.md)**
to register your own EVE application (Client ID, secret, callback `http://localhost:8765/callback`).
With a pilot below All 5, stats report `missing_skills`, and the optimiser only considers modules and ammo the
pilot can use (`usable_only`, on by default).
**Command boosts** use Pyfa's generic links: `command_links=[{"type": "armor_all", "strength": 5, "mindlink": true}]`.
Strength is the booster ship's bonus in %/level (0 to 5), and max skills with T2 bursts are assumed.
`list_command_links` lists the types. Boosts, pilot and damage profile stay with a fit through clones and
optimisation.
## Assumptions and limits
- EHP uses the fit's damage profile (default omni). DPS is raw, not applied against a target.
- Implants and drugs (boosters) in the EFT are respected but not optimised. ESI imports skills, not implants.
- Command boosts are the generic-link model, not a specific booster ship's fit.
- Each evaluation takes about 10 ms, so greedy runs over a few hundred variants finish in seconds.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues