cito-mcp 0.5.3 → 0.5.4

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
@@ -550,6 +550,11 @@ claude mcp add cito -e CITO_API_KEY=cito_… "--" npx -y cito-mcp
550
550
 
551
551
  ## Changelog (summary)
552
552
 
553
+ ### 0.5.4
554
+
555
+ - **Apex Legends through `call_api`.** `/apex/...` paths (ALGS events, standings, scores) were refused with `PATH_NOT_ALLOWED` although they are a live REST product. They are allowed now and listed in `list_capabilities`.
556
+ - **`api_health` reports your real plan and limits**: the tier plus the per-minute and monthly limit, used and remaining, read from the API's `/health`, instead of `null`.
557
+
553
558
  ### 0.5.3
554
559
 
555
560
  - **Paces itself and retries rate limits.** One question fans out into several API calls, and on the Free plan (10 requests/minute on average, bursts to 30) those bursts came back as `429`s the MCP never waited out. Requests are now capped at 4 in flight per API key (2 for a minute after a `429`), and once the API sends `X-RateLimit-Remaining-Minute: 0` the next request waits for the minute window instead of being refused. Paid keys never receive those headers, so they never wait.
package/dist/envelope.js CHANGED
@@ -38,7 +38,7 @@ export const DEFAULT_RECOVER = {
38
38
  'Call api_health to see which games your plan includes',
39
39
  ],
40
40
  PATH_NOT_ALLOWED: [
41
- 'Use only allowlisted path prefixes: /health /lol /cs2 /dota2 /cod /ufc /fortnite /tennis /raf /bkfc, MMA archives /bellator /pride /strikeforce /wec /dream /elitexc /affliction /pfl /oktagon /mma',
41
+ 'Use only allowlisted path prefixes: /health /lol /cs2 /dota2 /cod /ufc /fortnite /apex /tennis /raf /bkfc, MMA archives /bellator /pride /strikeforce /wec /dream /elitexc /affliction /pfl /oktagon /mma',
42
42
  'Prefer a curated tool from list_capabilities when one covers the outcome',
43
43
  ],
44
44
  NOT_IMPLEMENTED: [
@@ -17,6 +17,7 @@ game enum (unless a tool documents otherwise): lol | cs2 | dota2 | ufc | cod | t
17
17
  - Default when the user named one title: that game. Default for "what's live?" / multi-title: omit game or all on tools that accept it.
18
18
  - On UNSUPPORTED_GAME / 403 for a title: stop retrying that game; call api_health; report plan gaps.
19
19
  - Fortnite and other titles may appear via call_api/resources; do not assume they are in the curated enum until list_capabilities says so.
20
+ - Apex Legends (ALGS) is call_api only: /apex/algs/events, /apex/algs/events/{seriesId}/scores, /apex/algs/players, /apex/algs/global-stats/{eventId}. Not on the game enum and not in the OpenAPI spec; list_routes { game: "apex" } returns the path list as a note.
20
21
  - RAF (Real American Freestyle wrestling) and BKFC (Bare Knuckle Fighting Championship) are NOT on the game enum: use the combat_* tools with league raf|bkfc (combat_events, combat_card, combat_bout, combat_athlete, combat_h2h, combat_rankings), or call_api /raf/... and /bkfc/.... RAF scores per PERIOD (wrestling points, takedowns); BKFC per ROUND (punches landed/thrown, knockdowns). Never mix either into UFC answers.
21
22
  - MMA archives: Bellator, PRIDE, Strikeforce, WEC, DREAM, EliteXC, Affliction, plus /mma for a fighter's whole non-UFC career (recordByPromotion). combat_* tools with league bellator|pride|strikeforce|wec|dream|elitexc|affliction|mma, or call_api /{league}/.... Closed archives (1997-2017) with official per-ROUND stats in UFC's shape ("13 of 18", round 0 = whole fight); no live, rankings or upcoming cards (combat_card "last" = most recent card). Fighters share their UFC slug (ufcFighterSlug). Never mix these into UFC answers. Their stats predate the Free/Starter window: HISTORY_WINDOW_EXCEEDED means Pro, not a bad key.
22
23
  - PFL (Professional Fighters League): combat_* with league pfl, or call_api /pfl/.... Every card since 2018, upcoming cards (combat_events when upcoming, combat_card "next") and live fight status (/pfl/live: bout status "live" with live.round / live.clock). Results only (winner, method, round, time): no strike or round stats exist for PFL. league mma includes PFL. Never mix into UFC answers.
@@ -476,7 +476,7 @@ const TOOL_CATALOG = [
476
476
  name: 'list_routes',
477
477
  outcome: 'Index of raw REST routes from the live OpenAPI spec (method, path, summary)',
478
478
  parallelSafe: true,
479
- games: [...PRIMARY_GAMES, 'fortnite'],
479
+ games: [...PRIMARY_GAMES, 'fortnite', 'apex'],
480
480
  jobs: ['app_scaffold'],
481
481
  exampleArgs: { game: 'ufc', q: 'rankings' },
482
482
  preferOver: ['guessing a REST path for call_api', 'fetching the full 90KB spec by hand'],
@@ -486,7 +486,7 @@ const TOOL_CATALOG = [
486
486
  name: 'call_api',
487
487
  outcome: 'Allowlisted raw REST escape hatch (unshaped data.raw)',
488
488
  parallelSafe: true,
489
- games: [...PRIMARY_GAMES, 'fortnite'],
489
+ games: [...PRIMARY_GAMES, 'fortnite', 'apex'],
490
490
  jobs: ['app_scaffold'],
491
491
  exampleArgs: { method: 'GET', path: '/cs2/rankings/teams', queryJson: '{"limit":20}' },
492
492
  preferOver: [],
@@ -558,6 +558,14 @@ const GAMES_META = [
558
558
  { id: 'cs2', label: 'Counter-Strike 2', depth: 'deep', notes: 'Prefixed ids; live path /cs2/live; trends/rankings' },
559
559
  { id: 'dota2', label: 'Dota 2', depth: 'thin', notes: 'Numeric ids; radar; no standings/roster endpoints' },
560
560
  { id: 'cod', label: 'Call of Duty (CDL)', depth: 'medium', notes: 'Org slugs; CDL standings; UUID match ids' },
561
+ {
562
+ id: 'apex',
563
+ label: 'Apex Legends (ALGS)',
564
+ depth: 'thin',
565
+ notes: 'call_api only (no curated tools): /apex/algs/events, /apex/algs/events/{seriesId} (+ /scores, /bans), ' +
566
+ '/apex/algs/matches, /apex/algs/players (+ /search), /apex/algs/global-stats/{eventId} (+ /players, /legends), ' +
567
+ '/apex/algs/transactions, /apex/algs/regions, /apex/algs/maps. Not in the OpenAPI spec yet (list_routes { game: "apex" } returns this path list as a note).',
568
+ },
561
569
  {
562
570
  id: 'ufc',
563
571
  label: 'UFC / MMA',
@@ -776,20 +784,38 @@ Example: { "includeGameProbes": true }`,
776
784
  else if (keyValid) {
777
785
  includedGames.push(...PRIMARY_GAMES);
778
786
  }
787
+ const healthBody = health.ok && health.data && typeof health.data === 'object' && !Array.isArray(health.data)
788
+ ? health.data
789
+ : null;
790
+ const limits = healthBody?.rateLimit && typeof healthBody.rateLimit === 'object'
791
+ ? healthBody.rateLimit
792
+ : null;
793
+ const perMinute = limits?.perMinute && typeof limits.perMinute === 'object'
794
+ ? limits.perMinute
795
+ : null;
796
+ const headerRate = rateLimit;
797
+ const tier = headerRate.tier ?? (typeof healthBody?.tier === 'string' ? healthBody.tier : null);
798
+ const mergedRateLimit = {
799
+ ...headerRate,
800
+ tier,
801
+ limit: headerRate.limit ?? (typeof perMinute?.limit === 'number' ? perMinute.limit : null),
802
+ };
779
803
  return successEnvelope({
780
804
  source: 'api_health',
781
805
  game: null,
782
806
  requestId,
783
807
  tookMs: Date.now() - started,
784
808
  upstreamCalls,
785
- rateLimit,
809
+ rateLimit: mergedRateLimit,
786
810
  data: {
787
811
  reachable,
788
812
  keyValid,
789
813
  answeredBy,
790
814
  httpStatus: authedStatus,
791
- tier: rateLimit.tier ?? null,
792
- rateLimit,
815
+ tier,
816
+ plan: typeof healthBody?.plan === 'string' ? healthBody.plan : null,
817
+ rateLimit: mergedRateLimit,
818
+ ...(limits ? { limits } : {}),
793
819
  includedGames,
794
820
  ...(probes.length ? { probes } : {}),
795
821
  },
@@ -804,6 +830,7 @@ const ALLOWLIST_PREFIXES = [
804
830
  '/cod',
805
831
  '/ufc',
806
832
  '/fortnite',
833
+ '/apex',
807
834
  '/tennis',
808
835
  '/raf',
809
836
  '/bkfc',
@@ -840,7 +867,7 @@ Prefer curated tools for all standard jobs (live, schedule, profiles, standings,
840
867
 
841
868
  Do not use when: a curated tool covers the outcome. Avoid parallel storms; same plan rate limits apply.
842
869
 
843
- Path must start with / and match allowlisted prefixes: /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma.
870
+ Path must start with / and match allowlisted prefixes: /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /apex, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma.
844
871
  Rejects absolute URLs and path traversal → PATH_NOT_ALLOWED.
845
872
 
846
873
  Parallel-safe: yes but discouraged in bulk. Upstream cost: 1.
@@ -901,7 +928,7 @@ Example: { "method": "GET", "path": "/cs2/rankings/teams", "queryJson": "{\\"pag
901
928
  source: 'call_api',
902
929
  requestId,
903
930
  tookMs: Date.now() - started,
904
- hint: 'Use prefixes /health /lol /cs2 /dota2 /cod /ufc /fortnite /tennis /raf /bkfc /bellator /pride /strikeforce /wec /dream /elitexc /affliction /pfl /oktagon /mma',
931
+ hint: 'Use prefixes /health /lol /cs2 /dota2 /cod /ufc /fortnite /apex /tennis /raf /bkfc /bellator /pride /strikeforce /wec /dream /elitexc /affliction /pfl /oktagon /mma',
905
932
  });
906
933
  }
907
934
  let query = {};
@@ -962,6 +989,10 @@ Example: { "method": "GET", "path": "/cs2/rankings/teams", "queryJson": "{\\"pag
962
989
  const SPEC_OMITS = {
963
990
  tennis: 'The published OpenAPI spec documents no /tennis paths, but tennis routes exist and work (players, matches, h2h, rankings, tournaments, live). Use the curated tools with game tennis, or call_api with /tennis/* paths.',
964
991
  lol: 'The published OpenAPI spec documents no /lol paths, but LoL routes exist and work. Use the curated LoL tools (live_matches, upcoming_schedule, team_profile, standings, player_profile); for raw access, /lol/* is allowlisted for call_api even though it is undocumented.',
992
+ apex: 'The published OpenAPI spec documents no /apex paths, but the Apex Legends ALGS routes exist and work through call_api: '
993
+ + 'GET /apex/algs/events, /apex/algs/events/{seriesId} (+ /scores, /bans), /apex/algs/matches, /apex/algs/players (+ /search?q=), '
994
+ + '/apex/algs/global-stats (+ /{eventId}, /{eventId}/players, /{eventId}/legends), /apex/algs/transactions, /apex/algs/tournaments, '
995
+ + '/apex/algs/regions, /apex/algs/maps.',
965
996
  };
966
997
  export const listRoutes = {
967
998
  name: 'list_routes',
@@ -1002,10 +1033,10 @@ Example: { "game": "ufc", "q": "rankings" }`,
1002
1033
  const started = Date.now();
1003
1034
  const requestId = newRequestId();
1004
1035
  const gameRaw = typeof args.game === 'string' ? args.game.toLowerCase().trim() : '';
1005
- if (gameRaw && !gameRaw.match(/^(lol|cs2|dota2|cod|ufc|fortnite|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma)$/)) {
1036
+ if (gameRaw && !gameRaw.match(/^(lol|cs2|dota2|cod|ufc|fortnite|apex|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma)$/)) {
1006
1037
  return errorEnvelope({
1007
1038
  code: 'UNSUPPORTED_GAME',
1008
- message: `unsupported game "${gameRaw}"; use lol|cs2|dota2|cod|ufc|fortnite|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma`,
1039
+ message: `unsupported game "${gameRaw}"; use lol|cs2|dota2|cod|ufc|fortnite|apex|tennis|raf|bkfc|bellator|pride|strikeforce|wec|dream|elitexc|affliction|pfl|oktagon|mma`,
1009
1040
  game: null,
1010
1041
  source: 'list_routes',
1011
1042
  requestId,
@@ -1028,7 +1059,7 @@ Example: { "game": "ufc", "q": "rankings" }`,
1028
1059
  httpStatus: res.status,
1029
1060
  recover: [
1030
1061
  'Call list_capabilities for curated tools that need no route knowledge',
1031
- 'call_api accepts /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma',
1062
+ 'call_api accepts /health, /lol, /cs2, /dota2, /cod, /ufc, /fortnite, /apex, /tennis, /raf, /bkfc, /bellator, /pride, /strikeforce, /wec, /dream, /elitexc, /affliction, /pfl, /oktagon, /mma',
1032
1063
  ],
1033
1064
  });
1034
1065
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cito-mcp",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Standalone MCP server for the Cito esports and sports API — curated outcome tools for agents (live scoreboards, round economy, opening duels, clutches, vetoes, rosters, tennis, mma).",
5
5
  "type": "module",
6
6
  "homepage": "https://cito.gg",