eve-sde-mcp
# eve-sde-mcp
[](https://github.com/ramonvanalteren/eve-sde-mcp/actions/workflows/ci.yml)
MCP server providing access to Eve Online's Static Data Export (SDE) and live character data via the ESI API — ship stats, module attributes, universe data, industry blueprints, character skills, and more.
A companion skill bundle ships in [`skills/eve-trading/`](skills/eve-trading/) as one installable plugin package (also listed in the `eve-sde` marketplace at [.claude-plugin/marketplace.json](.claude-plugin/marketplace.json)), covering three independently-triggered skills: **eve-trading** (hybrid station trading workflows), **eve-fitting** (fitting discipline), **eve-industry** (build-margin verification via price_build, production review, BPO candidate selection). Installing the `eve-trading` plugin gets you all three.
Static data is powered by the [Fuzzwork](https://www.fuzzwork.co.uk/dump/) SQLite conversion of CCP's SDE. Live data uses EVE SSO OAuth with PKCE (no client secret needed).
## Tools
### Static Data (SDE)
| Tool | Description |
|------|-------------|
| `search_types` | Search items by name with category/group filters |
| `get_type` | Full type detail with dogma attributes, effects, and traits |
| `get_type_attributes` | Dogma attributes (CPU, PG, damage, resists, etc.) |
| `get_type_effects` | Effects and slot type (hi/med/low/rig) |
| `compare_types` | Side-by-side attribute comparison for multiple types |
| `get_group` | Inventory group with all types |
| `get_category` | Inventory category with child groups |
| `get_market_group` | Market group tree navigation |
| `search_systems` | Search solar systems by name |
| `get_system` | System details, connected systems, stations |
| `get_region` | Region with constellations |
| `get_structure` | Resolve a station or player-structure id to name, solar system, structure type, and the system's industry cost indices — NPC stations from the SDE, Upwell structures via authenticated ESI (esi-universe.read_structures.v1). Also enriches get_industry_jobs and get_character_assets with resolved facility/location names |
| `get_station` | Station details |
| `get_blueprint` | Blueprint materials, products, skills, time |
| `search_blueprints` | Find blueprints by product name |
| `query_sde` | Raw read-only SQL against the SDE |
| `get_sde_status` | SDE version, download date, table list |
| `refresh_sde` | Download/update the SDE from Fuzzwork |
### Live Character Data (ESI)
| Tool | Description |
|------|-------------|
| `esi_login` | Start EVE SSO OAuth login flow |
| `esi_status` | Show authenticated characters and token status |
| `esi_logout` | Remove stored tokens for a character |
| `esi_switch_character` | Switch active character for queries |
| `get_character_skills` | All trained skills with SDE-enriched names and groups |
| `get_skill_queue` | Current skill training queue |
| `get_character_attributes` | Character attributes (int/mem/per/will/cha) |
| `check_skill_requirements` | Check if character meets skill reqs for a ship/module |
### Market & Trading (ESI)
| Tool | Description |
|------|-------------|
| `get_wallet_balance` | Character ISK balance |
| `get_character_orders` | Open market orders with item names |
| `get_order_history` | Completed/cancelled/expired orders |
| `get_wallet_journal` | ISK income/expense log |
| `get_wallet_transactions` | Recent market buys/sells with item names |
| `get_market_prices` | Global average/adjusted prices (public) |
| `get_region_orders` | Market orders for an item in a region (public) |
| `get_market_history` | Daily price/volume history for an item (public) |
| `get_structure_orders` | Orders in a player-owned structure (authenticated) |
| `get_market_types` | List type IDs with active orders in a region (public) |
### Accounting Ledger (local)
ESI's wallet journal/transactions only cover a rolling ~30 days and order history ~90 days — these tools persist synced data permanently in a local SQLite ledger (`~/.eve-sde/ledger.db`) so realized P&L, FIFO cost basis, daily closes, and relisting-fee correlation all survive past those windows.
| Tool | Description |
|------|-------------|
| `sync_wallet_ledger` | Pull all currently-available wallet journal + transactions + orders into the local ledger |
| `run_daily_close` | Sync, apply FIFO cost-basis matching, and compute a day's realized/unrealized P&L — defaults to the last completed UTC day (00:00–24:00); past dates get historical marks + reconstructed escrow, and every close reconciles NAV change vs. prior close; broker fees split into new-listing vs. relisting. BOM linkage: delivered manufacturing jobs consume material FIFO lots and create product lots at all-in basis, with a production section in the report |
| `get_daily_close_by_position` | Same day-close, broken out per item type_id instead of one portfolio total, incl. both directions of relisting-fee attribution and all-in net P&L per position |
| `get_daily_close` | Read a previously computed close for one date |
| `get_close_range` | Read a range of computed closes, with summed totals |
| `get_open_lots` | List current open FIFO lots (unsold inventory with acquisition cost, all-in cost basis incl. acquisition churn, and the sell-side relisting fees already sunk per position) |
| `get_station_fees` | Station broker-fee models for fee/order matching — config-first (`stationFees` in config.json), per-station derivation fallback, and discovery of unconfigured stations you trade at |
| `get_autoclose_status` | Inspect the autonomous daily-close heartbeat: config, per-character coverage of recent days, and the run log |
**Autonomous daily close.** While the server is running it closes days by itself: it syncs the wallet ledger once its last sync is older than 4 hours, and closes every completed UTC day (including backfilling gaps up to 25 days) once EVE downtime (~11:05 UTC) has published that day's market history — the default cutoff is 11:30 UTC. Everything is condition-based and idempotent, so a sleeping machine or a closed MCP client just means the next heartbeat catches up; nothing is lost as long as gaps stay under ESI's ~30-day windows. Failed attempts never accelerate the next tick, and both closes and syncs are capped per day, so a dead refresh token or an ESI outage backs off to the normal interval instead of hammering the API. Every attempt (success or failure) is logged to the `autoclose_runs` table — `get_autoclose_status` shows coverage and history. Configure or disable via `~/.eve-sde/config.json`:
```json
{
"autoClose": {
"enabled": true,
"minUtcHour": 11.5,
"tickMinutes": 30,
"syncMaxAgeHours": 4,
"lookbackDays": 25,
"maxAttemptsPerDate": 3,
"maxBackfillsPerTick": 5,
"maxSyncAttemptsPerDay": 3
}
}
```
**Station fee models (config-first).** Broker fees differ per station, and the ledger's fee/order matching needs each station's model: `~/.eve-sde/config.json` pins them. Components are **additive** — expected fee = order value × pct/100 + flat (a pure-percentage station sets only `brokerFeePct`; a Perimeter-style structure sets the 0.5% SCC surcharge plus its flat structure fee). Stations not in config derive a percentage from the character's own unambiguous fee/order history; stations with neither use the generic default and are flagged. `salesTaxPct` covers the character-level sales tax used for net-of-tax marks.
```json
{
"clientId": "your_client_id_here",
"salesTaxPct": 3.4,
"stationFees": {
"60003760": { "brokerFeePct": 1.491, "label": "Jita 4-4 CNAP" },
"1044752365771": { "brokerFeePct": 0.5, "brokerFeeFlat": 100, "label": "Perimeter 0.0% Neutral States Market HQ" }
},
"blueprintME": {
"2047": 10,
"1404": 8
}
}
```
`get_station_fees` shows the resolution per station and lists any unconfigured stations you trade at.
`blueprintME` is the FALLBACK for the close's bill-of-materials ME resolution (keyed by blueprint type id). The primary source is the character's synced ESI blueprints — each delivered job's own BPO is matched by item id for its exact ME (`get_character_blueprints`; requires `esi-characters.read_blueprints.v1`, in the default login scope set since this feature — re-auth once if your token predates it). Unlisted and unsynced blueprints default to ME 0 (base quantities, conservative basis). The daily close consumes delivered manufacturing jobs' materials from FIFO buy lots oldest-first and creates product lots at all-in basis (materials + installation); product sells then match real cost basis, and the close report carries a production section.
### Killmails (ESI)
| Tool | Description |
|------|-------------|
| `get_recent_killmails` | Character's recent kills and losses (IDs + hashes) |
| `get_killmail` | Full killmail detail with victim fitting, attackers, SDE names (public) |
### Fittings (ESI)
| Tool | Description |
|------|-------------|
| `get_fittings` | All saved fittings with ship/module names from SDE |
| `save_fitting` | Save a fitting from EFT format or structured input (write) |
| `delete_fitting` | Delete a saved fitting by ID (write) |
| `parse_eft` | Preview EFT parsing without saving — resolves names to IDs and slot flags; tolerates empty-slot markers, offline markers, x-quantities, mutated-module warnings |
| `check_fitting` | Exact fit check: CPU/PG/calibration/slot/hardpoint/drone-bay budgets from the SDE with skill effects applied (CPU/PG Management, Weapon/AWU, Evasive Maneuvering, weapon-rig drawbacks), plus a mass/inertia/align section (ln(4) in-game warp-entry formula, stack-penalized agility modifiers, online prop mass additions — the MWD passive-mass trap; sim-validated within ~2%); unmodeled effects listed (pyfa territory) |
| `killmail_to_eft` | Reconstruct a killmail victim's fit (destroyed + dropped) as EFT — feeds check_fitting / price_fitting / save_fitting |
| `price_fitting` | Sum the cheapest sell orders per fit item at a station (default Jita 4-4) — the real acquisition cost of a fit |
### Industry & Assets (ESI)
| Tool | Description |
|------|-------------|
| `get_industry_jobs` | Active/recent manufacturing, research, invention jobs |
| `get_industry_cost_indices` | System cost indices for industry (public) |
| `price_build` | Price a manufacturing job before committing runs: ME-adjusted blueprint materials at live market prices + installation cost vs the product's net sell — unit build cost and margin on both acquisition bases (materials at sell orders = instant/conservative, at buy orders = patient), book depths, thin-book warnings. Born from a production audit that found a 400-run job committed at +0.9% margin |
| `get_character_blueprints` | The character's blueprints with ME/TE/runs from ESI — also feeds the BOM pass's exact per-BPO ME resolution |
| `scan_builds` | Discover industry candidates: screen every market-obtainable T1 manufacturing BPO in a category (or a specific product list — synergy mode) with ESI bulk adjusted prices, then LIVE-verify the top candidates at a station (order-book margins on both bases, 30-day traded volume, book depths, input cost-share). Screen ranks, verification decides — the closed SDE blueprint universe makes industry discovery self-sufficient, no external tier feeds needed |
| `get_character_assets` | Items in hangars/containers with names |
| `get_character_contracts` | Courier, item exchange, auction contracts |
## Setup
Requires Node.js 22+ (managed with [fnm](https://github.com/Schniz/fnm) — the version is pinned in `.node-version`). Development and the deployed server both run this pinned version.
```bash
git clone https://github.com/ramonvanalteren/eve-sde-mcp.git
cd eve-sde-mcp
fnm use # switch to the pinned Node before installing
npm install
npm run build
```
The SDE database (~460MB) is auto-downloaded to `~/.eve-sde/eve.db` on first run.
## Server install (production)
The MCP server is **installed separately from the development checkout** — it never runs from the repo directly:
```bash
fnm use # deploy refuses to run under the wrong Node
npm run deploy
```
`npm run deploy` builds `dist/`, wipes and repopulates `~/.eve-sde/server/` (dist, a production-only `node_modules` built for the pinned runtime, a `start.sh` launcher with the resolved Node path baked in), verifies the native binding there, and updates the Claude Desktop config (with a timestamped backup; `--no-config` skips). Restart Claude Desktop afterwards. The resulting config entry is:
```json
{
"mcpServers": {
"eve-sde": {
"command": "/Users/you/.eve-sde/server/start.sh"
}
}
}
```
Updates are the same command — it's a clean redeploy. Data (`eve.db`, `ledger.db`, `auth.db`, `config.json`) lives in `~/.eve-sde/` and is shared between the installed server and dev runs, unchanged by deploys.
**Why the separation exists:** the server used to run from this checkout via `bootstrap.mjs`, launched by the MCP client under `/opt/homebrew/bin/node` while development ran under fnm — two runtimes, one shared `node_modules`. The launcher's on-mismatch "npm rebuild" silently flipped the `better-sqlite3` binary between ABIs on every relaunch, racing the dev shell's own rebuilds; a torn binary left macOS killing every process that tried to load it (Code Signature Invalid). Two rules now prevent that class of failure:
1. **The server owns its install.** `~/.eve-sde/server/` is independent of the dev tree — branch switches, `npm install`, and rebuilds in the repo can't affect a running or deployed server, and vice versa.
2. **Launchers never rebuild.** `start.sh` (repo and install) and `bootstrap.mjs` verify the binding and fail loudly with the fix on mismatch. They never mutate `node_modules` — silent self-repair by a launcher running under an unexpected runtime is what corrupted the shared install.
## Claude Desktop / Claude Chat
Use the deployed install (see [Server install](#server-install-production)) — don't point an MCP client at the dev checkout. If you know what you're doing and want a dev-tree launch anyway:
```json
{
"mcpServers": {
"eve-sde": {
"command": "/path/to/eve-sde-mcp/start.sh"
}
}
}
```
Restart Claude Desktop to connect.
## ESI Authentication
To use the live character data tools, you need an EVE SSO application:
1. Register at https://developers.eveonline.com — create an app with "Authentication & API Access", callback URL `http://localhost:8085/callback`
2. Create `~/.eve-sde/config.json`:
```json
{ "clientId": "your_client_id_here" }
```
3. Use the `esi_login` tool — it opens a browser for EVE SSO login and stores encrypted tokens locally
Tokens are encrypted at rest (AES-256-GCM) and stored in `~/.eve-sde/auth.db`. Scopes include skill reading, wallet, market, industry, assets, blueprints, universe structures, contracts, and fittings (read+write). Multi-character support is built in.
## Development
```bash
fnm use
npm run dev # Run with tsx (no build needed)
npm test # Run test suite
npm run test:watch # Watch mode
npm run build # Compile TypeScript
npm run rebuild # Rebuild better-sqlite3 for the current fnm Node
npm run deploy # (Re)install the server to ~/.eve-sde/server
```
`start.sh` and `bootstrap.mjs` (dev-tree launchers) check that the native binding loads under the resolved runtime and exit with instructions on mismatch — they never rebuild automatically. If the binding breaks after a Node switch, run `fnm use` (to the `.node-version` pin) and `npm run rebuild`.
## Versioning
Two things carry a semver version, each bumped **in the same commit/PR that changes it** — not on a schedule, not batched up later:
- **The MCP server** — `package.json` `"version"`. This is the single source of truth: `src/server.ts` reads it at construction (`createServer()`), so the version the client sees (`get_sde_status`, MCP `initialize`) can't drift from `package.json` by forgetting a second edit.
- **The `eve-trading` skill plugin** — `skills/eve-trading/.claude-plugin/plugin.json` `"version"`. It bundles three independently-triggered skills (eve-trading, eve-fitting, eve-industry); a change to any one of them bumps this one version, since they ship together as a single install.
Bump rule for both, by the nature of the change:
- **PATCH** — bug fix, formula/rounding correction, doc/reference wording fix, no behavior or interface change a caller/reader needs to know about.
- **MINOR** — new tool, new skill workflow, new optional parameter, materially expanded guidance — additive, backward compatible.
- **MAJOR** — a tool's parameters/output shape change incompatibly, a tool is removed, or a skill's guidance changes in a way that contradicts what it said before (e.g. a formula correction that flips a number callers already depend on — judgment call between PATCH and MAJOR: PATCH if the old behavior was simply wrong and nothing sane depended on the bug, MAJOR if something plausibly did).
If a PR touches both `src/` and `skills/eve-trading/`, bump both independently against their own history — they are not tied together.
## Data
- **SDE**: `~/.eve-sde/eve.db` — use `refresh_sde` to update
- **Auth tokens**: `~/.eve-sde/auth.db` — encrypted, use `esi_logout` to remove
- **Config**: `~/.eve-sde/config.json` — EVE SSO Client ID
- The `query_sde` tool allows arbitrary SELECT queries for anything the specific tools don't cover
## License
MIT
TDQS
Scored across 17 tools
Most tools have distinct purposes, but there is some overlap between get_type, get_type_attributes, and get_type_effects, as well as between get_blueprint and search_blueprints. However, descriptions clarify the differences, and the overlap is minimal.
Tool names follow a consistent verb_noun pattern (get_, search_, compare_, query_, refresh_), with only minor plural/singular variations that are intuitive. The naming is predictable and clear.
17 tools is slightly above average but appropriate for the large EVE Online SDE domain. Each tool serves a clear purpose, and the count is reasonable for covering types, blueprints, systems, regions, and custom queries.
The tool surface is remarkably complete for an SDE database, covering retrieval of all major entity types (types, blueprints, systems, regions, stations, market groups, categories, groups) plus search, comparison, and raw SQL access for edge cases.