cito-mcp 0.1.0 → 0.2.2

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 CHANGED
@@ -1,30 +1,73 @@
1
1
  # cito-mcp
2
2
 
3
- Standalone [MCP](https://modelcontextprotocol.io) server for the [Cito esports API](https://api.citoapi.com) (LoL, Dota 2, CS2, COD, Fortnite, UFC, and more).
3
+ Standalone [MCP](https://modelcontextprotocol.io) server for the [Cito esports API](https://api.citoapi.com) **curated outcome tools** for agents building esports apps, dashboards, bots, and research flows.
4
4
 
5
- **Zero per-endpoint code:** on boot the server fetches the live OpenAPI specs from the API and generates one MCP tool per operation (105 tools today from the global + LoL specs). When the API ships a new endpoint, it appears here automatically on the next spec refresh — restart to pick it up immediately.
5
+ **Version:** `0.2.2` · **Node:** `>=20` · **Install:** `npx cito-mcp`
6
6
 
7
- ## Requirements
7
+ Primary games: **lol · cs2 · dota2 · cod · ufc**. Fortnite and long-tail REST stay available via `call_api`.
8
8
 
9
- - Node.js >= 20
10
- - A Cito API key (`CITO_API_KEY`)
9
+ ---
11
10
 
12
- ## Quickstart (Claude Code)
11
+ ## Philosophy: outcomes, not endpoints
12
+
13
+ v0.1 exposed ~100+ tools auto-generated from OpenAPI. Agents had to pick among thin path wrappers, invent IDs, and stitch multi-call UI screens themselves. That catalog was hard to select against and brittle across games.
14
+
15
+ **v0.2 ships 15 hand-authored tools** that answer *jobs* instead of mirroring REST:
16
+
17
+ | Job | Tool |
18
+ | --- | --- |
19
+ | What’s live right now? | `live_matches` |
20
+ | What’s coming up? | `upcoming_schedule` |
21
+ | Build a match card / recap | `match_summary` |
22
+ | Deep timeline / live state | `match_details` |
23
+ | Team page | `team_profile` |
24
+ | Player / fighter form | `player_profile` |
25
+ | Table / rankings | `standings` |
26
+ | Pre-match briefing | `match_preview` |
27
+ | Event / fight-night card | `event_card` |
28
+ | Rivalry record | `head_to_head` |
29
+ | Name → ID | `resolve_entity` / `search_entities` |
30
+
31
+ Composites multi-fetch server-side, return a **stable JSON envelope**, and isolate partial failures in `partial[]` so one bad secondary section does not fail the whole page.
32
+
33
+ **MCP is design-time / agent assistance.** Ship production backends with Cito REST + the user’s API key. Do not put multi-tenant traffic through this process.
34
+
35
+ ---
36
+
37
+ ## Install
38
+
39
+ ### One-liner (published package)
40
+
41
+ ```bash
42
+ npx cito-mcp
43
+ ```
44
+
45
+ Requires `CITO_API_KEY` in the environment (see [Environment](#environment)). Most hosts inject env via their MCP config rather than a bare shell.
46
+
47
+ ### Claude Code
48
+
49
+ ```bash
50
+ claude mcp add cito -e CITO_API_KEY=cito_your_key_here -- npx cito-mcp
51
+ ```
52
+
53
+ Windows — if `npx` fails under Claude:
13
54
 
14
55
  ```bash
15
- claude mcp add cito -- env CITO_API_KEY=cito_your_key_here npx cito-mcp
56
+ claude mcp add cito -e CITO_API_KEY=cito_your_key_here -- cmd /c npx cito-mcp
16
57
  ```
17
58
 
18
- Or run it straight from a clone of this repo (no publish needed):
59
+ From a local clone (development):
19
60
 
20
61
  ```bash
21
- cd mcp && npm install && npm run build
22
- claude mcp add cito -- env CITO_API_KEY=cito_your_key_here node "$PWD/dist/index.js"
62
+ cd mcp
63
+ npm install
64
+ npm run build
65
+ claude mcp add cito -e CITO_API_KEY=cito_your_key_here -- node "%CD%\dist\index.js"
23
66
  ```
24
67
 
25
- ## Cursor / Windsurf
68
+ ### Cursor
26
69
 
27
- Add to your MCP config JSON (e.g. `~/.cursor/mcp.json`):
70
+ Add to Cursor MCP settings (e.g. `.cursor/mcp.json` or Cursor Settings → MCP):
28
71
 
29
72
  ```json
30
73
  {
@@ -40,61 +83,370 @@ Add to your MCP config JSON (e.g. `~/.cursor/mcp.json`):
40
83
  }
41
84
  ```
42
85
 
43
- The server exits immediately with a clear stderr message if `CITO_API_KEY` is missing. The key is sent only as the `x-api-key` header to the API and is never logged.
86
+ Windows local clone:
87
+
88
+ ```json
89
+ {
90
+ "mcpServers": {
91
+ "cito": {
92
+ "command": "node",
93
+ "args": ["C:/Users/Owner/Projects/cito-api-scraper-windows/mcp/dist/index.js"],
94
+ "env": {
95
+ "CITO_API_KEY": "cito_your_key_here"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ### Windsurf / other MCP hosts
103
+
104
+ Same shape as Cursor: `command` + `args` + `env.CITO_API_KEY`. Prefer `npx cito-mcp` so clients pick up published patches without a local install.
105
+
106
+ The server **exits immediately** if `CITO_API_KEY` is missing. The key is sent only as the `x-api-key` header and is never logged.
107
+
108
+ ---
109
+
110
+ ## Environment
111
+
112
+ | Variable | Required | Default | Description |
113
+ | --- | --- | --- | --- |
114
+ | `CITO_API_KEY` | **yes** | — | Cito API key → `x-api-key` on every upstream call |
115
+ | `CITO_API_BASE` | no | `https://api.citoapi.com/api/v1` | Override base URL (staging / self-hosted) |
116
+
117
+ ---
118
+
119
+ ## Preferred tool order
120
+
121
+ 1. Unsure → `list_capabilities` (optional `api_health` for tier / included games)
122
+ 2. Name without ID → `resolve_entity` (chain) or `search_entities` (browse)
123
+ 3. Live / upcoming → `live_matches` / `upcoming_schedule`
124
+ 4. Match card → `match_summary` (then `match_details` if needed)
125
+ 5. Team / player pages → `team_profile` / `player_profile`
126
+ 6. Tables / rivalry / preview / event card → `standings` / `head_to_head` / `match_preview` / `event_card`
127
+ 7. Escape hatch → `call_api` (allowlisted paths only)
128
+
129
+ **Never invent IDs** — resolve them or take them from live / schedule / search results.
130
+
131
+ Mnemonic: **resolve → live/schedule → summary → deep**.
132
+
133
+ ---
134
+
135
+ ## Tool catalog (15)
136
+
137
+ All tools are **read-only**. Names are `snake_case` with **no** `cito_` prefix (the server name already brands the surface).
138
+
139
+ | Tool | Outcome | When to use | When not to use | Key inputs |
140
+ | --- | --- | --- | --- | --- |
141
+ | `list_capabilities` | Catalog of tools, games, jobs, recipes (no network) | Session start; map UI screens to tools; filter by `game` / `job` | You already know the tool and have IDs | `game?`, `job?`, `q?`, `includeExamples?`, `includeRecipes?` |
142
+ | `api_health` | Reachability, key validity, tier, rate limits, optional game probes | Once per session; after 401/403/RATE_LIMIT; entitlement checks | Fetching live scores or entity data | `includeGameProbes?` |
143
+ | `resolve_entity` | Name → best typed id/slug (+ ranked candidates) | User named “T1”, “s1mple”, “IEM Cologne” without an ID; chaining into profiles | Already have a stable id/slug; large browse lists | `q`, `game?`, `type?`, `limit?` |
144
+ | `search_entities` | Paginated entity browse/search for one game | Typeahead / pickers; “list teams matching…” | Single-name resolve for chaining; full profile pages | `game`, `q?`, `type?`, `limit?`, `cursor?`, `activeOnly?` |
145
+ | `live_matches` | Live board (one game or all); CS2 uses `/cs2/live`; includes UFC | “What’s live?”; ops/dashboard strip | Upcoming fixtures; historical recaps | `game?` (`all` or omit), `limitPerGame?`, `includeLabelsOnly?` |
146
+ | `upcoming_schedule` | Upcoming fixtures/events with filters | “What’s on this week?”; team next matches | Live-only boards; deep match recaps | `game`, `hours?`, `from?`, `to?`, `team?`, `league?`, `tournamentId?`, `limit?`, `cursor?` |
147
+ | `match_summary` | Composite match card: score, performances, media stubs | Default match UI / recap after a known `matchId` | No matchId yet; pure pre-match → `match_preview`; deep timelines | `game`, `matchId`, `view?`, `includePlayerStats?`, `includeMedia?` |
148
+ | `match_details` | Deep sections: timeline, live state, maps, advanced | Analyst dive; live in-game window; full demo list | First-pass live board; simple chat recaps | `game`, `matchId`, `gameId?`, `sections?`, `includeTimeline?`, `includeLiveState?`, `includeAdvanced?` |
149
+ | `player_profile` | Player/fighter + form/trends/radar when available | “How is X playing?”; player page scaffold | Full roster → `team_profile`; unresolved name | `game`, `playerId` **or** `slug`, `recentLimit?`, `includeTrends?`, `view?`, `window?`, `role?` |
150
+ | `team_profile` | Team/org + roster + recent form | Team page / “who is on this roster?” | UFC fighters → `player_profile`; unresolved name; pure tables | `game`, `teamId` **or** `slug`, `recentLimit?`, `includeFormer?`, `view?` |
151
+ | `head_to_head` | Composed H2H (no first-class REST H2H) | Rivalry / series record; preview context | Single-side form only; live scores; standings | `game`, `sideA`, `sideB`, `entityType?`, `limit?`, `from?`, `to?` |
152
+ | `standings` | League/event tables or world/division rankings | Playoff picture; UFC rankings; CDL / CS2 tables | Team form; live scores; match recaps | `game`, `scope?`, `leagueId?`, `tournamentId?`, `eventId?`, `season?`, `stage?`, `division?`, `limit?` |
153
+ | `match_preview` | Pre-match briefing: sides, rosters/form, H2H stub | Upcoming deep link; pick’ems; preview cards | Completed recaps; deep live state | `game`, `matchId` **or** (`teamA` + `teamB`), `eventId?`, `includeH2H?`, `includeRosters?`, `recentLimit?` |
154
+ | `event_card` | Event / fight-night card: identity, bout/match list, optional standings | UFC card, CS2 event hub, tournament overview | Live-only strip; single match recap | `game`, `eventIdOrSlug` **or** `q`, `includeBouts?`, `includeStandings?`, `limit?` |
155
+ | `call_api` | Allowlisted raw REST (`data.raw`) | Fortnite / long-tail paths; payload debugging | Any job covered by a curated tool | `path`, `method?`, `queryJson?`, `bodyJson?` |
156
+
157
+ ### Games
158
+
159
+ | Game | Depth | Notes |
160
+ | --- | --- | --- |
161
+ | `lol` | deep | Team slugs; `lol-match-*`; no native global search |
162
+ | `cs2` | deep | Prefixed ids; live path **`GET /cs2/live`**; trends / rankings |
163
+ | `dota2` | thin | Numeric ids; radar; weak/no standings & roster endpoints |
164
+ | `cod` | medium | Org slugs; CDL standings; UUID match ids |
165
+ | `ufc` | medium | Fighter slugs; `boutId`; live + rankings |
166
+
167
+ Fortnite: **`call_api` only** until promoted into the primary enum.
168
+
169
+ ### `call_api` allowlist
170
+
171
+ Path must start with `/` and match one of:
172
+
173
+ `/health` · `/lol` · `/cs2` · `/dota2` · `/cod` · `/ufc` · `/fortnite`
174
+
175
+ Absolute URLs and `..` traversal → `PATH_NOT_ALLOWED`. Prefer curated tools for all standard jobs.
176
+
177
+ ---
178
+
179
+ ## Response envelope
180
+
181
+ Every tool returns one JSON object:
182
+
183
+ ```json
184
+ {
185
+ "ok": true,
186
+ "data": {},
187
+ "meta": {
188
+ "game": "cs2",
189
+ "source": "match_summary",
190
+ "fetchedAt": "2026-07-27T12:00:00.000Z",
191
+ "requestId": "mcp_…",
192
+ "tookMs": 120,
193
+ "upstreamCalls": 3,
194
+ "rateLimit": { "tier": "pro", "remaining": 54 }
195
+ },
196
+ "pagination": { "limit": 20, "hasMore": false, "nextCursor": null },
197
+ "partial": [],
198
+ "error": null
199
+ }
200
+ ```
201
+
202
+ | Condition | How to handle |
203
+ | --- | --- |
204
+ | `ok: false` | Tool-level failure; MCP `isError: true`; read `error.code` + `error.recover[]` |
205
+ | `ok: true` with `partial[]` | Use successful sections; **do not** treat as total failure |
206
+ | List tools | `data.items` + `pagination` (`limit`, `hasMore`, `nextCursor`, …) |
207
+
208
+ **Error codes:** `VALIDATION` · `NOT_FOUND` · `UNSUPPORTED_GAME` · `UNAUTHORIZED` · `RATE_LIMIT` · `UPSTREAM` · `PATH_NOT_ALLOWED` · `NOT_IMPLEMENTED`
209
+
210
+ **Ambiguity:** `resolve_entity` returns `ok: true` with `data.needsDisambiguation` + `data.candidates` — pick a candidate; do not wait for a hard error.
211
+
212
+ Retry only when `error.retryable` is true (typically `RATE_LIMIT`, `UPSTREAM`).
213
+
214
+ **Pagination:** default limit 20, max 50 (some tools clamp differently). Honor `pagination.hasMore` and pass `pagination.nextCursor` only — never invent cursors or reuse them after changing filters.
215
+
216
+ ---
217
+
218
+ ## Builder recipes
219
+
220
+ Use these as agent playbooks when scaffolding apps. Prefer one composite per screen over N+1 thin GETs.
221
+
222
+ ### 1. Live scoreboard
223
+
224
+ **Goal:** multi-game (or single-game) live strip with optional match drill-down.
225
+
226
+ ```
227
+ 1. api_health # optional — once per session
228
+ 2. live_matches { game: "all" } # or "cs2" / "lol" / …
229
+ 3. For a selected row:
230
+ match_summary { game, matchId } # default card
231
+ match_details { …, includeLiveState: true } # only if needed
232
+ ```
233
+
234
+ **UI map**
235
+
236
+ | Screen region | Source |
237
+ | --- | --- |
238
+ | Live rows (label, score, status) | `live_matches` → `data.sections[].items` |
239
+ | Expanded card | `match_summary` → `data.scoreline`, `playerPerformances`, `media` |
240
+ | In-game window | `match_details` sections `liveState` / `timeline` |
241
+
242
+ **Do not:** N+1 `match_details` for every live row; invent matchIds; use `upcoming_schedule` for live-only boards.
243
+
244
+ ### 2. Player profile page
245
+
246
+ **Goal:** identity, team, recent form for a named player or fighter.
247
+
248
+ ```
249
+ 1. resolve_entity { q: "s1mple", game: "cs2", type: "player" }
250
+ # or type: "fighter" for UFC
251
+ 2. player_profile {
252
+ game,
253
+ playerId | slug, # from resolve best/candidates
254
+ recentLimit: 10,
255
+ includeTrends: true,
256
+ view: "summary"
257
+ }
258
+ 3. Optional: head_to_head for rivalry context (fighters/teams)
259
+ ```
260
+
261
+ **UI map**
262
+
263
+ | Screen region | Source |
264
+ | --- | --- |
265
+ | Header / identity | `data.player` |
266
+ | Current team | `data.currentTeam` |
267
+ | Recent form list | `data.recentMatches` + `data.form` |
268
+ | Career / radar | `data.career`, `data.radar`, `data.seasonStats` (when present) |
269
+
270
+ **Do not:** invent playerIds; use `team_profile` for full rosters; skip resolve when the user only gave a display name.
44
271
 
45
- ## How tool updates work (multi-spec)
272
+ ### 3. Event hub
46
273
 
47
- The server merges **multiple OpenAPI specs** at boot the global spec does not cover every game:
274
+ **Goal:** schedule strip + standings/rankings + match previews for a league, tournament, or event.
48
275
 
49
- - **Default sources**: the global spec (`$CITO_API_BASE/openapi.json`) + the LoL spec (`$CITO_API_BASE/lol/openapi.json`). Today: 71 + 34 = 105 generated tools.
50
- - **Opportunistic per-game specs**: at boot the server also probes `$CITO_API_BASE/{cod,fortnite,dota2,cs2,ufc}/openapi.json` **with your `x-api-key`** and merges any that return 200. 401/404s are skipped quietly (one stderr line each).
51
- - **Add or replace sources via env**:
52
- - `CITO_OPENAPI_URLS` comma-separated list, replaces the defaults entirely (e.g. `CITO_OPENAPI_URLS=https://api.citoapi.com/api/v1/openapi.json,https://api.citoapi.com/api/v1/cod/openapi.json`).
53
- - `CITO_OPENAPI_URL` (singular) single-spec override (legacy, still works).
54
- - **Per-spec base URLs**: each spec's `servers[0].url` is used as the base for its tools (the global spec's paths are relative to `/api/v1`; the LoL spec's paths already include `/api/v1/lol` and its servers entry is the bare origin — so `servers[0].url` + verbatim paths is correct for both). Escape hatch: `CITO_SPEC_BASE_<KEY>` (e.g. `CITO_SPEC_BASE_LOL=https://staging.example.com/api/v1`).
55
- - **Collisions**: merge is path/operation concatenation. On an `operationId` collision across specs, the later spec's tool is prefixed with its game key (`cito_lol_…`) and logged to stderr.
56
- - **No operationId?** Fallback names are derived from method + path (`get /api/v1/lol/live/{gameId}/stats` → `cito_getlollivebygameidstats`). The real LoL spec currently ships no operationIds, so all its tools use fallback names.
57
- - The last good specs are cached to `mcp/.spec-cache.json` (one merged file, per-source entries) — a failed fetch falls back to the cached copy per source.
58
- - Specs are refreshed every `CITO_SPEC_REFRESH_MINUTES` (default 60); tools are regenerated in place and clients are notified via `tools/list_changed`. **Restart the server to pick up brand-new endpoints immediately.**
59
- - Tool names: `cito_` + sanitized name. Descriptions preserve the spec's summaries and per-parameter docs.
60
- - All logs go to **stderr only** (stdout is the MCP channel).
276
+ ```
277
+ 1. list_capabilities { job: "schedule" } # optional orientation
278
+ 2. upcoming_schedule {
279
+ game: "cs2", # or lol / cod / ufc / dota2
280
+ hours: 168, # or from/to ISO window
281
+ league | tournamentId | team # scope filters as available
282
+ }
283
+ 3. standings {
284
+ game,
285
+ scope: "event" | "league" | "tournament" | "world" | "division",
286
+ eventId | leagueId | tournamentId | division | season
287
+ }
288
+ 4. For a featured upcoming match:
289
+ match_preview { game, matchId } # or teamA + teamB
290
+ 5. When a match goes live / completes:
291
+ match_summary { game, matchId }
292
+ ```
293
+
294
+ **UI map**
295
+
296
+ | Screen region | Source |
297
+ | --- | --- |
298
+ | Upcoming list | `upcoming_schedule` → `data.items` |
299
+ | Table / ranks | `standings` → `data.rows` |
300
+ | Featured match briefing | `match_preview` → `sideA` / `sideB` / `h2h` / `talkingPoints` |
301
+ | Live / completed card | `match_summary` |
61
302
 
62
- ## Curated extras
303
+ **Notes:** Dota standings may return `NOT_IMPLEMENTED` or sparse data — fall back to schedule + profiles. UFC event hubs lean on fighter slugs, `boutId`, and `standings` with `scope: "division"`.
63
304
 
64
- Hand-written, on top of the generated tools:
305
+ ### Other quick recipes (from `list_capabilities`)
65
306
 
66
- | Tool | Purpose |
307
+ | Recipe id | Steps |
67
308
  | --- | --- |
68
- | `cito_live_overview` | One merged "what's live right now" across all games (`/lol/live`, `/dota2/matches/live`, `/cs2/matches/live`, `/cod/matches/live`): per-game count + first few match labels. Games that 4xx are skipped, not fatal. |
69
- | `cito_api_health` | Authenticated probe proving your key works: tries `$CITO_API_BASE/health`, falls back to `$CITO_API_BASE/lol/leagues`, reports which probe answered, plus rate-limit/tier headers when present. |
309
+ | `live_ops_board` | `api_health` `live_matches` `match_summary` / `match_details` |
310
+ | `team_page` | `resolve_entity { type: "team" }` `team_profile` |
311
+ | `match_center_completed` | schedule/live for `matchId` → `match_summary` → optional `match_details` |
312
+ | `app_scaffold` | `list_capabilities` ∥ `api_health` → sample composites → **ship REST in production** |
313
+
314
+ ---
70
315
 
71
- Resources:
316
+ ## Resources
72
317
 
73
- - `cito://llms.txt` the API's agent-context file, fetched on read.
74
- - `cito://openapi.json` the current spec this server generated its tools from.
318
+ | URI | Purpose |
319
+ | --- | --- |
320
+ | `cito://capabilities` | Static tool / game / workflow summary |
321
+ | `cito://llms.txt` | Live product agent context (may 404 upstream) |
322
+ | `cito://openapi.json` | Live public OpenAPI for **typed clients** — not for tool generation |
75
323
 
76
- ## Behavior notes
324
+ ---
77
325
 
78
- - Path params are interpolated into the URL, query props become the query string, `body` becomes the JSON body.
79
- - Non-2xx responses are returned as-is with `isError: true` — agents can read the API's own error codes (quota, tier gates, 404s).
80
- - Responses over ~100KB are truncated with a note telling the agent to narrow with query parameters.
81
- - Adding new endpoints to the API = **zero MCP code**. The spec is the contract.
326
+ ## Transports
82
327
 
83
- ## HTTP transport (optional)
328
+ **stdio** is the default (Claude Code, Cursor, Windsurf).
84
329
 
85
- stdio is the default (what Claude Code / Cursor use). A stateless Streamable HTTP endpoint is available:
330
+ Optional **stateless Streamable HTTP**:
86
331
 
87
332
  ```bash
88
333
  CITO_API_KEY=cito_your_key_here node dist/index.js --http 8787
89
334
  # → http://127.0.0.1:8787/mcp
90
335
  ```
91
336
 
337
+ ---
338
+
339
+ ## Migration from 0.1 (auto-generated tools)
340
+
341
+ **Breaking change.** `0.1.x` loaded OpenAPI at boot and mass-generated ~100+ tools (`generateAllTools`), plus a couple of hard-coded helpers (e.g. `cito_live_overview`, `cito_api_health`). Agents selected among thin REST wrappers and often reimplemented composition client-side.
342
+
343
+ | 0.1 pattern | 0.2 replacement |
344
+ | --- | --- |
345
+ | Generated per-path tools (`/cs2/matches/...`, etc.) | Outcome tools in the table above |
346
+ | `cito_live_overview` / ad-hoc live probes | `live_matches` (fixed CS2 path `/cs2/live`; UFC included) |
347
+ | `cito_api_health` | `api_health` |
348
+ | OpenAPI-named tools with `cito_` noise | **15** `snake_case` names, no `cito_` prefix |
349
+ | Raw HTTP-shaped responses | Stable envelope: `ok`, `data`, `meta`, `pagination`, `partial`, `error` |
350
+ | Agent stitches roster + matches + H2H | `team_profile`, `player_profile`, `head_to_head`, `match_preview` |
351
+ | Fortnite / niche paths as first-class tools | `call_api` allowlist |
352
+ | Spec cache / boot network for tool list | Static catalog; `list_capabilities` is offline |
353
+
354
+ **Action items for existing clients**
355
+
356
+ 1. Drop any hardcoded 0.1 tool names; rebind prompts to the 15-tool catalog.
357
+ 2. Always parse the envelope (`ok` / `partial` / `error.recover`) — do not assume bare REST JSON.
358
+ 3. Use `resolve_entity` before profile tools when you only have a display name.
359
+ 4. Prefer composites over reintroducing N+1 `call_api` loops.
360
+ 5. Bump config to `npx cito-mcp` (or rebuild from this package) and re-test live board + one profile + one match flow.
361
+
362
+ There is **no compatibility shim** for 0.1 tool names in 0.2.
363
+
364
+ ---
365
+
92
366
  ## Development
93
367
 
94
368
  ```bash
95
369
  npm install
96
370
  npm run build # tsc → dist/
97
- npm test # node:test via tsx, no network
371
+ npm test # node:test via tsx, offline (no network)
372
+ npm start # node dist/index.js (requires CITO_API_KEY)
373
+ ```
374
+
375
+ Source layout (authoritative tool registry: `src/tools/index.ts`):
376
+
377
+ ```
378
+ src/
379
+ index.ts # MCP wiring, resources, stdio / --http
380
+ client.ts # fetchJson, auth, present, cursors
381
+ envelope.ts # success / error envelopes, MCP isError
382
+ instructions.ts # server instructions for agents
383
+ tools/
384
+ index.ts # allTools (15)
385
+ meta.ts # list_capabilities, api_health, call_api
386
+ resolve.ts # resolve_entity, search_entities
387
+ live.ts # live_matches, upcoming_schedule
388
+ match.ts # match_summary, match_details
389
+ player.ts # player_profile
390
+ team.ts # team_profile, head_to_head
391
+ standings.ts # standings
392
+ insight.ts # match_preview, event_card
393
+ normalize.ts # cross-game normalization
394
+ types.ts # ToolDef, schemas
395
+ ```
396
+
397
+ ---
398
+
399
+ ## Publish notes
400
+
401
+ Package: **`cito-mcp@0.2.2`**
402
+
403
+ | Item | Value |
404
+ | --- | --- |
405
+ | npm name | `cito-mcp` |
406
+ | bin | `cito-mcp` → `dist/index.js` |
407
+ | published files | `dist/`, `README.md` (`package.json` `"files"`) |
408
+ | engines | `node >= 20` |
409
+ | runtime dependency | `@modelcontextprotocol/sdk` only |
410
+ | prepublish | `npm run prepublishOnly` → `npm run build` |
411
+
412
+ ### Pre-publish checklist
413
+
414
+ 1. `npm test` — offline suite green.
415
+ 2. `npm run build` — clean `dist/`.
416
+ 3. Smoke against a real key (not in CI by default):
417
+ - `api_health`
418
+ - `live_matches`
419
+ - `resolve_entity` (e.g. T1 / s1mple)
420
+ - `match_summary` with a real `matchId` from live/schedule
421
+ 4. Confirm `package.json` version and README match the shipped tool list (15).
422
+ 5. `npm publish` from `mcp/` (or your release pipeline) with appropriate npm auth / access.
423
+
424
+ ### Install line for docs & marketing
425
+
426
+ ```bash
427
+ claude mcp add cito -e CITO_API_KEY=cito_… -- npx cito-mcp
428
+ ```
429
+
430
+ ```json
431
+ { "command": "npx", "args": ["cito-mcp"], "env": { "CITO_API_KEY": "cito_…" } }
98
432
  ```
99
433
 
100
- Environment variables: `CITO_API_KEY` (required), `CITO_API_BASE` (default `https://api.citoapi.com/api/v1`), `CITO_OPENAPI_URLS` / `CITO_OPENAPI_URL` (spec source overrides), `CITO_SPEC_BASE_<KEY>` (per-spec base override), `CITO_SPEC_REFRESH_MINUTES` (default 60).
434
+ ### Semver expectations
435
+
436
+ - **0.2.0** is a **major surface break** vs 0.1 (tool rename + removal of OpenAPI mass-generation).
437
+ - Further 0.2.x patches may refine envelopes and composite quality without renaming the 15 tools.
438
+ - Promoting Fortnite (or other titles) into the primary `game` enum would be a minor feature bump with catalog/docs updates.
439
+
440
+ ---
441
+
442
+ ## Changelog (summary)
443
+
444
+ ### 0.2.0
445
+
446
+ - **Breaking:** replaced OpenAPI mass-generated tools with **15 curated outcome tools**
447
+ - Stable JSON envelope + MCP server instructions
448
+ - Fixed CS2 live path (`/cs2/live`); UFC in live fan-out
449
+ - Composites with `partial[]` recovery; `call_api` path allowlist
450
+ - Resources: `cito://capabilities`, `cito://llms.txt`, `cito://openapi.json`
451
+
452
+ See [`_workflow/CHANGELOG-0.2.md`](_workflow/CHANGELOG-0.2.md) for the full 0.2 write-up.