eve-online-mcp
by HammoTime
README.md
# EVE Online MCP library
Shared, runtime-independent TypeScript source for the local `eve-online-mcp`
application and the `eve-online-hosted-mcp` Workers service. This public
repository is consumed as a Git submodule pinned to a reviewed commit, not as an
independently published npm package. Licensed AGPL-3.0-only; extracted from
`HammoTime/eve-online-mcp` at `05ca567`.
## Runtime boundary
- `src/openapi.ts` owns the read-only operation catalog and manually reviewed
safe POST allowlist; `openapi/esi-openapi.json` is the canonical pinned schema.
- `src/esi-client.ts` owns validated ESI requests, response limits, pagination,
freshness metadata and bounded per-client caching. Protected cache keys vary
by a SHA-256 digest of the access token. Public requests never obtain tokens.
- `src/auth.ts` and `src/token-identity.ts` provide token contracts, refresh and
rotation callbacks, scope inspection, and verified EVE SSO identities.
- `src/server.ts` registers transport-independent MCP tools/resources/prompts.
- `src/tool-output-schemas.ts` describes the structured results of the 14 core
tools, including host authorization, partial workflows and source metadata.
- Skill catalogs, dependency graphs, planning, entity resolution, character
context and market snapshots are shared here.
The runtime uses Web APIs and has no Node filesystem, process, HTTP listener,
browser-launch, or Cloudflare binding dependency. File loading, SSO callbacks,
credential persistence, archive extraction, scheduling, and static-data storage
belong to each application. Schema maintenance scripts currently live in the
local application and update this submodule's pinned document.
## Integration
```sh
git submodule add https://github.com/HammoTime/eve-online-mcp-lib.git lib
git submodule update --init --recursive
```
Import the source directly and compile/bundle it with your application:
```ts
import { OperationCatalog } from "./lib/src/openapi.js";
import { EsiClient } from "./lib/src/esi-client.js";
import { createEveServer } from "./lib/src/server.js";
const catalog = new OperationCatalog(pinnedDocument);
const client = new EsiClient(catalog, sessionTokenProvider, {
userAgent: "my-eve-service/1.0 (contact@example.com)",
});
const server = createEveServer(catalog, client, {
identity: { name: "my-eve-service", version: "1.0.0" },
authentication: sessionCharacterAuthentication,
staticData: applicationStaticDataSource,
});
```
Applications supply `@modelcontextprotocol/server`, `@opentelemetry/api`, `jose`, and `zod` using the
compatible ranges in `package.json`. The `.js` imports resolve to `.ts` source
during TypeScript compilation. Build the submodule with the consumer; no npm
workspace, sibling checkout, or separately published artifact is required.
Create a separate `EsiClient`, token provider, authentication adapter and MCP
server for each user session. Never use a global mutable default character or
credential store across users. Authorization handles are bound to their client
instance. The in-memory ESI cache is instance-local and credential-sensitive;
this library does not provide persistent Workers caching or hosted sessions.
Refresh providers must receive an identity-verification callback and a durable
rotation callback in authenticated applications.
`StaticDataSource.initialize()` returns a build-consistent `SkillReader`, freshness
status and an optional idempotent `release()` callback. Every caller, including
status-only/background initialization, must release in `finally`. `SkillCatalog`
is the in-memory implementation retained for hosted adapters, fixtures and replay;
local adapters can provide bounded SQLite lookups without exposing a full catalog.
Name interpretation, prerequisite validation and planning remain shared. The hosted
application supplies its own D1 adapter, full-SDE workflow and refresh policy.
ESI defaults are 128 cache entries, 20,000,000 serialized UTF-8 cache bytes, 64
tracked wire requests, 64 waiters per request and a 30-second wire/body deadline.
Positive finite integer constructor options can change these bounds. Identical GETs
share only after each caller authorizes, with independent cancellation and isolated
returned objects. POSTs never coalesce. Protected continuation metadata is rebuilt
for each caller, preserving explicit acting-character selection. Hosts must supply
working asynchronous context propagation (or an explicit request signal) even when
trace export is disabled. Abandoned authorization work is detached from request
completion so durable refresh persistence can finish without blocking cancellation.
## Tool output contracts
Public combat records are available through `search_zkillmails` and `get_zkillmail`.
See [zKillboard usage and limits](docs/zkillboard.md) for filters, caching,
bounded response slices and historical-evidence caveats. No EVE login is needed.
See [model-facing response budgets](docs/response-budgets.md) for bounded slices,
snapshot-checked continuations, compact plan views and migration examples. Search
defaults to 10 candidates (maximum 25), character lists omit scopes unless requested,
and map PNG previews are opt-in. Full raw data remains retrievable in bounded slices.
All 14 core tools advertise an `outputSchema` in `tools/list`. These are success
contracts for the existing `structuredContent` object, not new result wrappers.
Every schema explicitly has an object root, including the alternative local and
hosted authorization results and the skill planner's target-selection results.
This prevents the SDK's older-protocol projection from adding a `{ result: ... }`
envelope. The optional `render_eve_map` extension retains its separate schema.
The schemas describe character lists, target candidates, dependency graphs,
training plans, operation discovery/invocation metadata, entity matches,
character sections and bounded market aggregates. `call_esi.data` and successful
character-section data accept any JSON value, including arrays, scalar wallet
balances, strings (also used for non-JSON upstream text) and null. Upstream JSON
schemas, rate-limit extensions and host refresh progress are also JSON-valued,
not restricted to a guessed ESI payload shape. Source freshness, nullable page
counts, pagination next-call arguments, warnings and caveats are retained.
Skill tools initialize static data internally and return build/freshness status;
there is no separate initialization tool. Hosts own operator refresh/recovery.
Character context preserves permitted sections when another section lacks scopes.
Character selection persists across sessions sharing the local store or hosted
user and OAuth client; prefer `call_esi.actingCharacterId` for one request.
Static-data status belongs to the host adapter. Its known fields are typed but
optional; additional JSON status fields are allowed. Local cache paths/counts
are not required from hosted adapters, and hosted `checkedAt` can be null.
Local authorization returns the same character-list object as list/select;
hosted authorization returns `status: "authorization_required"`, a browser URL,
the requested character ID and a message, without claiming consent completed.
`isError: true` results keep the existing error body; the SDK skips success-schema
validation for those results. A wholly failed character context is also a tool
error. Partial character contexts, incomplete market snapshots and
`needs_target_selection` plans are **not** tool errors and satisfy their success
contracts. Always inspect section errors, completeness and freshness before
treating a response as evidence. Output schemas do not change access controls,
upstream response validation or diagnostic capture policy.
Core handlers return a compact JSON text fallback, identical to
`JSON.stringify(structuredContent)`. SDK input/output validation failures
remain SDK-generated text errors. Diagnostic and hosted OAuth challenge metadata
remain on the MCP result's `_meta`, outside the output schema.
Contract tests use the actual SDK over legacy in-memory transports and a modern
Streamable HTTP client connected to the SDK's per-request HTTP handler through
an in-process fetch adapter. They validate the advertised JSON Schemas as well
as the Zod contracts, and check malformed outputs and unchanged text/error
delivery across legacy and modern protocol revisions.
They also sweep every pinned read-only operation through search and inspection,
and check that SDK output-validation errors and telemetry do not echo malformed
private result values.
## OpenTelemetry and hosted authorization
The library uses only the OpenTelemetry API. With no SDK it is a no-op; the host
owns the context manager, exporter, sampling and lifecycle. MCP tool/resource/
prompt handlers, ESI calls and network requests, token refresh, static parsing,
entity resolution, market and character summaries, skill graphs and plans emit
spans. Closed field projections record reviewed public inputs, effective limits,
branch decisions, clocks, output counts and stable error codes. Credentials,
private character state, free text and raw exception messages remain excluded.
Operation metrics use fixed names and bounded labels.
Hosts with a process-wide SDK can use its global tracer. Workers can bind a
per-invocation tracer with `withTracer(tracer, operation)` from `src/telemetry.ts`.
That tracer follows the active OpenTelemetry context through async operations,
so service bindings and durable workflow steps can preserve W3C parent context.
Configure a metrics provider in the host to enable the library's metric instruments.
The library never initializes an SDK or exports data itself.
The optional `adapters/telemetry-runtime.ts` supplies a bounded SDK implementation
for hosts. It provides real delta metrics, correlated OTLP logs, and diagnostic
artifact hooks. See [diagnostic capture and offline replay](docs/diagnostics.md)
for the evidence contract, limits, supported replay boundaries and commands.
`createEveServer` accepts `hostedAuthorizationUrl` to add MCP OAuth challenge
metadata to auth errors. A hosted character adapter may return
`status: "authorization_required"` and a browser URL instead of starting a local
browser. Existing local adapters remain compatible. `call_esi.actingCharacterId`
selects credentials for protected operations that lack a character path parameter;
it is validated separately and is never forwarded as an ESI parameter.
## Development
Use `.devcontainer/devcontainer.json`, or the equivalent Docker environment:
```sh
docker build --target development -f .devcontainer/Dockerfile -t eve-online-mcp-lib-dev .
docker run --rm --user node -v "$PWD:/workspace" -w /workspace eve-online-mcp-lib-dev sh -lc "npm ci && npm run validate"
```
Validation includes formatting, strict lint, typechecking, runtime compilation
without Node globals, coverage tests, a browser-target bundle check, and a build.
Publish library commits before updating a consumer's Git submodule pointer.
## Cartography extension
Optional `cartography` services register `render_eve_map` and artifact resource
templates; a `routing` adapter additionally registers `plan_eve_route` and
`plan_eve_travel`. Consumers supply public SDE data, private artifact storage and
an optional PNG adapter. The runtime-independent planner owns exact directed
shortest paths, stop optimization, replay and totals. The renderer accepts its
opaque `routeId`, or a context `boundary` and `pointsOfInterest` without routes.
Nonempty caller-supplied route arrays are rejected. Crowded route maps retry on a larger padded canvas.
Rendering failures preserve the complete route as text, without generating itinerary images. See [architecture and limits](docs/route-planning.md).
The assistant must never compute, merge or replace route or skill plans; tool
failures are reported, not worked around with scripts or model reasoning.
Request `boundary: { kind: "neighborhood", center: "Jita", jumps: 1 }` directly
for a center and all its distinct incoming/outgoing permanent-stargate neighbors
from validated SDE, without an ESI discovery chain. Names/IDs use the existing exact
reference resolver. `jumps` defaults to `1`; other values are rejected. Selection
never expands POIs or a second hop, and the existing 250-system limit fails
with the complete count rather than trimming the neighborhood.
Absent adapters leave existing consumers unchanged. Never expose stored artifacts
across users without an owner-scoped adapter; base geography being public does not
make caller-authored plans public.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues