UNPKG

endfield-mcp

Version:

MCP Server for Arknights: Endfield (TypeScript / stdio + Streamable HTTP)

104 lines (103 loc) • 4.62 kB
/** * GitHub-backed data sync for Endfield-MCP. * * Mirrors the design of PRTS-MCP's `ts/src/data/sync.ts` — the same proven * decision tree, cascade fallback, and atomic-write semantics. The only * Endfield-specific parts are the User-Agent and the absence of the * `upstream-<sha>` tag convention (our mirror uses plain semver tags, so * commitSha === tag verbatim). * * Contract: * - Downloads GitHub Release zip assets only when the release tag changes. * - Skips the upstream API call entirely when cached data is fresher than * CACHE_TTL_SECONDS (avoids burning GitHub anonymous quota on every start). * - Falls back gracefully to cached data when the network is unavailable, * and to "no data" only when neither cache nor network can provide. * - All writes are write-to-tmp-then-rename for crash safety. */ /** Describes an upstream GitHub repository and the files required from it. */ export interface RepoSpec { owner: string; repo: string; branch: string; files: readonly string[]; /** Absolute path to the local directory where files are written. */ localRoot: string; } export type SyncStatus = "updated" | "up_to_date" | "offline_fallback" | "no_data"; export interface SyncResult { spec: RepoSpec; status: SyncStatus; commitSha: string | null; error: string | null; } /** Describes a GitHub Release asset to download as a local zip. */ export interface ReleaseSpec { owner: string; repo: string; /** Asset filename in the release, e.g. "endfield-tables.zip". */ assetName: string; /** Absolute destination path for the downloaded zip. */ localZip: string; /** Optional validator returning missing or invalid zip entries. */ validateZip?: (zipPath: string) => string[]; } /** Describes a GitHub Release zip asset that should be extracted locally. */ export interface ReleaseArchiveSpec { owner: string; repo: string; assetName: string; localZip: string; localRoot: string; requiredFiles: readonly string[]; } /** * Fetch the release tag and asset download URL for the requested asset. * * Lists the repo's releases (newest first) and returns the first Release * that actually contains `spec.assetName`. We deliberately do NOT use the * `/releases/latest` endpoint: that returns the single newest Release for the * whole repo, which only carries *that* release's assets. Once a repo ships * several independent assets across different Releases (tables/story/worldview), * `/releases/latest` makes every non-latest asset look "missing" and breaks * their syncs. Listing + asset-name matching is robust to that layout. * * Return / throw contract (load-bearing — `syncRelease` distinguishes the two): * - Returns `{tag, url}` when a Release carrying the asset is found. * - Returns `null` when the API responded but NO scanned Release has the * asset (genuine absence — caller must NOT retry via blind download). * - THROWS on network/API failure (caller treats as "unreachable" and may * fall back to blind download / cached data). * * This distinction matters because the blind-download fallback in `syncRelease` * constructs a `releases/latest/download/<asset>` URL — which only makes sense * when we genuinely couldn't reach the API, not when the API confirmed the * asset is absent. */ export declare function checkLatestRelease(spec: ReleaseSpec, timeoutMs?: number): Promise<{ tag: string; url: string; } | null>; /** * Download the release asset zip atomically, then write cache metadata. * Uses write-to-tmp-then-rename for crash safety. */ export declare function downloadReleaseAsset(spec: ReleaseSpec, tag: string, assetUrl: string, timeoutMs?: number): Promise<void>; /** * Check latest GitHub Release and download the zip if the tag has changed. * * Decision tree: * 1. Cache fresh AND zip exists → up_to_date (skip API call). * 2. Network failure → offline_fallback (zip exists) / no_data (no zip). * 3. Tag unchanged AND zip exists → up_to_date (refresh fetchedAt). * 4. Tag changed or zip missing → downloadReleaseAsset → updated / fallback. */ export declare function syncRelease(spec: ReleaseSpec): Promise<SyncResult>; /** * Download a GitHub Release zip asset and extract it into localRoot. * * Keeps the data distribution path aligned with PRTS-MCP's pattern * (release zip → local extraction → DirectoryStore reads) while preserving * whatever on-disk layout the Endfield mirror ships. */ export declare function syncReleaseArchive(spec: ReleaseArchiveSpec): Promise<SyncResult>;