cito-mcp 0.5.4 → 0.5.5

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,13 @@ claude mcp add cito -e CITO_API_KEY=cito_… "--" npx -y cito-mcp
550
550
 
551
551
  ## Changelog (summary)
552
552
 
553
+ ### 0.5.5
554
+
555
+ - **`cs2_veto_sequence` returns the veto.** It called the match head-to-head route and answered with the two teams' head-to-head record (`cs2_team_h2h`) instead of the veto (0.5.1 to 0.5.4). It now calls `/cs2/matches/{matchId}/veto`: `vetoOrder` (every ban, pick and the decider, in order), `vetoStatus` (`complete`, `pending`, `forfeit_no_veto`, `unavailable`), `bestOf` and the maps.
556
+ - **`cs2_opening_duels` with `playerId` works.** It called a route the API does not have (every call was a 404). A player's opening duels now come from the career totals: `openingKills` / `openingDeaths` (null when fewer than 90% of the player's maps record them), the recorded sums, coverage and success rate.
557
+ - **`cs2_team_map_stats` takes `opponentId`** for a map-by-map comparison of two teams (`/cs2/teams/{team}/map-pool/vs/{opponent}`: win rates, round difference, pick/ban/decider rates, their meetings per map, edge). Map rows carry `inPool` / `poolStatus`.
558
+ - `cs2_round_economy` points to `/cs2/maps/{mapId}/economy` for per-player money each round.
559
+
553
560
  ### 0.5.4
554
561
 
555
562
  - **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`.
package/dist/tools/cs2.js CHANGED
@@ -59,7 +59,8 @@ export const cs2LiveScoreboard = {
59
59
  };
60
60
  export const cs2RoundEconomy = {
61
61
  name: 'cs2_round_economy',
62
- description: 'Round-by-round tactical economy for a CS2 map. Classifies every round buy type (Full Buy, Force Buy, Semi-Eco, Full Eco), team equipment spend, freeze-time equipment values, and loss bonus counter.',
62
+ description: 'Round-by-round tactical economy for a CS2 map. Classifies every round buy type (Full Buy, Force Buy, Semi-Eco, Full Eco), team equipment spend, freeze-time equipment values, and loss bonus counter. '
63
+ + 'For money per PLAYER each round (startMoney, moneySpent, moneyLeft, equipmentValue, buyType, purchases) use call_api GET /cs2/maps/{mapId}/economy (by=player|team|round; paid plans).',
63
64
  inputSchema: {
64
65
  type: 'object',
65
66
  properties: {
@@ -123,9 +124,9 @@ export const cs2OpeningDuels = {
123
124
  const rawPlayer = typeof args.playerId === 'string' ? args.playerId.trim() : '';
124
125
  const limit = clampInt(args.limit, 20, 1, 100);
125
126
  const path = rawPlayer
126
- ? `/cs2/players/${encodeURIComponent(rawPlayer)}/opening-duels`
127
+ ? `/cs2/players/${encodeURIComponent(rawPlayer)}/career`
127
128
  : `/cs2/stats/opening-duels`;
128
- const res = await fetchJson(ctx, path, { query: { limit } });
129
+ const res = await fetchJson(ctx, path, rawPlayer ? {} : { query: { limit } });
129
130
  if (!res.ok) {
130
131
  return errorEnvelope({
131
132
  code: mapHttpToCode(res.status),
@@ -137,8 +138,29 @@ export const cs2OpeningDuels = {
137
138
  });
138
139
  }
139
140
  const payload = unwrap(res.data);
141
+ let data = payload;
142
+ if (rawPlayer) {
143
+ const p = asRecord(payload) ?? {};
144
+ const t = asRecord(p.totals) ?? {};
145
+ const fk = typeof t.openingKillsRecorded === 'number' ? t.openingKillsRecorded : null;
146
+ const fd = typeof t.openingDeathsRecorded === 'number' ? t.openingDeathsRecorded : null;
147
+ data = {
148
+ entityType: 'cs2_player_opening_duels',
149
+ playerId: p.playerId ?? null,
150
+ playerNickname: p.playerNickname ?? null,
151
+ maps: t.maps ?? null,
152
+ openingKills: t.openingKills ?? null,
153
+ openingDeaths: t.openingDeaths ?? null,
154
+ openingKillsRecorded: fk,
155
+ openingDeathsRecorded: fd,
156
+ openingDuelMaps: t.openingDuelMaps ?? null,
157
+ openingDuelCoverage: t.openingDuelCoverage ?? null,
158
+ openingDuelSuccessRate: fk !== null && fd !== null && fk + fd > 0 ? Math.round((fk / (fk + fd)) * 10000) / 10000 : null,
159
+ source: `/cs2/players/${rawPlayer}/career`,
160
+ };
161
+ }
140
162
  return successEnvelope({
141
- data: payload,
163
+ data,
142
164
  game: 'cs2',
143
165
  source: 'cs2_opening_duels',
144
166
  requestId: reqId,
@@ -188,16 +210,18 @@ export const cs2Clutches = {
188
210
  };
189
211
  export const cs2TeamMapStats = {
190
212
  name: 'cs2_team_map_stats',
191
- description: 'Team win rates, round win rates, and CT/T side win splits across the competitive map pool (Mirage, Inferno, Nuke, Dust2, Ancient, Anubis, Vertigo) over the last 30 or 90 days.',
213
+ description: 'Team win rates, round win rates, and CT/T side win splits per map over the last N days. Each map carries inPool / poolStatus (current Active Duty pool) and removedAt. '
214
+ + 'Pass opponentId to compare two teams map by map instead (GET /cs2/teams/{team}/map-pool/vs/{opponent}): win rates, round difference, pick/ban/decider rates, their meetings on each map and an edge per map.',
192
215
  inputSchema: {
193
216
  type: 'object',
194
217
  properties: {
195
218
  teamId: stringSchema('Team ID or slug (e.g. "cs2-team-4608" or "natus-vincere"). Required.', 'cs2-team-4608'),
196
219
  days: {
197
220
  type: 'integer',
198
- description: 'Timeframe in days: 30, 90, 180, or 365 (default 90).',
221
+ description: 'Timeframe in days: 30, 90, 180, or 365 (default 90; with opponentId the API default is 180).',
199
222
  default: 90,
200
223
  },
224
+ opponentId: stringSchema('Optional opponent team ID or slug (e.g. "mouz"): returns the map-pool head-to-head of the two teams.', 'mouz'),
201
225
  },
202
226
  required: ['teamId'],
203
227
  additionalProperties: false,
@@ -215,9 +239,12 @@ export const cs2TeamMapStats = {
215
239
  requestId: reqId,
216
240
  });
217
241
  }
218
- const days = typeof args.days === 'number' ? args.days : 90;
219
- const path = `/cs2/teams/${encodeURIComponent(rawTeam)}/map-stats`;
220
- const res = await fetchJson(ctx, path, { query: { days } });
242
+ const rawOpponent = typeof args.opponentId === 'string' ? args.opponentId.trim() : '';
243
+ const days = typeof args.days === 'number' ? args.days : rawOpponent ? undefined : 90;
244
+ const path = rawOpponent
245
+ ? `/cs2/teams/${encodeURIComponent(rawTeam)}/map-pool/vs/${encodeURIComponent(rawOpponent)}`
246
+ : `/cs2/teams/${encodeURIComponent(rawTeam)}/map-stats`;
247
+ const res = await fetchJson(ctx, path, days === undefined ? {} : { query: { days } });
221
248
  if (!res.ok) {
222
249
  return errorEnvelope({
223
250
  code: mapHttpToCode(res.status),
@@ -276,7 +303,8 @@ export const cs2UtilityLeaderboard = {
276
303
  };
277
304
  export const cs2VetoSequence = {
278
305
  name: 'cs2_veto_sequence',
279
- description: 'Map pick/ban veto sequence for a CS2 match. Shows which team banned which map, map picks, and decider map in chronological order.',
306
+ description: 'Map pick/ban veto sequence for a CS2 match (GET /cs2/matches/{matchId}/veto). Returns vetoOrder (each ban, pick and the decider in order, with the team), '
307
+ + 'vetoStatus (complete | pending | forfeit_no_veto | unavailable; vetoOrder is [] unless complete), bestOf and the maps.',
280
308
  inputSchema: {
281
309
  type: 'object',
282
310
  properties: {
@@ -298,8 +326,7 @@ export const cs2VetoSequence = {
298
326
  requestId: reqId,
299
327
  });
300
328
  }
301
- const cleanId = rawId.replace(/^cs2-match-/, '');
302
- const path = `/cs2/matches/${encodeURIComponent(cleanId)}/head-to-head`;
329
+ const path = `/cs2/matches/${encodeURIComponent(rawId)}/veto`;
303
330
  const res = await fetchJson(ctx, path);
304
331
  if (!res.ok) {
305
332
  return errorEnvelope({
@@ -312,10 +339,8 @@ export const cs2VetoSequence = {
312
339
  });
313
340
  }
314
341
  const payload = unwrap(res.data);
315
- const r = asRecord(payload);
316
- const veto = r?.veto ?? r?.vetoSequence ?? payload;
317
342
  return successEnvelope({
318
- data: veto,
343
+ data: payload,
319
344
  game: 'cs2',
320
345
  source: 'cs2_veto_sequence',
321
346
  requestId: reqId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cito-mcp",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
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",