eve-online-mcp 0.1.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -6
- package/dist/character-context.d.ts +17 -0
- package/dist/character-context.js +119 -0
- package/dist/character-context.js.map +1 -0
- package/dist/entity-resolution.d.ts +7 -0
- package/dist/entity-resolution.js +115 -0
- package/dist/entity-resolution.js.map +1 -0
- package/dist/esi-client.d.ts +44 -2
- package/dist/esi-client.js +328 -64
- package/dist/esi-client.js.map +1 -1
- package/dist/market-snapshot.d.ts +9 -0
- package/dist/market-snapshot.js +174 -0
- package/dist/market-snapshot.js.map +1 -0
- package/dist/openapi.d.ts +5 -0
- package/dist/openapi.js +31 -1
- package/dist/openapi.js.map +1 -1
- package/dist/operation-metadata.d.ts +8 -0
- package/dist/operation-metadata.js +114 -0
- package/dist/operation-metadata.js.map +1 -0
- package/dist/operation-search.d.ts +20 -0
- package/dist/operation-search.js +166 -0
- package/dist/operation-search.js.map +1 -0
- package/dist/server.js +127 -28
- package/dist/server.js.map +1 -1
- package/dist/workflow-common.d.ts +2 -0
- package/dist/workflow-common.js +12 -0
- package/dist/workflow-common.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,16 @@ The server is generated at runtime from a pinned copy of CCP's OpenAPI 3.1 docum
|
|
|
6
6
|
|
|
7
7
|
## What the MCP server exposes
|
|
8
8
|
|
|
9
|
-
- `search_esi_operations`
|
|
10
|
-
- `get_esi_operation` returns exact parameters, request-body schema, OAuth scopes, cache hints, and rate-limit metadata.
|
|
11
|
-
- `call_esi` invokes
|
|
12
|
-
- `
|
|
9
|
+
- `search_esi_operations` ranks endpoints using deterministic lexical and curated intent matching, supports hard tag/authentication filters and offsets, and explains every match.
|
|
10
|
+
- `get_esi_operation` returns exact parameters, request-body schema, required caller inputs, defaults, pagination guidance, OAuth scopes, cache hints, safe examples where available, and rate-limit metadata.
|
|
11
|
+
- `call_esi` invokes one page of one catalogued read operation. It rejects undeclared parameters, validates values, fixes the origin to ESI, supplies compatibility headers, and never accepts an Authorization header from a tool call.
|
|
12
|
+
- `resolve_eve_entities` performs one exact-only public batch lookup from names to every matching ID/category, or from IDs to names/categories. Ambiguous and unresolved values remain explicit.
|
|
13
|
+
- `get_character_context` retrieves only the requested `profile`, `location`, `ship`, `skills`, `skillQueue`, and/or `wallet` sections for an explicit character ID, with per-section data, freshness, and errors.
|
|
14
|
+
- `get_market_snapshot` collects bounded pages of public regional orders for one type, optionally filters one exact location, and returns observed aggregates with honest completeness warnings.
|
|
15
|
+
- `eve-esi://catalog` describes pinned API coverage, excluded operation count, and guidance for the generic and focused workflows.
|
|
13
16
|
- `plan_eve_adventure` is a prompt for evidence-based recommendations with costs, preparation, risk, travel, and a concrete first action.
|
|
14
17
|
|
|
15
|
-
ESI cache headers are respected in memory,
|
|
18
|
+
ESI cache headers are respected in memory, protected cache entries are isolated by credential context, and every response reports fetch/serve/expiry timestamps plus defensive page metadata. Errors include stable codes, retryability, Retry-After guidance, and a suggested action. Individual responses and bounded composite workflows use 5 MB safety ceilings. A descriptive User-Agent is sent as [recommended by ESI](https://developers.eveonline.com/docs/services/esi/best-practices/); it is derived from the installed package version and has the form `eve-online-mcp/<version> (adam@hammo.dev; +https://github.com/HammoTime/eve-online-mcp)`.
|
|
16
19
|
|
|
17
20
|
## Development container
|
|
18
21
|
|
|
@@ -154,7 +157,11 @@ Select the `plan_eve_adventure` prompt in your MCP host, or ask something like:
|
|
|
154
157
|
|
|
155
158
|
> Using my current location, skills, wallet, assets, and the nearby market, give me three two-hour exploration plans. Explain risk and startup cost, then recommend the best first step.
|
|
156
159
|
|
|
157
|
-
The model can
|
|
160
|
+
The model can resolve exact names/IDs, request explicit character sections, and use the bounded public market workflow without relying on memorized route names. Generic questions still use search, inspect, and call. `call_esi` always remains one page; when its validated page count is available, another page can be requested with the returned `pagination.nextCall`.
|
|
161
|
+
|
|
162
|
+
Character context is not an atomic snapshot: each requested section reports its own source and freshness, and successful public profile retrieval can coexist with a protected-section authentication failure. Skill and skill-queue results retain ESI's warning that completed queue entries may not appear in the skills endpoint until the next character login.
|
|
163
|
+
|
|
164
|
+
Market snapshots cover the public regional orders endpoint only. `locationId` is an exact local filter over those regional rows, not access to private structure markets. Completeness means all reported pages were accepted within the selected page/byte bounds without detected inconsistency; it does not mean prices are real-time, universally accessible, or executable. Buy-order range and minimum volume still apply, and an observed spread is not guaranteed profit.
|
|
158
165
|
|
|
159
166
|
## Schema monitoring
|
|
160
167
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type EsiClient } from "./esi-client.js";
|
|
2
|
+
import type { OperationCatalog } from "./openapi.js";
|
|
3
|
+
export declare const CHARACTER_SECTIONS: {
|
|
4
|
+
readonly profile: "GetCharactersCharacterId";
|
|
5
|
+
readonly location: "GetCharactersCharacterIdLocation";
|
|
6
|
+
readonly ship: "GetCharactersCharacterIdShip";
|
|
7
|
+
readonly skills: "GetCharactersCharacterIdSkills";
|
|
8
|
+
readonly skillQueue: "GetCharactersCharacterIdSkillqueue";
|
|
9
|
+
readonly wallet: "GetCharactersCharacterIdWallet";
|
|
10
|
+
};
|
|
11
|
+
export type CharacterSection = keyof typeof CHARACTER_SECTIONS;
|
|
12
|
+
export declare function getCharacterContext(client: EsiClient, catalog: OperationCatalog, input: {
|
|
13
|
+
characterId: number;
|
|
14
|
+
sections: CharacterSection[];
|
|
15
|
+
}, options?: {
|
|
16
|
+
maxResultBytes?: number;
|
|
17
|
+
}): Promise<Record<string, unknown>>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { EsiRequestError, publicEsiError, } from "./esi-client.js";
|
|
2
|
+
import { sourceMetadata } from "./workflow-common.js";
|
|
3
|
+
export const CHARACTER_SECTIONS = {
|
|
4
|
+
profile: "GetCharactersCharacterId",
|
|
5
|
+
location: "GetCharactersCharacterIdLocation",
|
|
6
|
+
ship: "GetCharactersCharacterIdShip",
|
|
7
|
+
skills: "GetCharactersCharacterIdSkills",
|
|
8
|
+
skillQueue: "GetCharactersCharacterIdSkillqueue",
|
|
9
|
+
wallet: "GetCharactersCharacterIdWallet",
|
|
10
|
+
};
|
|
11
|
+
const SKILLS_CAVEAT = "Completed skill-queue entries can precede updates to the skills endpoint until the character next logs in; this tool does not infer adjusted skill levels.";
|
|
12
|
+
function responseLimitError(limit) {
|
|
13
|
+
return new EsiRequestError(`Character context section exceeds the ${limit} byte aggregate result limit`, undefined, undefined, { code: "RESPONSE_LIMIT", retryable: false });
|
|
14
|
+
}
|
|
15
|
+
function serializedBytes(value) {
|
|
16
|
+
return Buffer.byteLength(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
export async function getCharacterContext(client, catalog, input, options = {}) {
|
|
19
|
+
const maxResultBytes = options.maxResultBytes ?? 5_000_000;
|
|
20
|
+
const caveats = input.sections.some((section) => section === "skills" || section === "skillQueue")
|
|
21
|
+
? [
|
|
22
|
+
SKILLS_CAVEAT,
|
|
23
|
+
"Each section is fetched separately; successful retrieval does not guarantee identical observation time or freshness.",
|
|
24
|
+
]
|
|
25
|
+
: [
|
|
26
|
+
"Each section is fetched separately; successful retrieval does not guarantee identical observation time or freshness.",
|
|
27
|
+
];
|
|
28
|
+
const selected = input.sections.map((section) => ({
|
|
29
|
+
section,
|
|
30
|
+
operation: catalog.get(CHARACTER_SECTIONS[section]),
|
|
31
|
+
}));
|
|
32
|
+
const requiredScopes = [
|
|
33
|
+
...new Set(selected.flatMap(({ operation }) => operation.requiredScopes)),
|
|
34
|
+
].sort();
|
|
35
|
+
let authorization;
|
|
36
|
+
let authorizationError;
|
|
37
|
+
if (requiredScopes.length > 0) {
|
|
38
|
+
try {
|
|
39
|
+
authorization = await client.authorize(requiredScopes);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
authorizationError = error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
const sections = {};
|
|
46
|
+
let successes = 0;
|
|
47
|
+
for (const { section, operation } of selected) {
|
|
48
|
+
if (operation.requiredScopes.length > 0 && authorizationError) {
|
|
49
|
+
sections[section] = {
|
|
50
|
+
status: "error",
|
|
51
|
+
error: publicEsiError(authorizationError),
|
|
52
|
+
};
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
const response = await client.call({
|
|
57
|
+
operationId: operation.operationId,
|
|
58
|
+
path: { character_id: input.characterId },
|
|
59
|
+
}, operation.requiredScopes.length > 0 ? authorization : undefined);
|
|
60
|
+
const sectionResult = {
|
|
61
|
+
status: "ok",
|
|
62
|
+
data: response.data,
|
|
63
|
+
source: sourceMetadata(response),
|
|
64
|
+
};
|
|
65
|
+
const candidate = {
|
|
66
|
+
characterId: input.characterId,
|
|
67
|
+
requestedSections: input.sections,
|
|
68
|
+
status: "complete",
|
|
69
|
+
sections: { ...sections, [section]: sectionResult },
|
|
70
|
+
atomic: false,
|
|
71
|
+
caveats,
|
|
72
|
+
};
|
|
73
|
+
if (serializedBytes(candidate) > maxResultBytes) {
|
|
74
|
+
sections[section] = {
|
|
75
|
+
status: "error",
|
|
76
|
+
error: publicEsiError(responseLimitError(maxResultBytes)),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
sections[section] = sectionResult;
|
|
81
|
+
successes += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
sections[section] = { status: "error", error: publicEsiError(error) };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const buildResult = () => ({
|
|
89
|
+
characterId: input.characterId,
|
|
90
|
+
requestedSections: input.sections,
|
|
91
|
+
status: successes === selected.length
|
|
92
|
+
? "complete"
|
|
93
|
+
: successes === 0
|
|
94
|
+
? "failed"
|
|
95
|
+
: "partial",
|
|
96
|
+
sections,
|
|
97
|
+
atomic: false,
|
|
98
|
+
caveats,
|
|
99
|
+
});
|
|
100
|
+
let result = buildResult();
|
|
101
|
+
for (const section of [...input.sections].reverse()) {
|
|
102
|
+
if (serializedBytes(result) <= maxResultBytes)
|
|
103
|
+
break;
|
|
104
|
+
const sectionResult = sections[section];
|
|
105
|
+
if (typeof sectionResult === "object" &&
|
|
106
|
+
sectionResult !== null &&
|
|
107
|
+
"status" in sectionResult &&
|
|
108
|
+
sectionResult.status === "ok") {
|
|
109
|
+
sections[section] = {
|
|
110
|
+
status: "error",
|
|
111
|
+
error: publicEsiError(responseLimitError(maxResultBytes)),
|
|
112
|
+
};
|
|
113
|
+
successes -= 1;
|
|
114
|
+
result = buildResult();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result;
|
|
118
|
+
}
|
|
119
|
+
//# sourceMappingURL=character-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"character-context.js","sourceRoot":"","sources":["../src/character-context.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { EsiRequestError } from "./esi-client.js";
|
|
2
|
+
import { sourceMetadata } from "./workflow-common.js";
|
|
3
|
+
const CATEGORY_MAP = {
|
|
4
|
+
agents: "agent",
|
|
5
|
+
agent: "agent",
|
|
6
|
+
alliances: "alliance",
|
|
7
|
+
alliance: "alliance",
|
|
8
|
+
characters: "character",
|
|
9
|
+
character: "character",
|
|
10
|
+
constellations: "constellation",
|
|
11
|
+
constellation: "constellation",
|
|
12
|
+
corporations: "corporation",
|
|
13
|
+
corporation: "corporation",
|
|
14
|
+
factions: "faction",
|
|
15
|
+
faction: "faction",
|
|
16
|
+
inventory_types: "inventory_type",
|
|
17
|
+
inventory_type: "inventory_type",
|
|
18
|
+
regions: "region",
|
|
19
|
+
region: "region",
|
|
20
|
+
stations: "station",
|
|
21
|
+
station: "station",
|
|
22
|
+
systems: "solar_system",
|
|
23
|
+
solar_system: "solar_system",
|
|
24
|
+
};
|
|
25
|
+
function invalidResponse(message) {
|
|
26
|
+
return new EsiRequestError(message, undefined, undefined, {
|
|
27
|
+
code: "INVALID_UPSTREAM_RESPONSE",
|
|
28
|
+
retryable: false,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function isRecord(value) {
|
|
32
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
33
|
+
}
|
|
34
|
+
function candidateFrom(value, upstreamCategory) {
|
|
35
|
+
if (!isRecord(value) ||
|
|
36
|
+
typeof value.id !== "number" ||
|
|
37
|
+
!Number.isSafeInteger(value.id) ||
|
|
38
|
+
value.id <= 0 ||
|
|
39
|
+
typeof value.name !== "string")
|
|
40
|
+
throw invalidResponse("ESI entity resolution returned a malformed candidate");
|
|
41
|
+
return {
|
|
42
|
+
id: value.id,
|
|
43
|
+
name: value.name,
|
|
44
|
+
category: CATEGORY_MAP[upstreamCategory] ?? upstreamCategory,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function statusFor(candidates) {
|
|
48
|
+
if (candidates.length === 0)
|
|
49
|
+
return "unresolved";
|
|
50
|
+
return candidates.length === 1 ? "resolved" : "ambiguous";
|
|
51
|
+
}
|
|
52
|
+
function uniqueNames(names) {
|
|
53
|
+
const seen = new Set();
|
|
54
|
+
return names.filter((name) => {
|
|
55
|
+
const key = name.toLocaleLowerCase("en-US");
|
|
56
|
+
if (seen.has(key))
|
|
57
|
+
return false;
|
|
58
|
+
seen.add(key);
|
|
59
|
+
return true;
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export async function resolveEveEntities(client, input) {
|
|
63
|
+
if ("names" in input) {
|
|
64
|
+
const response = await client.call({
|
|
65
|
+
operationId: "PostUniverseIds",
|
|
66
|
+
body: uniqueNames(input.names),
|
|
67
|
+
});
|
|
68
|
+
if (!isRecord(response.data))
|
|
69
|
+
throw invalidResponse("ESI name resolution did not return an object");
|
|
70
|
+
const candidates = [];
|
|
71
|
+
for (const [category, values] of Object.entries(response.data)) {
|
|
72
|
+
if (!Array.isArray(values))
|
|
73
|
+
throw invalidResponse(`ESI name resolution category ${category} was not an array`);
|
|
74
|
+
candidates.push(...values.map((value) => candidateFrom(value, category)));
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
matchMode: "exact",
|
|
78
|
+
results: input.names.map((name) => {
|
|
79
|
+
const matches = candidates.filter((candidate) => candidate.name.toLocaleLowerCase("en-US") ===
|
|
80
|
+
name.toLocaleLowerCase("en-US"));
|
|
81
|
+
return {
|
|
82
|
+
input: name,
|
|
83
|
+
status: statusFor(matches),
|
|
84
|
+
candidates: matches,
|
|
85
|
+
};
|
|
86
|
+
}),
|
|
87
|
+
source: sourceMetadata(response),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const response = await client.call({
|
|
91
|
+
operationId: "PostUniverseNames",
|
|
92
|
+
body: [...new Set(input.ids)],
|
|
93
|
+
});
|
|
94
|
+
if (!Array.isArray(response.data))
|
|
95
|
+
throw invalidResponse("ESI ID resolution did not return an array");
|
|
96
|
+
const candidates = response.data.map((value) => {
|
|
97
|
+
if (!isRecord(value) || typeof value.category !== "string")
|
|
98
|
+
throw invalidResponse("ESI ID resolution returned a malformed candidate");
|
|
99
|
+
return candidateFrom(value, value.category);
|
|
100
|
+
});
|
|
101
|
+
return {
|
|
102
|
+
matchMode: "exact",
|
|
103
|
+
results: input.ids.map((id) => {
|
|
104
|
+
const matches = candidates.filter((candidate) => candidate.id === id);
|
|
105
|
+
return {
|
|
106
|
+
input: id,
|
|
107
|
+
status: statusFor(matches),
|
|
108
|
+
candidates: matches,
|
|
109
|
+
};
|
|
110
|
+
}),
|
|
111
|
+
source: sourceMetadata(response),
|
|
112
|
+
caveat: "Depending on upstream behavior, one invalid ID can cause ESI to reject the entire batch.",
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=entity-resolution.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entity-resolution.js","sourceRoot":"","sources":["../src/entity-resolution.ts"],"names":[],"mappings":"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"}
|
package/dist/esi-client.d.ts
CHANGED
|
@@ -15,25 +15,67 @@ export interface EsiResponse {
|
|
|
15
15
|
cached: boolean;
|
|
16
16
|
headers: Record<string, string>;
|
|
17
17
|
data: JsonValue | string | null;
|
|
18
|
+
freshness: {
|
|
19
|
+
fetchedAt: string;
|
|
20
|
+
servedAt: string;
|
|
21
|
+
expiresAt: string | null;
|
|
22
|
+
sourceLastModified: string | null;
|
|
23
|
+
};
|
|
24
|
+
pagination: {
|
|
25
|
+
mode: "page" | "none";
|
|
26
|
+
currentPage: number | null;
|
|
27
|
+
totalPages: number | null;
|
|
28
|
+
hasMore: boolean | null;
|
|
29
|
+
nextCall: EsiCallInput | null;
|
|
30
|
+
};
|
|
18
31
|
}
|
|
32
|
+
export type EsiErrorCode = "UNKNOWN_OPERATION" | "VALIDATION_ERROR" | "AUTHENTICATION_REQUIRED" | "AUTHENTICATION_FAILED" | "MISSING_SCOPES" | "FORBIDDEN" | "NOT_FOUND" | "THROTTLED" | "NETWORK_ERROR" | "UPSTREAM_ERROR" | "RESPONSE_LIMIT" | "INVALID_UPSTREAM_RESPONSE";
|
|
19
33
|
export declare class EsiRequestError extends Error {
|
|
20
34
|
readonly status?: number | undefined;
|
|
21
35
|
readonly details?: (JsonValue | string) | undefined;
|
|
22
|
-
|
|
36
|
+
readonly metadata: {
|
|
37
|
+
code?: EsiErrorCode;
|
|
38
|
+
retryable?: boolean;
|
|
39
|
+
retryAfterSeconds?: number | null;
|
|
40
|
+
suggestedAction?: string | null;
|
|
41
|
+
};
|
|
42
|
+
constructor(message: string, status?: number | undefined, details?: (JsonValue | string) | undefined, metadata?: {
|
|
43
|
+
code?: EsiErrorCode;
|
|
44
|
+
retryable?: boolean;
|
|
45
|
+
retryAfterSeconds?: number | null;
|
|
46
|
+
suggestedAction?: string | null;
|
|
47
|
+
});
|
|
48
|
+
get code(): EsiErrorCode;
|
|
49
|
+
get retryable(): boolean;
|
|
50
|
+
get retryAfterSeconds(): number | null;
|
|
51
|
+
get suggestedAction(): string | null;
|
|
52
|
+
}
|
|
53
|
+
export interface EsiAuthorization {
|
|
54
|
+
readonly authorizationContext: "esi";
|
|
23
55
|
}
|
|
56
|
+
export declare function publicEsiError(error: unknown): Record<string, unknown>;
|
|
24
57
|
export declare class EsiClient {
|
|
25
58
|
private readonly catalog;
|
|
26
59
|
private readonly tokenProvider;
|
|
27
60
|
private readonly options;
|
|
28
61
|
private readonly cache;
|
|
62
|
+
private readonly authorizationTokens;
|
|
63
|
+
private readonly responseSizes;
|
|
29
64
|
private readonly baseUrl;
|
|
30
65
|
constructor(catalog: OperationCatalog, tokenProvider: TokenProvider, options?: {
|
|
31
66
|
fetchImplementation?: typeof fetch;
|
|
32
67
|
userAgent?: string;
|
|
33
68
|
maxResponseBytes?: number;
|
|
34
69
|
baseUrl?: string;
|
|
70
|
+
clock?: () => Date;
|
|
35
71
|
});
|
|
36
|
-
|
|
72
|
+
authorize(requiredScopes: string[]): Promise<EsiAuthorization>;
|
|
73
|
+
responseByteLength(response: EsiResponse): number;
|
|
74
|
+
call(input: EsiCallInput, authorization?: EsiAuthorization): Promise<EsiResponse>;
|
|
75
|
+
private now;
|
|
76
|
+
private requireToken;
|
|
77
|
+
private tokenFor;
|
|
78
|
+
private paginationFor;
|
|
37
79
|
private buildUrl;
|
|
38
80
|
private buildHeaders;
|
|
39
81
|
private rejectUnknown;
|