cito-mcp 0.3.20 → 0.4.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 +7 -1
- package/dist/tools/cs2.js +421 -0
- package/dist/tools/index.js +13 -1
- package/dist/tools/leaderboard.js +427 -0
- package/dist/tools/meta.js +241 -1
- package/dist/tools/odds.js +251 -0
- package/dist/tools/player.js +340 -1
- package/dist/tools/rankings.js +147 -1
- package/dist/tools/schedule.js +234 -0
- package/dist/tools/team.js +183 -1
- package/dist/tools/tournaments.js +253 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -12,7 +12,7 @@ Primary games & sports: **lol · cs2 · dota2 · cod · ufc · tennis**. Long-ta
|
|
|
12
12
|
|
|
13
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
14
|
|
|
15
|
-
**
|
|
15
|
+
**Ships 42 curated outcome tools** that answer *jobs* instead of mirroring REST:
|
|
16
16
|
|
|
17
17
|
| Job | Tool |
|
|
18
18
|
| --- | --- |
|
|
@@ -28,6 +28,12 @@ v0.1 exposed ~100+ tools auto-generated from OpenAPI. Agents had to pick among t
|
|
|
28
28
|
| Pre-match briefing (with surface) | `match_preview` |
|
|
29
29
|
| Event / fight-night card | `event_card` |
|
|
30
30
|
| Rivalry record | `head_to_head` |
|
|
31
|
+
| Tennis betting odds | `tennis_odds` |
|
|
32
|
+
| Tennis tournament catalog | `tournaments` |
|
|
33
|
+
| One day of tennis | `tennis_schedule` |
|
|
34
|
+
| Tennis player match log | `player_matches` |
|
|
35
|
+
| Tennis career statistics | `player_stats` |
|
|
36
|
+
| Tennis ranking trajectory | `player_rankings_history` |
|
|
31
37
|
| Fighter / team photos | `event_card` · `player_profile` |
|
|
32
38
|
| Which raw REST route exists? | `list_routes` |
|
|
33
39
|
| Name → ID | `resolve_entity` / `search_entities` |
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedicated CS2 esports analytics & live scorebot MCP tools.
|
|
3
|
+
*
|
|
4
|
+
* Exposes deep CS2 domain data from the 161 backend endpoints:
|
|
5
|
+
* - Live real-time scoreboards & round state
|
|
6
|
+
* - Round-by-round tactical economies & buy classifications
|
|
7
|
+
* - First blood / opening duels per player & map
|
|
8
|
+
* - 1vX clutch success rates (1v1 through 1v5)
|
|
9
|
+
* - Team map pool analytics (Mirage, Inferno, Nuke, Dust2, Ancient, Anubis, Vertigo)
|
|
10
|
+
* - Sub-second utility & flashbang efficiency leaderboard
|
|
11
|
+
* - Match map pick/ban veto sequence
|
|
12
|
+
* - Pro roster changes & transfers
|
|
13
|
+
*/
|
|
14
|
+
import { asRecord, clampInt, fetchJson, unwrapPayload, } from '../client.js';
|
|
15
|
+
import { errorEnvelope, mapHttpToCode, newRequestId, successEnvelope, } from '../envelope.js';
|
|
16
|
+
import { limitSchema, READ_TOOL_ANNOTATIONS, stringSchema, } from './types.js';
|
|
17
|
+
/** Unwraps Cito envelope whether inner entity is an object or array */
|
|
18
|
+
function unwrap(val) {
|
|
19
|
+
const p = unwrapPayload(val);
|
|
20
|
+
const r = asRecord(p);
|
|
21
|
+
if (r && Array.isArray(r.data))
|
|
22
|
+
return r.data;
|
|
23
|
+
if (r && Array.isArray(r.items))
|
|
24
|
+
return r.items;
|
|
25
|
+
return p;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 1. cs2_live_scoreboard
|
|
29
|
+
* Real-time round state, bomb status, and player combat stats for an active match.
|
|
30
|
+
*/
|
|
31
|
+
export const cs2LiveScoreboard = {
|
|
32
|
+
name: 'cs2_live_scoreboard',
|
|
33
|
+
description: 'Live real-time in-game scoreboard for an active CS2 match. Returns current round, bomb status, team scores, and individual player stats (K/A/D, ADR, HS%, alive status, HP, armor, weapon, and equipment value).',
|
|
34
|
+
inputSchema: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
properties: {
|
|
37
|
+
matchId: stringSchema('Match ID (e.g. "cs2-match-2397733" or "2397733"). Required.', 'cs2-match-2397733'),
|
|
38
|
+
},
|
|
39
|
+
required: ['matchId'],
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
},
|
|
42
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
43
|
+
handler: async (args, ctx) => {
|
|
44
|
+
const reqId = newRequestId();
|
|
45
|
+
const rawId = typeof args.matchId === 'string' ? args.matchId.trim() : '';
|
|
46
|
+
if (!rawId) {
|
|
47
|
+
return errorEnvelope({
|
|
48
|
+
code: 'VALIDATION',
|
|
49
|
+
message: 'matchId is required',
|
|
50
|
+
game: 'cs2',
|
|
51
|
+
source: 'cs2_live_scoreboard',
|
|
52
|
+
requestId: reqId,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const cleanId = rawId.replace(/^cs2-match-/, '');
|
|
56
|
+
const path = `/cs2/live/${encodeURIComponent(cleanId)}/scoreboard`;
|
|
57
|
+
const res = await fetchJson(ctx, path);
|
|
58
|
+
if (!res.ok) {
|
|
59
|
+
return errorEnvelope({
|
|
60
|
+
code: mapHttpToCode(res.status),
|
|
61
|
+
message: `Live scoreboard fetch failed: ${res.status} on ${path}`,
|
|
62
|
+
game: 'cs2',
|
|
63
|
+
source: 'cs2_live_scoreboard',
|
|
64
|
+
requestId: reqId,
|
|
65
|
+
httpStatus: res.status,
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const payload = unwrap(res.data);
|
|
69
|
+
return successEnvelope({
|
|
70
|
+
data: payload,
|
|
71
|
+
game: 'cs2',
|
|
72
|
+
source: 'cs2_live_scoreboard',
|
|
73
|
+
requestId: reqId,
|
|
74
|
+
rateLimit: res.headers,
|
|
75
|
+
});
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
/**
|
|
79
|
+
* 2. cs2_round_economy
|
|
80
|
+
* Tactical round-by-round buy classification and equipment values for a map.
|
|
81
|
+
*/
|
|
82
|
+
export const cs2RoundEconomy = {
|
|
83
|
+
name: 'cs2_round_economy',
|
|
84
|
+
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.',
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
mapId: stringSchema('Map ID or Game ID (e.g. "7716-map-1" or "cs2-game-123"). Required.', '7716-map-1'),
|
|
89
|
+
},
|
|
90
|
+
required: ['mapId'],
|
|
91
|
+
additionalProperties: false,
|
|
92
|
+
},
|
|
93
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
94
|
+
handler: async (args, ctx) => {
|
|
95
|
+
const reqId = newRequestId();
|
|
96
|
+
const rawMapId = typeof args.mapId === 'string' ? args.mapId.trim() : '';
|
|
97
|
+
if (!rawMapId) {
|
|
98
|
+
return errorEnvelope({
|
|
99
|
+
code: 'VALIDATION',
|
|
100
|
+
message: 'mapId is required',
|
|
101
|
+
game: 'cs2',
|
|
102
|
+
source: 'cs2_round_economy',
|
|
103
|
+
requestId: reqId,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const path = `/cs2/maps/${encodeURIComponent(rawMapId)}/rounds`;
|
|
107
|
+
let res = await fetchJson(ctx, path);
|
|
108
|
+
if (!res.ok && res.status === 404) {
|
|
109
|
+
res = await fetchJson(ctx, `/cs2/games/${encodeURIComponent(rawMapId)}/rounds`);
|
|
110
|
+
}
|
|
111
|
+
if (!res.ok) {
|
|
112
|
+
return errorEnvelope({
|
|
113
|
+
code: mapHttpToCode(res.status),
|
|
114
|
+
message: `Round economy fetch failed: ${res.status} on ${path}`,
|
|
115
|
+
game: 'cs2',
|
|
116
|
+
source: 'cs2_round_economy',
|
|
117
|
+
requestId: reqId,
|
|
118
|
+
httpStatus: res.status,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
const payload = unwrap(res.data);
|
|
122
|
+
return successEnvelope({
|
|
123
|
+
data: payload,
|
|
124
|
+
game: 'cs2',
|
|
125
|
+
source: 'cs2_round_economy',
|
|
126
|
+
requestId: reqId,
|
|
127
|
+
rateLimit: res.headers,
|
|
128
|
+
});
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* 3. cs2_opening_duels
|
|
133
|
+
* First blood / opening kill statistics by player or overall leaderboard.
|
|
134
|
+
*/
|
|
135
|
+
export const cs2OpeningDuels = {
|
|
136
|
+
name: 'cs2_opening_duels',
|
|
137
|
+
description: 'First blood and opening duel statistics. Pass playerId to view a pro player\'s First Kills (FK), First Deaths (FD), and opening duel conversion %, or omit playerId to view the global CS2 opening duel leaderboard.',
|
|
138
|
+
inputSchema: {
|
|
139
|
+
type: 'object',
|
|
140
|
+
properties: {
|
|
141
|
+
playerId: stringSchema('Optional player ID or slug (e.g. "chucky" or "cs2-player-123"). If omitted, returns global leaderboard.', 'chucky'),
|
|
142
|
+
limit: limitSchema({ default: 20, max: 100 }),
|
|
143
|
+
},
|
|
144
|
+
additionalProperties: false,
|
|
145
|
+
},
|
|
146
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
147
|
+
handler: async (args, ctx) => {
|
|
148
|
+
const reqId = newRequestId();
|
|
149
|
+
const rawPlayer = typeof args.playerId === 'string' ? args.playerId.trim() : '';
|
|
150
|
+
const limit = clampInt(args.limit, 20, 1, 100);
|
|
151
|
+
const path = rawPlayer
|
|
152
|
+
? `/cs2/players/${encodeURIComponent(rawPlayer)}/opening-duels`
|
|
153
|
+
: `/cs2/stats/opening-duels`;
|
|
154
|
+
const res = await fetchJson(ctx, path, { query: { limit } });
|
|
155
|
+
if (!res.ok) {
|
|
156
|
+
return errorEnvelope({
|
|
157
|
+
code: mapHttpToCode(res.status),
|
|
158
|
+
message: `Opening duels fetch failed: ${res.status} on ${path}`,
|
|
159
|
+
game: 'cs2',
|
|
160
|
+
source: 'cs2_opening_duels',
|
|
161
|
+
requestId: reqId,
|
|
162
|
+
httpStatus: res.status,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
const payload = unwrap(res.data);
|
|
166
|
+
return successEnvelope({
|
|
167
|
+
data: payload,
|
|
168
|
+
game: 'cs2',
|
|
169
|
+
source: 'cs2_opening_duels',
|
|
170
|
+
requestId: reqId,
|
|
171
|
+
rateLimit: res.headers,
|
|
172
|
+
});
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* 4. cs2_clutches
|
|
177
|
+
* 1vX clutch situation success records (1v1 through 1v5).
|
|
178
|
+
*/
|
|
179
|
+
export const cs2Clutches = {
|
|
180
|
+
name: 'cs2_clutches',
|
|
181
|
+
description: '1vX clutch situation success records. Pass playerId for a specific player\'s clutch breakdown (1v1, 1v2, 1v3, 1v4, 1v5 attempted vs won), or omit to view the global clutch leaderboard.',
|
|
182
|
+
inputSchema: {
|
|
183
|
+
type: 'object',
|
|
184
|
+
properties: {
|
|
185
|
+
playerId: stringSchema('Optional player ID or slug (e.g. "xm1nd" or "cs2-player-456"). If omitted, returns global leaderboard.', 'xm1nd'),
|
|
186
|
+
limit: limitSchema({ default: 20, max: 100 }),
|
|
187
|
+
},
|
|
188
|
+
additionalProperties: false,
|
|
189
|
+
},
|
|
190
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
191
|
+
handler: async (args, ctx) => {
|
|
192
|
+
const reqId = newRequestId();
|
|
193
|
+
const rawPlayer = typeof args.playerId === 'string' ? args.playerId.trim() : '';
|
|
194
|
+
const limit = clampInt(args.limit, 20, 1, 100);
|
|
195
|
+
const path = rawPlayer
|
|
196
|
+
? `/cs2/players/${encodeURIComponent(rawPlayer)}/clutches`
|
|
197
|
+
: `/cs2/stats/clutches`;
|
|
198
|
+
const res = await fetchJson(ctx, path, { query: { limit } });
|
|
199
|
+
if (!res.ok) {
|
|
200
|
+
return errorEnvelope({
|
|
201
|
+
code: mapHttpToCode(res.status),
|
|
202
|
+
message: `Clutches fetch failed: ${res.status} on ${path}`,
|
|
203
|
+
game: 'cs2',
|
|
204
|
+
source: 'cs2_clutches',
|
|
205
|
+
requestId: reqId,
|
|
206
|
+
httpStatus: res.status,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
const payload = unwrap(res.data);
|
|
210
|
+
return successEnvelope({
|
|
211
|
+
data: payload,
|
|
212
|
+
game: 'cs2',
|
|
213
|
+
source: 'cs2_clutches',
|
|
214
|
+
requestId: reqId,
|
|
215
|
+
rateLimit: res.headers,
|
|
216
|
+
});
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* 5. cs2_team_map_stats
|
|
221
|
+
* Team performance across the competitive map pool.
|
|
222
|
+
*/
|
|
223
|
+
export const cs2TeamMapStats = {
|
|
224
|
+
name: 'cs2_team_map_stats',
|
|
225
|
+
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.',
|
|
226
|
+
inputSchema: {
|
|
227
|
+
type: 'object',
|
|
228
|
+
properties: {
|
|
229
|
+
teamId: stringSchema('Team ID or slug (e.g. "hltv-team-4608", "cs2-team-4608", or "natus-vincere"). Required.', 'hltv-team-4608'),
|
|
230
|
+
days: {
|
|
231
|
+
type: 'integer',
|
|
232
|
+
description: 'Timeframe in days: 30, 90, 180, or 365 (default 90).',
|
|
233
|
+
default: 90,
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
required: ['teamId'],
|
|
237
|
+
additionalProperties: false,
|
|
238
|
+
},
|
|
239
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
240
|
+
handler: async (args, ctx) => {
|
|
241
|
+
const reqId = newRequestId();
|
|
242
|
+
const rawTeam = typeof args.teamId === 'string' ? args.teamId.trim() : '';
|
|
243
|
+
if (!rawTeam) {
|
|
244
|
+
return errorEnvelope({
|
|
245
|
+
code: 'VALIDATION',
|
|
246
|
+
message: 'teamId is required',
|
|
247
|
+
game: 'cs2',
|
|
248
|
+
source: 'cs2_team_map_stats',
|
|
249
|
+
requestId: reqId,
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const days = typeof args.days === 'number' ? args.days : 90;
|
|
253
|
+
const path = `/cs2/teams/${encodeURIComponent(rawTeam)}/map-stats`;
|
|
254
|
+
const res = await fetchJson(ctx, path, { query: { days } });
|
|
255
|
+
if (!res.ok) {
|
|
256
|
+
return errorEnvelope({
|
|
257
|
+
code: mapHttpToCode(res.status),
|
|
258
|
+
message: `Team map stats fetch failed: ${res.status} on ${path}`,
|
|
259
|
+
game: 'cs2',
|
|
260
|
+
source: 'cs2_team_map_stats',
|
|
261
|
+
requestId: reqId,
|
|
262
|
+
httpStatus: res.status,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
const payload = unwrap(res.data);
|
|
266
|
+
return successEnvelope({
|
|
267
|
+
data: payload,
|
|
268
|
+
game: 'cs2',
|
|
269
|
+
source: 'cs2_team_map_stats',
|
|
270
|
+
requestId: reqId,
|
|
271
|
+
rateLimit: res.headers,
|
|
272
|
+
});
|
|
273
|
+
},
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* 6. cs2_utility_leaderboard
|
|
277
|
+
* Sub-second cached leaderboard for flashbang & utility efficiency.
|
|
278
|
+
*/
|
|
279
|
+
export const cs2UtilityLeaderboard = {
|
|
280
|
+
name: 'cs2_utility_leaderboard',
|
|
281
|
+
description: 'Sub-second cached leaderboard of CS2 utility and flashbang efficiency. Returns pro player rankings for effective blind duration per flash, enemy blind time, flashes thrown, and grenade ADR.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
limit: limitSchema({ default: 20, max: 100 }),
|
|
286
|
+
},
|
|
287
|
+
additionalProperties: false,
|
|
288
|
+
},
|
|
289
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
290
|
+
handler: async (args, ctx) => {
|
|
291
|
+
const reqId = newRequestId();
|
|
292
|
+
const limit = clampInt(args.limit, 20, 1, 100);
|
|
293
|
+
const path = `/cs2/stats/utility`;
|
|
294
|
+
const res = await fetchJson(ctx, path, { query: { limit } });
|
|
295
|
+
if (!res.ok) {
|
|
296
|
+
return errorEnvelope({
|
|
297
|
+
code: mapHttpToCode(res.status),
|
|
298
|
+
message: `Utility leaderboard fetch failed: ${res.status} on ${path}`,
|
|
299
|
+
game: 'cs2',
|
|
300
|
+
source: 'cs2_utility_leaderboard',
|
|
301
|
+
requestId: reqId,
|
|
302
|
+
httpStatus: res.status,
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
const payload = unwrap(res.data);
|
|
306
|
+
return successEnvelope({
|
|
307
|
+
data: payload,
|
|
308
|
+
game: 'cs2',
|
|
309
|
+
source: 'cs2_utility_leaderboard',
|
|
310
|
+
requestId: reqId,
|
|
311
|
+
rateLimit: res.headers,
|
|
312
|
+
});
|
|
313
|
+
},
|
|
314
|
+
};
|
|
315
|
+
/**
|
|
316
|
+
* 7. cs2_veto_sequence
|
|
317
|
+
* Map pick/ban sequence and veto history for a match.
|
|
318
|
+
*/
|
|
319
|
+
export const cs2VetoSequence = {
|
|
320
|
+
name: 'cs2_veto_sequence',
|
|
321
|
+
description: 'Map pick/ban veto sequence for a CS2 match. Shows which team banned which map, map picks, and decider map in chronological order.',
|
|
322
|
+
inputSchema: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
matchId: stringSchema('Match ID (e.g. "2397603" or "cs2-match-2397603"). Required.', '2397603'),
|
|
326
|
+
},
|
|
327
|
+
required: ['matchId'],
|
|
328
|
+
additionalProperties: false,
|
|
329
|
+
},
|
|
330
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
331
|
+
handler: async (args, ctx) => {
|
|
332
|
+
const reqId = newRequestId();
|
|
333
|
+
const rawId = typeof args.matchId === 'string' ? args.matchId.trim() : '';
|
|
334
|
+
if (!rawId) {
|
|
335
|
+
return errorEnvelope({
|
|
336
|
+
code: 'VALIDATION',
|
|
337
|
+
message: 'matchId is required',
|
|
338
|
+
game: 'cs2',
|
|
339
|
+
source: 'cs2_veto_sequence',
|
|
340
|
+
requestId: reqId,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
const cleanId = rawId.replace(/^cs2-match-/, '');
|
|
344
|
+
const path = `/cs2/matches/${encodeURIComponent(cleanId)}/head-to-head`;
|
|
345
|
+
const res = await fetchJson(ctx, path);
|
|
346
|
+
if (!res.ok) {
|
|
347
|
+
return errorEnvelope({
|
|
348
|
+
code: mapHttpToCode(res.status),
|
|
349
|
+
message: `Veto sequence fetch failed: ${res.status} on ${path}`,
|
|
350
|
+
game: 'cs2',
|
|
351
|
+
source: 'cs2_veto_sequence',
|
|
352
|
+
requestId: reqId,
|
|
353
|
+
httpStatus: res.status,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
const payload = unwrap(res.data);
|
|
357
|
+
const r = asRecord(payload);
|
|
358
|
+
const veto = r?.veto ?? r?.vetoSequence ?? payload;
|
|
359
|
+
return successEnvelope({
|
|
360
|
+
data: veto,
|
|
361
|
+
game: 'cs2',
|
|
362
|
+
source: 'cs2_veto_sequence',
|
|
363
|
+
requestId: reqId,
|
|
364
|
+
rateLimit: res.headers,
|
|
365
|
+
});
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
/**
|
|
369
|
+
* 8. cs2_roster_transfers
|
|
370
|
+
* Recent professional roster changes, benchings, and transfers.
|
|
371
|
+
*/
|
|
372
|
+
export const cs2RosterTransfers = {
|
|
373
|
+
name: 'cs2_roster_transfers',
|
|
374
|
+
description: 'Recent professional CS2 roster changes, player benchings, stand-ins, and team transfers.',
|
|
375
|
+
inputSchema: {
|
|
376
|
+
type: 'object',
|
|
377
|
+
properties: {
|
|
378
|
+
limit: limitSchema({ default: 20, max: 100 }),
|
|
379
|
+
},
|
|
380
|
+
additionalProperties: false,
|
|
381
|
+
},
|
|
382
|
+
annotations: READ_TOOL_ANNOTATIONS,
|
|
383
|
+
handler: async (args, ctx) => {
|
|
384
|
+
const reqId = newRequestId();
|
|
385
|
+
const limit = clampInt(args.limit, 20, 1, 100);
|
|
386
|
+
const path = `/cs2/transfers/recent`;
|
|
387
|
+
let res = await fetchJson(ctx, path, { query: { limit } });
|
|
388
|
+
if (!res.ok && res.status === 404) {
|
|
389
|
+
res = await fetchJson(ctx, `/cs2/roster-changes/recent`, { query: { limit } });
|
|
390
|
+
}
|
|
391
|
+
if (!res.ok) {
|
|
392
|
+
return errorEnvelope({
|
|
393
|
+
code: mapHttpToCode(res.status),
|
|
394
|
+
message: `Roster transfers fetch failed: ${res.status} on ${path}`,
|
|
395
|
+
game: 'cs2',
|
|
396
|
+
source: 'cs2_roster_transfers',
|
|
397
|
+
requestId: reqId,
|
|
398
|
+
httpStatus: res.status,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
const payload = unwrap(res.data);
|
|
402
|
+
return successEnvelope({
|
|
403
|
+
data: payload,
|
|
404
|
+
game: 'cs2',
|
|
405
|
+
source: 'cs2_roster_transfers',
|
|
406
|
+
requestId: reqId,
|
|
407
|
+
rateLimit: res.headers,
|
|
408
|
+
});
|
|
409
|
+
},
|
|
410
|
+
};
|
|
411
|
+
/** All curated CS2 tools */
|
|
412
|
+
export const cs2Tools = [
|
|
413
|
+
cs2LiveScoreboard,
|
|
414
|
+
cs2RoundEconomy,
|
|
415
|
+
cs2OpeningDuels,
|
|
416
|
+
cs2Clutches,
|
|
417
|
+
cs2TeamMapStats,
|
|
418
|
+
cs2UtilityLeaderboard,
|
|
419
|
+
cs2VetoSequence,
|
|
420
|
+
cs2RosterTransfers,
|
|
421
|
+
];
|
package/dist/tools/index.js
CHANGED
|
@@ -6,8 +6,13 @@ import { playerTools } from './player.js';
|
|
|
6
6
|
import { teamTools } from './team.js';
|
|
7
7
|
import { standingsTools } from './standings.js';
|
|
8
8
|
import { rankingsTools } from './rankings.js';
|
|
9
|
+
import { leaderboardTools } from './leaderboard.js';
|
|
9
10
|
import { insightTools } from './insight.js';
|
|
10
|
-
|
|
11
|
+
import { cs2Tools } from './cs2.js';
|
|
12
|
+
import { oddsTools } from './odds.js';
|
|
13
|
+
import { tournamentTools } from './tournaments.js';
|
|
14
|
+
import { scheduleTools } from './schedule.js';
|
|
15
|
+
/** Curated outcome-tool catalog. Order matches preferred cold-start ladder. */
|
|
11
16
|
export const allTools = [
|
|
12
17
|
...metaTools.filter((t) => t.name === 'list_capabilities' || t.name === 'api_health'),
|
|
13
18
|
...resolveTools,
|
|
@@ -17,7 +22,14 @@ export const allTools = [
|
|
|
17
22
|
...teamTools,
|
|
18
23
|
...standingsTools,
|
|
19
24
|
...rankingsTools,
|
|
25
|
+
...leaderboardTools,
|
|
26
|
+
// Tennis depth added after the coverage audit: odds, tournament discovery and
|
|
27
|
+
// the daily schedule were reachable only through call_api.
|
|
28
|
+
...oddsTools,
|
|
29
|
+
...tournamentTools,
|
|
30
|
+
...scheduleTools,
|
|
20
31
|
...insightTools,
|
|
32
|
+
...cs2Tools,
|
|
21
33
|
// Escape-hatch pair last: discover routes, then call one.
|
|
22
34
|
...metaTools.filter((t) => t.name === 'list_routes' || t.name === 'call_api'),
|
|
23
35
|
];
|