Foundational: full scope set + read+write direction
CI / lint (pull_request) Successful in 50s
CI / lint (push) Successful in 50s
CI / test (pull_request) Successful in 58s
CI / publish (pull_request) Has been skipped
CI / test (push) Successful in 56s
CI / publish (push) Successful in 51s

Expand config.SCOPES to the complete character + corp scope set (64 read,
9 write), split into READ_SCOPES / WRITE_SCOPES so a caller can opt into a
read-only subset via FAMILIAR_SCOPES. Update the README: familiar is no
longer "read-only by design" — reads are free, writes are opt-in, previewed,
and audited.

Part of #17. Note: changing scopes requires re-running `familiar login` per
character to re-consent.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
LatteandClaude Opus 4.8 committed 2026-06-27 19:04:01 +02:00
1 parent 61837d4783
commit 77e47e75aa
2 files changed
+91 -19

No files matched your search

+7 -7
View File
@@ -1,22 +1,22 @@
# familiar # familiar
> Your familiar across New Eden — a read-only EVE Online MCP server that lets a local AI see your entire account and find anything in EVE. > Your familiar across New Eden — an EVE Online MCP server that lets a local AI see your whole account and the universe, and act on your behalf when you ask.
In EVE, your familiar isn't a cat on the windowsill. It's the companion that rides along through New Eden, sees what you see, remembers what you can't, and fetches anything you ask for. `familiar` is exactly that: an [MCP](https://modelcontextprotocol.io) server that gives a local AI (Claude Code, Claude Desktop, Cowork) a complete, read-only window into your EVE Online account and the entire game universe. In EVE, your familiar isn't a cat on the windowsill. It's the companion that rides along through New Eden, sees what you see, remembers what you can't, and fetches — or does — whatever you ask. `familiar` is exactly that: an [MCP](https://modelcontextprotocol.io) server that gives a local AI (Claude Code, Claude Desktop, Cowork) a complete window into your EVE Online account and the entire game universe, plus hands to act on it.
Ask your familiar where your assets are, what your skill queue looks like, what Tritanium sells for in Jita, the safest route to a lowsec system, or whether a fit is worth building. It looks, it finds, it reports back. Ask your familiar where your assets are, what your skill queue looks like, what Tritanium sells for in Jita, the safest route to a lowsec system, or whether a fit is worth building — and have it set a waypoint or draft a mail when you say so. It looks, it finds, it reports back, and it acts when asked.
## Principles ## Principles
- **Read-only by design.** `familiar` only ever *reads*. No market orders, no fittings changes, no mail sent, no waypoints set. The AI can see everything and break nothing. - **Safe by default.** Reading is free; every *write* — sending mail, setting waypoints, editing contacts/fittings/PI, fleet commands — is off until you turn it on, previewed before it runs, and recorded in an audit log. The AI can see everything and changes nothing unless you let it.
- **Local-first.** Runs on your own machine. Your refresh token and account data never leave your control. - **Local-first.** Runs on your own machine. Your refresh token and account data never leave your control.
- **Everything, eventually.** The goal is full coverage: every read scope ESI exposes, plus the complete static universe. Built in phases so it actually ships. - **Everything, eventually.** The goal is full coverage: the ESI scopes you grant (character *and* corp, read and write), plus the complete static universe. Built in phases so it actually ships.
## How it works ## How it works
Two data sources sit behind the server: Two data sources sit behind the server:
- **ESI** (the [EVE Swagger Interface](https://esi.evetech.net/)) — the official REST API. Public endpoints (market, universe, industry indices) need no login. Character and corp data (wallet, assets, skills, industry, contracts, mail, location) come through EVE SSO with **read-only OAuth2 scopes**. - **ESI** (the [EVE Swagger Interface](https://esi.evetech.net/)) — the official REST API. Public endpoints (market, universe, industry indices) need no login. Character and corp data (wallet, assets, skills, industry, contracts, mail, location, and more) come through EVE SSO with **read and write OAuth2 scopes** across both character and corp; write scopes power the opt-in actions.
- **SDE** (the [Static Data Export](https://developers.eveonline.com/resource/resources)) — the full static dataset of items, blueprints, systems and stations, cached locally for fast lookups and anything name- or recipe-based. - **SDE** (the [Static Data Export](https://developers.eveonline.com/resource/resources)) — the full static dataset of items, blueprints, systems and stations, cached locally for fast lookups and anything name- or recipe-based.
Built in **Python with FastMCP**, OAuth via PKCE with a localhost callback, refresh token stored encrypted on disk. ESI `Expires` headers are respected for caching, and the SDE is loaded into a local store. Built in **Python with FastMCP**, OAuth via PKCE with a localhost callback, refresh token stored encrypted on disk. ESI `Expires` headers are respected for caching, and the SDE is loaded into a local store.
@@ -51,7 +51,7 @@ Built in phases. Each phase is independently useful, so the project stays shippa
## Status ## Status
Phase 0. Just hatched. Phases 0–2 done on `dev`: public market & universe lookups, EVE SSO (PKCE) login with an encrypted token store, and read-only account tools (wallet, skills, location, clones, assets) across multiple characters and accounts. Now expanding to full character + corp coverage with opt-in writes (Phases 3–5). `main` stays untouched until the first complete version is ready.
## License ## License
+84 -12
View File
@@ -21,23 +21,95 @@ CALLBACK_PORT = int(os.environ.get("FAMILIAR_CALLBACK_PORT", "8765"))
CALLBACK_PATH = "/callback" CALLBACK_PATH = "/callback"
REDIRECT_URI = f"http://{CALLBACK_HOST}:{CALLBACK_PORT}{CALLBACK_PATH}" REDIRECT_URI = f"http://{CALLBACK_HOST}:{CALLBACK_PORT}{CALLBACK_PATH}"
# Read-only scopes requested at login. These must be enabled on the EVE app. # Scopes requested at login. Split read vs write so a caller can opt into a
SCOPES = os.environ.get( # read-only subset (set FAMILIAR_SCOPES) while the default grants the full set.
"FAMILIAR_SCOPES", # All of these must also be enabled on the EVE application itself.
" ".join( READ_SCOPES = [
[ "publicData",
"esi-wallet.read_character_wallet.v1", # character: location / state
"esi-assets.read_assets.v1",
"esi-skills.read_skills.v1",
"esi-skills.read_skillqueue.v1",
"esi-location.read_location.v1", "esi-location.read_location.v1",
"esi-location.read_ship_type.v1", "esi-location.read_ship_type.v1",
"esi-location.read_online.v1", "esi-location.read_online.v1",
"esi-clones.read_clones.v1", "esi-clones.read_clones.v1",
"esi-clones.read_implants.v1", "esi-clones.read_implants.v1",
] "esi-characters.read_fatigue.v1",
), # character: skills / wallet / assets
).split() "esi-skills.read_skills.v1",
"esi-skills.read_skillqueue.v1",
"esi-wallet.read_character_wallet.v1",
"esi-assets.read_assets.v1",
# character: industry / market / contracts
"esi-industry.read_character_jobs.v1",
"esi-industry.read_character_mining.v1",
"esi-markets.read_character_orders.v1",
"esi-contracts.read_character_contracts.v1",
"esi-characters.read_blueprints.v1",
# character: social / profile
"esi-characters.read_contacts.v1",
"esi-characters.read_standings.v1",
"esi-characters.read_loyalty.v1",
"esi-characters.read_medals.v1",
"esi-characters.read_titles.v1",
"esi-characters.read_agents_research.v1",
"esi-characters.read_chat_channels.v1",
"esi-characters.read_notifications.v1",
"esi-characters.read_fw_stats.v1",
"esi-characters.read_freelance_jobs.v1",
"esi-characters.read_corporation_roles.v1",
"esi-mail.read_mail.v1",
"esi-calendar.read_calendar_events.v1",
"esi-fittings.read_fittings.v1",
"esi-fleets.read_fleet.v1",
"esi-killmails.read_killmails.v1",
"esi-activities.read_character.v1",
"esi-access.read_lists.v1",
# structures / universe / markets
"esi-search.search_structures.v1",
"esi-universe.read_structures.v1",
"esi-structures.read_character.v1",
"esi-structures.read_corporation.v1",
"esi-markets.structure_markets.v1",
"esi-planets.read_customs_offices.v1",
"esi-alliances.read_contacts.v1",
# corp
"esi-corporations.read_corporation_membership.v1",
"esi-corporations.track_members.v1",
"esi-corporations.read_divisions.v1",
"esi-corporations.read_titles.v1",
"esi-corporations.read_medals.v1",
"esi-corporations.read_standings.v1",
"esi-corporations.read_contacts.v1",
"esi-corporations.read_structures.v1",
"esi-corporations.read_starbases.v1",
"esi-corporations.read_facilities.v1",
"esi-corporations.read_container_logs.v1",
"esi-corporations.read_blueprints.v1",
"esi-corporations.read_fw_stats.v1",
"esi-corporations.read_projects.v1",
"esi-corporations.read_freelance_jobs.v1",
"esi-wallet.read_corporation_wallet.v1",
"esi-wallet.read_corporation_wallets.v1",
"esi-assets.read_corporation_assets.v1",
"esi-industry.read_corporation_jobs.v1",
"esi-industry.read_corporation_mining.v1",
"esi-markets.read_corporation_orders.v1",
"esi-contracts.read_corporation_contracts.v1",
"esi-killmails.read_corporation_killmails.v1",
]
WRITE_SCOPES = [
"esi-mail.organize_mail.v1",
"esi-mail.send_mail.v1",
"esi-calendar.respond_calendar_events.v1",
"esi-characters.write_contacts.v1",
"esi-fittings.write_fittings.v1",
"esi-fleets.write_fleet.v1",
"esi-ui.open_window.v1",
"esi-ui.write_waypoint.v1",
"esi-planets.manage_planets.v1", # read + write
]
SCOPES = os.environ.get("FAMILIAR_SCOPES", " ".join(READ_SCOPES + WRITE_SCOPES)).split()
def data_dir() -> Path: def data_dir() -> Path: